<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jay</title>
    <description>The latest articles on Forem by Jay (@jaygooby).</description>
    <link>https://forem.com/jaygooby</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F227078%2Fb15a8904-d776-44aa-9c88-9bf2950009b8.jpg</url>
      <title>Forem: Jay</title>
      <link>https://forem.com/jaygooby</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jaygooby"/>
    <language>en</language>
    <item>
      <title>asdf, python and automatically enabling virtual envs</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Tue, 13 Jun 2023 12:08:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/asdf-python-and-automatically-enabling-virtual-envs-1inh</link>
      <guid>https://forem.com/jaygooby/asdf-python-and-automatically-enabling-virtual-envs-1inh</guid>
      <description>&lt;p&gt;I've switched wholly to &lt;a href="https://asdf-vm.com"&gt;asdf&lt;/a&gt; for all my projects now, both work and personal, thanks to &lt;a href="https://ruby.social/@emma"&gt;@emma&lt;/a&gt; who first put me on to it. In the day job, it's used for managing ruby and node versions with the &lt;a href="https://github.com/asdf-vm/asdf-ruby"&gt;asdf-ruby&lt;/a&gt; and &lt;a href="https://github.com/asdf-vm/asdf-nodejs"&gt;asdf-nodejs&lt;/a&gt; plugins.&lt;/p&gt;

&lt;p&gt;I've started to use the &lt;a href="https://github.com/asdf-community/asdf-python"&gt;asdf-python&lt;/a&gt; plugin here on this blog, as I have a small &lt;a href="https://jay.gooby.org/2022/03/17/archiving-and-posting-my-music-we-like-slack-chats"&gt;python script I use to extract my music posts&lt;/a&gt; from Slack to archive to my &lt;a href="https://jay.gooby.org/music-i-like/"&gt;Music I've liked&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;When I first switched over, everything stopped working, because I'd forgotten to first enable the python virtual env by calling &lt;code&gt;source env/bin/activate&lt;/code&gt;. I've since forgotten to do this a couple of times, so it's time to automate!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hyperupcall/autoenv"&gt;autoenv&lt;/a&gt; will run commands in a &lt;code&gt;.env&lt;/code&gt; file when you cd into a directory, and will also run any in &lt;code&gt;.env.leave&lt;/code&gt; when you &lt;code&gt;cd&lt;/code&gt; out, so it's perfect for setting and unsetting your python env.&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://github.com/hyperupcall/autoenv#installation-method"&gt;installed autoenv&lt;/a&gt;, and created the &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.leave&lt;/code&gt; files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"AUTOENV_ENABLE_LEAVE=1&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;source &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$AUTOENV_CUR_DIR&lt;/span&gt;&lt;span class="s2"&gt;/env/bin/activate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env
&lt;span class="nb"&gt;echo &lt;/span&gt;deactivate &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env.leave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then when you next &lt;code&gt;cd&lt;/code&gt; into your project folder, you'll be prompted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;autoenv:
autoenv: WARNING:
autoenv: This is the first time you are about to source /Users/jay/Library/CloudStorage/Dropbox/Work/gooby.org/2021/2021.gooby.org/.env:
autoenv:
autoenv:   --- (begin contents) ---------------------------------------
autoenv:     AUTOENV_ENABLE_LEAVE=1$
autoenv:     source "$AUTOENV_CUR_DIR/env/bin/activate"$
autoenv:
autoenv:   --- (end contents) -----------------------------------------
autoenv:
autoenv: Are you sure you want to allow this? (y/N)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to check you really want to run that &lt;code&gt;.env&lt;/code&gt; in the future. Ditto the first time you &lt;code&gt;cd&lt;/code&gt; out of the project&lt;sup id="fnref1"&gt;1&lt;/sup&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;autoenv:
autoenv: WARNING:
autoenv: This is the first time you are about to source /Users/jay/Work/gooby.org/2021/2021.gooby.org/.env.leave:
autoenv:
autoenv:   --- (begin contents) ---------------------------------------
autoenv:     deactivate$
autoenv:
autoenv:   --- (end contents) -----------------------------------------
autoenv:
autoenv: Are you sure you want to allow this? (y/N)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From then on, you won't see the prompts again. You can hide them completely if you add &lt;code&gt;AUTOENV_ASSUME_YES=1&lt;/code&gt; to the top of both &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.leave&lt;/code&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Because we're passing the full path to activate in the &lt;code&gt;.env&lt;/code&gt; file using &lt;code&gt;source "$AUTOENV_CUR_DIR/env/bin/activate"&lt;/code&gt;, if we cd within the project folder structure, we'll still keep our activated environment, because autoenv looks for a parent &lt;code&gt;.env&lt;/code&gt; file. If we just had &lt;code&gt;source env/bin/activate&lt;/code&gt; then we'd lose the environment activation, because the path to &lt;code&gt;env/bin/activate&lt;/code&gt; is relative and would no longer be correct. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>asdf</category>
      <category>python</category>
    </item>
    <item>
      <title>Clink makes working with Windows’ `cmd.exe` so much nicer</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Tue, 06 Jun 2023 15:16:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/clink-makes-working-with-windows-cmdexe-so-much-nicer-22nb</link>
      <guid>https://forem.com/jaygooby/clink-makes-working-with-windows-cmdexe-so-much-nicer-22nb</guid>
      <description>&lt;p&gt;Due to a project at work, I’m using Windows’ &lt;code&gt;cmd.exe&lt;/code&gt; for something like the first time in a decade. I can’t use &lt;a href="https://learn.microsoft.com/en-us/windows/wsl"&gt;WSL&lt;/a&gt; on the project, and &lt;a href="https://chrisant996.github.io/clink/"&gt;Clink&lt;/a&gt; with its syntax colouring and familiar readline history has already made it a 100 times more pleasant!&lt;/p&gt;

&lt;p&gt;You get a searchable history and tab completion. What wasn’t immediately obvious to me, was how to choose the suggested completion - if you press &lt;code&gt;tab&lt;/code&gt;, Clink will start cycling through commands and paths; you need to press &lt;code&gt;End&lt;/code&gt; or use the right arrow key. Loads more &lt;a href="https://chrisant996.github.io/clink/clink.html#how-completion-works"&gt;detail on completetion here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The repo lives at &lt;a href="https://github.com/chrisant996/clink"&gt;github.com/chrisant996/clink&lt;/a&gt; - give it a star!&lt;/p&gt;

</description>
      <category>cli</category>
      <category>windows</category>
    </item>
    <item>
      <title>Automating Jekyll card generation with ruby's Ferrum gem</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Thu, 12 May 2022 07:04:44 +0000</pubDate>
      <link>https://forem.com/jaygooby/automating-jekyll-card-generation-with-rubys-ferrum-gem-3h34</link>
      <guid>https://forem.com/jaygooby/automating-jekyll-card-generation-with-rubys-ferrum-gem-3h34</guid>
      <description>&lt;p&gt;I've now got a simple, ruby-only, automated way of making &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image"&gt;Twitter summary cards&lt;/a&gt; and &lt;a href="https://ogp.me"&gt;OpenGraph image previews&lt;/a&gt; for the posts on this blog. Now I just need to iterate on the &lt;a href="///images/cards/2022-05-11-automating-jekyll-card-generation-with-ruby-ferrum.png"&gt;currently horrible&lt;/a&gt; version-zero look of them!&lt;/p&gt;

&lt;p&gt;Historically you would have done this using javascript calling &lt;a href="https://developers.google.com/web/tools/puppeteer"&gt;Puppeteer&lt;/a&gt; driving a headless Chrome instance to generate the screenshot. &lt;a href="https://mikkelhartmann.dk/2021/01/17/socal-media-images.html"&gt;Mikkel Hartmann&lt;/a&gt; has a great write up of doing it this way.&lt;/p&gt;

&lt;p&gt;Ruby now has the awesome &lt;a href="https://github.com/rubycdp/ferrum"&gt;Ferrum gem&lt;/a&gt; which uses the &lt;a href="https://chromedevtools.github.io/devtools-protocol/"&gt;Chrome DevTools Protocol&lt;/a&gt; to control Chrome, so you can drop the need for JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://jekyllrb.com"&gt;Jekyll&lt;/a&gt; install I've got a &lt;code&gt;_cards&lt;/code&gt; symbolic link to my &lt;code&gt;_posts&lt;/code&gt; directory, and in my &lt;code&gt;_config.yml&lt;/code&gt; I've got a &lt;code&gt;defaults&lt;/code&gt; section that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_posts"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_cards"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;card&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to do this so your posts don't specify &lt;code&gt;layout: post&lt;/code&gt; in  their individual &lt;a href="https://jekyllrb.com/docs/front-matter/"&gt;frontmatter&lt;/a&gt;. If they did, this would override the &lt;code&gt;layout: card&lt;/code&gt; that we need the &lt;code&gt;cards&lt;/code&gt; collection to use, and we'd just end up with a screenshot of the entire page.&lt;/p&gt;

&lt;p&gt;So there's now a &lt;code&gt;cards&lt;/code&gt; &lt;a href="https://jekyllrb.com/docs/collections/"&gt;collection&lt;/a&gt; that mirrors my posts, but that render with their own &lt;code&gt;_layouts/cards.html&lt;/code&gt; layout. I use this to generate the post image from.&lt;/p&gt;

&lt;p&gt;Because of the symbolic link, every &lt;code&gt;_post&lt;/code&gt; entry like &lt;code&gt;_posts/2022-05-11-automating-jekyll-card-generation-with-ruby-ferrum.md&lt;/code&gt; will have a corresponding &lt;a href="https://jay.gooby.org/cards/2022-05-11-automating-jekyll-card-generation-with-ruby-ferrum.html"&gt;&lt;code&gt;/cards/2022-05-11-automating-jekyll-card-generation-with-ruby-ferrum.html&lt;/code&gt;&lt;/a&gt; page when you run &lt;code&gt;jekyll serve&lt;/code&gt; or &lt;code&gt;jekyll build&lt;/code&gt;.  It's these pages we'll point Chrome at to generate the images.&lt;/p&gt;

&lt;p&gt;After adding the Ferrum gem to my &lt;code&gt;Gemfile&lt;/code&gt;, I can use this ruby to make images for my posts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"Rubygems"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"Ferrum"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
  &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;go_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4000/cards/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# see all the options here https://github.com/rubycdp/ferrum#screenshots&lt;/span&gt;
  &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s2"&gt;"./images/cards/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="ss"&gt;full: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="c1"&gt;# final image size is window_size x scale&lt;/span&gt;
                     &lt;span class="ss"&gt;scale: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ferrum&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;window_size: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Check what cards we need to make&lt;/span&gt;
&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"_posts/*"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

  &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;png&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;
  &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".html"&lt;/span&gt;

  &lt;span class="n"&gt;generate_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./images/cards/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will use your local version of the jekyll site to create the screenshots from. To ensure it's running, and for a bunch of other tasks, I use a &lt;a href="https://makefiletutorial.com"&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/a&gt;. Here's a simplified version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;deploy&lt;/span&gt;

&lt;span class="nl"&gt;clean-site&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="c"&gt;# Do this step to ensure we don't accidentally publish drafts etc
&lt;/span&gt; &lt;span class="err"&gt;rm&lt;/span&gt; &lt;span class="err"&gt;-rf&lt;/span&gt; &lt;span class="err"&gt;_site&lt;/span&gt;

&lt;span class="c"&gt;# JEKYLL_ENV=production to avoid localhost urls in your jekyll-seo-tag links
&lt;/span&gt;&lt;span class="nl"&gt;serve-site&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="nv"&gt;JEKYLL_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production jekyll serve &amp;amp; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/jekyll.pid &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;15

&lt;span class="nl"&gt;build-cards&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serve-site&lt;/span&gt;
    ruby ./scripts/generate-cards.rb
    &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; &lt;span class="nf"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;shell&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/jekyll.pid&lt;span class="nf"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/jekyll.pid

&lt;span class="nl"&gt;deploy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clean-site build-cards&lt;/span&gt;
    rsync &lt;span class="nt"&gt;--progress&lt;/span&gt; &lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; ssh  _site/ gooby.org:~/jay.gooby.org/public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;deploy&lt;/code&gt; recipe cleans the site, serves it, generates the images using the ruby script, then "deploys" the site by copying it to my remote host with &lt;code&gt;rsync&lt;/code&gt;. Because it's the default recipe, I can just call &lt;code&gt;make&lt;/code&gt; in the root of my project to get the site live.&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>ruby</category>
      <category>ferrum</category>
      <category>cdp</category>
    </item>
    <item>
      <title>Use a basic Gmail account to “Send mail as” with a domain that uses Cloudflare email routing</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Thu, 05 May 2022 23:00:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing-89b</link>
      <guid>https://forem.com/jaygooby/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing-89b</guid>
      <description>&lt;p&gt;If you’ve got a basic Gmail account like &lt;code&gt;your.name@gmail.com&lt;/code&gt; (ie not a full&lt;a href="https://workspace.google.com"&gt;Google Workspace&lt;/a&gt; account) and a custom domain that you want to send email from, using Gmail’s “Send mail as” functionality, and you want to use this domain with &lt;a href="https://developers.cloudflare.com/email-routing/"&gt;Cloudflare’s email routing&lt;/a&gt; then this guide is for you…&lt;/p&gt;

&lt;h2&gt;
  
  
  ytho?
&lt;/h2&gt;

&lt;p&gt;First some background. Why would you want to do this? Typically, if you have a custom domain that you want to use for email, then you’d have to administer or have access to an email server for it. This email server will need to have DNS &lt;code&gt;MX&lt;/code&gt; records set up for it, and will also need a good &lt;a href="https://en.wikipedia.org/wiki/Cold_email#Bad_email_address_reputation"&gt;sender reputation&lt;/a&gt; if your emails aren’t going to end up in spam.&lt;/p&gt;

&lt;p&gt;Normally, to “Send mail as” in Gmail, you’d enter the custom domain’s email server SMTP details, and your email username and password. Then when you send emails from Gmail, Google contacts that mail server, and the emails go out from it, rather than from Gmail.&lt;/p&gt;

&lt;p&gt;The advantage of this is that you can let the domain’s email server sign the emails with a DKIM signature, as well as having whatever email addresses you want on that custom domain. The downside, is that you need an email server, and inboxes for those custom addresses.&lt;/p&gt;

&lt;p&gt;However, now that Cloudflare have made email routing available for domains where they are the authoritative nameserver (host your domain’s DNS records), you can use Gmail to send emails using your custom domain, and Cloudflare to route them, doing away with the need for a custom mailserver entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use Gmail’s “Send mail as” with a custom domain and Cloudflare email routing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Configure Cloudflare
&lt;/h3&gt;

&lt;p&gt;First you’ll need a domain configured to use Cloudflare, if not you’ll need to &lt;a href="https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/"&gt;change the domain’s authoritative nameservers&lt;/a&gt; first. Then you can &lt;a href="https://developers.cloudflare.com/email-routing/get-started/enable-email-routing/"&gt;setup Cloudflare email routing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You’ll be given some DNS records to set up by default, but you’ll want to alter these slightly.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1 Edit your SPF record
&lt;/h4&gt;

&lt;p&gt;Your &lt;code&gt;spf TXT&lt;/code&gt; record will need to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=spf1 a mx include:_spf.google.com include:_spf.mx.cloudflare.net ~all

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

&lt;/div&gt;



&lt;p&gt;– we’ve added &lt;code&gt;a mx include:_spf.google.com&lt;/code&gt; to indicate that google can send on our behalf, along with Cloudflare.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.2 Edit your DMARC record
&lt;/h4&gt;

&lt;p&gt;Change it so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=DMARC1; p=none; rua=mailto:you@example.com; aspf=r;

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

&lt;/div&gt;



&lt;p&gt;– where &lt;code&gt;example.com&lt;/code&gt; is your custom domain. The email address in the &lt;code&gt;rua&lt;/code&gt; field can be anything at your custom domain; it’s where email providers will periodically send you aggregated reports about your domain’s email.&lt;/p&gt;

&lt;p&gt;We’ve set the domain policy &lt;code&gt;p&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt; (other options are &lt;code&gt;quarantine&lt;/code&gt; and &lt;code&gt;reject&lt;/code&gt; if sending mail fails to pass DMARC checks). The SPF alignment policy &lt;code&gt;aspf&lt;/code&gt; is set to relaxed &lt;code&gt;r&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Setting the above is critical to not getting your custom domain’s email bounced or rejected, especially as it won’t be DKIM signed by Gmail.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3 Create an Email Route
&lt;/h4&gt;

&lt;p&gt;In your Cloudflare dashboard, click the &lt;strong&gt;Email&lt;/strong&gt; option, then add a destination address - use your regular gmail address &lt;code&gt;your.name@gmail.com&lt;/code&gt; etc. You’ll need to click the email that’s sent to you to confirm this.&lt;/p&gt;

&lt;p&gt;Once you’ve done that, you can add a custom email address e.g. &lt;code&gt;special@example.com&lt;/code&gt; and route it the gmail address you just confirmed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Configure Gmail
&lt;/h3&gt;

&lt;p&gt;Next we’ll do the Gmail configuration.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1 Create an app password
&lt;/h4&gt;

&lt;p&gt;First you’ll need to create an email app password in your Google account. Go to &lt;a href="https://myaccount.google.com/apppasswords"&gt;https://myaccount.google.com/apppasswords&lt;/a&gt; and choose &lt;code&gt;Email&lt;/code&gt; from the “Select app” dropdown and &lt;code&gt;Other&lt;/code&gt; for the device.&lt;/p&gt;

&lt;p&gt;Copy the password that’s generated for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2 Add the email address to Gmail’s “Send mail as” section
&lt;/h4&gt;

&lt;p&gt;There are &lt;a href="https://support.google.com/mail/answer/22370"&gt;detailed instructions on adding a new email address&lt;/a&gt;, but it’s relatively easy. Go to your &lt;a href="https://mail.google.com/mail/#settings/accounts"&gt;Gmail account settings&lt;/a&gt; and in the &lt;strong&gt;Send mail as:&lt;/strong&gt; section, click the &lt;strong&gt;Add another email address option&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the pop-up, enter your custom domain’s email address, untick the &lt;strong&gt;Treat as an alias&lt;/strong&gt; option, click the &lt;strong&gt;Specify a different “reply-to” address&lt;/strong&gt; link and add the same custom email address in there. Then click &lt;strong&gt;Next Step&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uTE0jj1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jay.gooby.org/images/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing/Screenshot%25202022-05-06%2520at%252017.50.18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uTE0jj1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jay.gooby.org/images/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing/Screenshot%25202022-05-06%2520at%252017.50.18.png" alt="Gmail form to add a custom sending email address" width="880" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overwrite the value for &lt;strong&gt;SMTP Server&lt;/strong&gt;. Use &lt;code&gt;smtp.gmail.com&lt;/code&gt;, leave the Port option as is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Username&lt;/strong&gt; should be the name part of your regular gmail address, so if you’re &lt;code&gt;your.name@gmail.com&lt;/code&gt; then you’d enter &lt;code&gt;your.name&lt;/code&gt;. The &lt;strong&gt;Password&lt;/strong&gt; is the email app password that you generated above. Click the &lt;strong&gt;Add Account&lt;/strong&gt; button. You’ll be sent an email to the custom email address you entered. Click the link in this, and you’re good to go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eS703F3Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jay.gooby.org/images/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing/Screenshot%25202022-05-06%2520at%252017.52.44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eS703F3Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jay.gooby.org/images/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing/Screenshot%25202022-05-06%2520at%252017.52.44.png" alt="Gmail form for smtp server details" width="880" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  All done
&lt;/h3&gt;

&lt;p&gt;You’ll be able to compose emails in Gmail and set the sender to your custom domain email address, and when people reply, Cloudflare will route these back to your regular gmail account, all without you needing a separate custom mail server.&lt;/p&gt;

</description>
      <category>gmail</category>
      <category>cloudflare</category>
      <category>spf</category>
      <category>dmarc</category>
    </item>
    <item>
      <title>Recursively searching and downloading objects from AWS S3 with the `aws` CLI</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Mon, 25 Apr 2022 23:00:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/recursively-searching-and-downloading-objects-from-aws-s3-with-the-aws-cli-4f7k</link>
      <guid>https://forem.com/jaygooby/recursively-searching-and-downloading-objects-from-aws-s3-with-the-aws-cli-4f7k</guid>
      <description>&lt;p&gt;Yesterday I was asked if we had some (very) old files from a project. They were originally generated by one of our freelancers, who at the end of the project, had given us a backup of his external drive for archiving.&lt;/p&gt;

&lt;p&gt;My go-to for this kind of long-term storage is &lt;a href="https://aws.amazon.com/s3/"&gt;AWS S3&lt;/a&gt; or &lt;a href="https://aws.amazon.com/s3/storage-classes/glacier/"&gt;Glacier&lt;/a&gt;. I’d backed up the drive to a &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingBucket.html"&gt;bucket&lt;/a&gt; and had forgotten about it until now.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;aws&lt;/code&gt; CLI is powerful but inscrutable, given that it can do anything the web console can, for any AWS service.&lt;/p&gt;

&lt;p&gt;The files were from Sibelius (&lt;code&gt;.sib&lt;/code&gt;) and for Clarinet, Flute and Sax. Cue some googling about the &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/s3api/list-objects-v2.html"&gt;&lt;code&gt;aws s3 list-objects&lt;/code&gt; command&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can combine the search terms to the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-filter.html#cli-usage-filter-client-side"&gt;&lt;code&gt;--query&lt;/code&gt; parameter&lt;/a&gt; to limit the results of the &lt;code&gt;list-objects&lt;/code&gt; call. Searches are case sensitive, and I wasn’t sure how the files would have been named originally, so I went for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--query "Contents[?(contains(Key,'larinet') || contains(Key,'lute') || contains(Key,'ax')) &amp;amp;&amp;amp; contains(Key,'.sib')]

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

&lt;/div&gt;



&lt;p&gt;To give me the best chance of finding &lt;code&gt;Clarinet&lt;/code&gt; or &lt;code&gt;clarinet&lt;/code&gt;, etc (no good of they’re named &lt;code&gt;CLARINET&lt;/code&gt;, but what kind of monster would do that?).&lt;/p&gt;

&lt;p&gt;Because I only need the path to the S3 object for the &lt;code&gt;get-object&lt;/code&gt; call, I’ll filter on just those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws s3api list-objects --bucket "my-bucket-name" --prefix 'path/to/files/' --query "Contents[?(contains(Key,'larinet') || contains(Key,'lute') || contains(Key,'ax')) &amp;amp;&amp;amp; contains(Key,'.sib')]"

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

&lt;/div&gt;



&lt;p&gt;This gives me an array of json objects like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    {
        "Key": "path/to/files/Project backup/Clarinets/example.sib",
        "LastModified": "2017-09-18T21:01:19+00:00",
        "ETag": "\"e9abc61fab413ec5de1126e7ab48ac38\"",
        "Size": 226180,
        "StorageClass": "STANDARD",
        "Owner": {
            "DisplayName": "owner-name",
            "ID": "d9fc4a52c54711ec9d640242ac120002f54ce640c54711ec9d640242ac120002"
        }
    },
    ...,
    ...,
]

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

&lt;/div&gt;



&lt;p&gt;We can then loop through these, downloading the files. Instead of just dumping them all in one directory, let’s replicate the path structure they’ve been saved to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Get the list of objects to download - note that the Key search is case sensitive
# hence using larinet instead of clarinet or Clarinet, etc
aws s3api list-objects --bucket "my-bucket-name" --prefix 'path/to/files/' --query "Contents[?(contains(Key,'larinet') || contains(Key,'lute') || contains(Key,'ax')) &amp;amp;&amp;amp; contains(Key,'.sib')]" &amp;gt; files.json

# loop through the list, grabbing each object, putting it in the relevant folder
cat files.json | jq -r '.[] | .Key'| while read key
  do dir="$(dirname "$key")"
     file="$(basename "$key")"     
     mkdir -p "$dir"
     aws s3api get-object --bucket "my-bucket-name" --key "$key" "${dir}"/"${file}"
  done

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

&lt;/div&gt;



</description>
      <category>bash</category>
      <category>aws</category>
      <category>s3</category>
    </item>
    <item>
      <title>Set up a freshly installed macos Monterey machine as a gitlab-runner host</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Wed, 13 Apr 2022 16:09:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/set-up-a-freshly-installed-macos-monterey-machine-as-a-gitlab-runner-host-1h55</link>
      <guid>https://forem.com/jaygooby/set-up-a-freshly-installed-macos-monterey-machine-as-a-gitlab-runner-host-1h55</guid>
      <description>&lt;p&gt;You've got a newly installed macos Monterey machine you want to use as a gitlab-runner host to test and build your ios and macos projects on. What do you need?&lt;/p&gt;

&lt;h2&gt;
  
  
  macos command line tools
&lt;/h2&gt;

&lt;p&gt;Get the macos Command Line Tools. In a terminal, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xcode-select --install
softwareupdate -l

# Then pick the most recent version of Command Line tools, e.g.
softwareupdate --install "Command Line Tools for Xcode-13.3"
sudo xcodebuild -license accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  homebrew
&lt;/h2&gt;

&lt;p&gt;Then install &lt;a href="https://brew.sh"&gt;homebrew&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and this &lt;code&gt;Brewfile&lt;/code&gt; (save it to &lt;code&gt;~/Brewfile&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew "asdf"
brew "bash"
brew "bash-completion"
brew "ack"
brew "autoconf"
brew "automake"
brew "coreutils"
brew "curl"
brew "gettext"
brew "git"
brew "gnu-sed"
brew "gnu-tar"
brew "gnu-indent"
brew "gnu-which"
brew "pkg-config"
brew "robotsandpencils/made/xcodes"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in the same folder as the &lt;code&gt;Brewfile&lt;/code&gt; (e.g. &lt;code&gt;cd ~ &amp;amp;&amp;amp; brew bundle&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Xcode
&lt;/h2&gt;

&lt;p&gt;And get and install the latest Xcode so you have the ios simulator, etc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XCODES_USERNAME="your_apple_id" XCODES_PASSWORD="your_apple_id_password" xcodes install --latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;gitlab-runner&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now install and configure the gitlab-runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install gitlab-runner
# uses `arch` to fetch the correct version
[ "$(arch)" = "i386" ] &amp;amp;&amp;amp; sudo curl --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64" || sudo curl --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64"

sudo chmod +x /usr/local/bin/gitlab-runner

# Register the runner
gitlab-runner register

# Install gitlab-runner as a user service.
# PLEASE NOTE you must enable autologin to this user
# on the macos machine or your runner will be showing
# as `stuck` the next time your macos machine reboots
# and you don't login.
su - $(whoami)
cd ~
gitlab-runner install
gitlab-runner start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Disable macos sleep for the logged in user
&lt;/h2&gt;

&lt;p&gt;Ensure the mac doesn't sleep, if you don't set your mac not to sleep, it will, and then your runner won't be able to pick up jobs, and will be showing as &lt;code&gt;stuck&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pmset -a sleep 0; sudo pmset -a hibernatemode 0; sudo pmset -a disablesleep 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optional ruby install with &lt;code&gt;asdf&lt;/code&gt; when using Fastlane
&lt;/h2&gt;

&lt;p&gt;I also need ruby, because I'm using &lt;a href="https://fastlane.tools"&gt;Fastlane&lt;/a&gt; to manage the ios and macos builds, and I'm using &lt;a href="https://asdf-vm.com"&gt;&lt;code&gt;asdf&lt;/code&gt;&lt;/a&gt; to manage ruby versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "ruby 2.7.6" &amp;gt; ~/.tool-versions
asdf plugin-add ruby
asdf install ruby 2.7.6
gem install fastlane
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>gitlab</category>
      <category>ci</category>
      <category>ios</category>
      <category>xcode</category>
    </item>
    <item>
      <title>Positional arguments in git aliases</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Mon, 12 Jul 2021 23:00:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/positional-arguments-in-git-aliases-3fp5</link>
      <guid>https://forem.com/jaygooby/positional-arguments-in-git-aliases-3fp5</guid>
      <description>&lt;p&gt;I wanted to make a &lt;code&gt;git add-and-commit&lt;/code&gt; alias, but getting the positional arguments to handle filenames with spaces in was a lot more complicated than I’d expected.&lt;/p&gt;

&lt;p&gt;My naive first try was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alias]
  add-and-commit = "!git add $@ &amp;amp;&amp;amp; git commit"

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

&lt;/div&gt;



&lt;p&gt;which works as long as you don’t have filenames that you don’t need to quote, and that you’re happy using git’s default method for capturing your commit message. You can call it like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add-and-commit readme.md example.txt

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

&lt;/div&gt;



&lt;p&gt;and it will add the two files &lt;code&gt;readme.md&lt;/code&gt; and &lt;code&gt;example.txt&lt;/code&gt; and then ask for a commit message from you.&lt;/p&gt;

&lt;p&gt;But if one of your files is e.g. called &lt;code&gt;file with a space.txt&lt;/code&gt; it barfs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add-and-commit readme.md "file with a space.txt"
fatal: pathspec 'file' did not match any files

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

&lt;/div&gt;



&lt;p&gt;Also, what if you want to pass your commit message at the same time? Now we need to consider &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Positional-Parameters.html"&gt;positional parameters&lt;/a&gt;, how we ensure they’re escaped properly and how we can grab some of them for the &lt;code&gt;git add&lt;/code&gt; portion of the alias, and the others for the &lt;code&gt;git commit&lt;/code&gt; portion. The aim is so that we can call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add-and-commit readme.md "file with a space.txt" -m "A nice long commit message"

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Capture the filenames
&lt;/h2&gt;

&lt;p&gt;Breaking this down, we want to separately capture all the filename arguments (in this case, &lt;code&gt;readme.md&lt;/code&gt; and &lt;code&gt;file with space.txt&lt;/code&gt;) before the last two, which are the &lt;code&gt;-m&lt;/code&gt; and the commit message itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html"&gt;bash parameter expansion&lt;/a&gt; is a our friend&lt;sup id="fnref:1"&gt;1&lt;/sup&gt; here. This &lt;a href="https://askubuntu.com/a/1011660/233579"&gt;Ask Ubuntu&lt;/a&gt; answer has some nice examples that helped me out.&lt;/p&gt;

&lt;p&gt;We want args &lt;code&gt;$1&lt;/code&gt; to the total number of args (&lt;code&gt;$#&lt;/code&gt;) minus 2, for the filenames (-2, because of the &lt;code&gt;-m&lt;/code&gt; and then the message itself).&lt;/p&gt;

&lt;p&gt;We need each argument quoted in case it has spaces in, so we use the quoted &lt;code&gt;"{$@}"&lt;/code&gt; format instead of &lt;code&gt;"{$*}"&lt;/code&gt; and we use bash arithetic to subtract 2 from the argument length &lt;code&gt;$(($#-2))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So to capture all the filenames, correctly quoted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# arguments, quoted, starting from 1 and going to -2 the total number of arguments
"${@:1:$(($#-2))}"

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Capture the commit message arguments
&lt;/h2&gt;

&lt;p&gt;And then we want to capture the last two arguments to pass to &lt;code&gt;git commit&lt;/code&gt;. The space before the &lt;code&gt;-2&lt;/code&gt; is important, and don’t forget to use the quoted &lt;code&gt;"${@}"&lt;/code&gt; style, because our commit message is bound to have spaces in too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# a negative value needs either a preceding space or parentheses
"${@: -2}"

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

&lt;/div&gt;



&lt;p&gt;Wrapping this all up, I ended up with this, which is slightly more complicated by the need to escape the quoting of the arguments to the git alias. First we &lt;code&gt;git add&lt;/code&gt; all the filenames, then we &lt;code&gt;git commit&lt;/code&gt; using our &lt;code&gt;-m&lt;/code&gt; message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alias]
  add-and-commit = "!git add \"${@:1:$(($#-2))}\" &amp;amp;&amp;amp; git commit \"${@: -2}\" #"

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LOL, no ↩&lt;/p&gt;

</description>
      <category>git</category>
      <category>bash</category>
    </item>
    <item>
      <title>Tricks with less</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Tue, 02 Mar 2021 16:41:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/tricks-with-less-17be</link>
      <guid>https://forem.com/jaygooby/tricks-with-less-17be</guid>
      <description>&lt;p&gt;&lt;a href="https://linux.die.net/man/1/less"&gt;&lt;code&gt;less&lt;/code&gt;&lt;/a&gt; is one of those utilities I use day in and day out, and it's easy to forget that it has some &lt;a href="https://unix.stackexchange.com/questions/31/list-of-useful-less-functions"&gt;really powerful features&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case-insenstive searching
&lt;/h2&gt;

&lt;p&gt;Nine times out of ten, I &lt;code&gt;less&lt;/code&gt; a file and then start finding lines using the &lt;code&gt;/&lt;/code&gt; command. You can make this case-insentive if you enter &lt;code&gt;-I&lt;/code&gt; and hit return - you'll see a prompt saying &lt;code&gt;Ignore case in searches and in patterns  (press RETURN)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then when you search using &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;?&lt;/code&gt; you can be confident that you won't miss something due to case sensitivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering
&lt;/h2&gt;

&lt;p&gt;Even more useful is filtering; you can strip out all the lines you don't care about by typing &lt;code&gt;&amp;amp;&lt;/code&gt; - you'll get a &lt;code&gt;&amp;amp;/&lt;/code&gt; prompt where you can enter your filter text. Say you have an nginx access log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;107.174.242.123 - - [02/Mar/2021:05:15:07 -0800] "GET /2021/01/04/hello-2021 HTTP/1.1" 200 7073 "https://jay.gooby.org/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.18 Safari/537.36"
104.199.125.198 - - [02/Mar/2021:05:15:33 -0800] "HEAD / HTTP/2.0" 200 390 "https://t.co/Pp644lxwd6" "-"
64.225.34.92 - - [02/Mar/2021:05:16:16 -0800] "GET /feed.xml HTTP/1.1" 200 13370 "-" "Feedbin feed-id:2056147 - 2 subscribers"
40.77.167.97 - - [02/Mar/2021:05:24:34 -0800] "GET /robots.txt HTTP/1.1" 200 4785 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
64.225.34.92 - - [02/Mar/2021:05:26:56 -0800] "GET /feed.xml HTTP/1.1" 200 13370 "-" "Feedbin feed-id:2056147 - 2 subscribers"
13.66.139.48 - - [02/Mar/2021:05:29:23 -0800] "GET / HTTP/1.1" 200 7706 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can hide anything that isn't a HEAD request by typing &lt;code&gt;&amp;amp;&lt;/code&gt; and then at the &lt;code&gt;&amp;amp;/&lt;/code&gt; prompt enter &lt;code&gt;HEAD&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;104.199.125.198 - - [02/Mar/2021:05:15:33 -0800] "HEAD / HTTP/2.0" 200 390 "https://t.co/Pp644lxwd6" "-"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use &lt;code&gt;less +F&lt;/code&gt; as a replacement for &lt;code&gt;tail -f&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;How many times have you been tailing a file with &lt;code&gt;tail -f&lt;/code&gt; and then wanted to search for something, so you end up quitting the tail and then &lt;code&gt;less&lt;/code&gt;ing it instead? I used to do this all the time, until I discovered that &lt;code&gt;less&lt;/code&gt; has a tail mode. Use it like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;less +F /some/file&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can ctrl-c to exit the tailing mode and then use all the above tricks to filter and search, and then when you're done, press shift-f to go back to tail mode.&lt;/p&gt;

</description>
      <category>howto</category>
      <category>cli</category>
    </item>
    <item>
      <title>uknown-host</title>
      <dc:creator>Jay</dc:creator>
      <pubDate>Wed, 10 Feb 2021 10:03:00 +0000</pubDate>
      <link>https://forem.com/jaygooby/uknown-host-1ng6</link>
      <guid>https://forem.com/jaygooby/uknown-host-1ng6</guid>
      <description>&lt;p&gt;I updated a server cluster last night and was getting loads of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

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

&lt;/div&gt;



&lt;p&gt;warnings. You get told about the offending line number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offending RSA key in ~/.ssh/known_hosts:531

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

&lt;/div&gt;



&lt;p&gt;And usually I’d just edit it and be done, but after doing it a couple of times I wrote &lt;a href="https://gist.github.com/jaygooby/00b58afd85be372a3a4c451dd535f94e"&gt;unknown-host&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Use this to remove the offending line from your&lt;/span&gt;
&lt;span class="c"&gt;# ~/.ssh/known-hosts file when you know the host&lt;/span&gt;
&lt;span class="c"&gt;# has actually changed and you get the&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;/span&gt;
&lt;span class="c"&gt;# @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @&lt;/span&gt;
&lt;span class="c"&gt;# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Offending RSA key in ~/.ssh/known_hosts:531&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# warning telling you what line needs removing.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# usage: unknown-host &amp;lt;line number&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# So calling:&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# unknown-host 531&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# will remove line 531 and then you can ssh again with the nag.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# jay@gooby.org&lt;/span&gt;
&lt;span class="c"&gt;# @jaygooby&lt;/span&gt;

&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;

&lt;span class="c"&gt;# number detection via https://stackoverflow.com/a/3951175/391826&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;[!&lt;/span&gt;0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"Usage: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;line number&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;d"&lt;/span&gt; ~/.ssh/known_hosts
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so I wouldn’t have to do that again, in the great spirit of &lt;a href="https://xkcd.com/1319/"&gt;xkcd’s “Automation” webcomic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xkcd.com/1319/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4uFpNKSD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgs.xkcd.com/comics/automation.png" alt="xkcd webcomic about how writing a simple script to automate a task grows exponentially"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>ssh</category>
      <category>rsa</category>
    </item>
  </channel>
</rss>
