<?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>DEV Community: Brandon Rozek</title>
    <description>The latest articles on DEV Community by Brandon Rozek (@brandonrozek).</description>
    <link>https://dev.to/brandonrozek</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%2F1002666%2Ffd75884e-8c14-46fb-9e80-0ad7df30ff49.jpg</url>
      <title>DEV Community: Brandon Rozek</title>
      <link>https://dev.to/brandonrozek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brandonrozek"/>
    <language>en</language>
    <item>
      <title>Deploying a Lightweight Git Server with CGit using Docker-Compose</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Sat, 21 Jan 2023 02:14:03 +0000</pubDate>
      <link>https://dev.to/brandonrozek/deploying-a-lightweight-git-server-with-cgit-using-docker-compose-3nnp</link>
      <guid>https://dev.to/brandonrozek/deploying-a-lightweight-git-server-with-cgit-using-docker-compose-3nnp</guid>
      <description>&lt;p&gt;In this post, I’ll talk about how we can setup CGit within a docker-compose setup. We’ll be using the &lt;a href="https://hub.docker.com/r/clearlinux/cgit" rel="noopener noreferrer"&gt;ClearLinux CGit&lt;/a&gt; container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Apache Webserver
&lt;/h2&gt;

&lt;p&gt;Within the CGit container, an apache webserver is setup to execute the CGit CGI scripts. This configuration is very similar to the &lt;a href="https://github.com/clearlinux/dockerfiles/blob/256680f7c6be8423081e67153de0bff1206f6b63/cgit/httpd-cgit.conf" rel="noopener noreferrer"&gt;default one&lt;/a&gt; provided by ClearLinux. However, the default holds your repositories in the &lt;code&gt;/cgit&lt;/code&gt; subfolder as I wanted it on the root &lt;code&gt;/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;/etc/httpd-cgit.conf&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ServerName localhost

# Next two lines changed for new document root
DocumentRoot /var/www/cgit
&amp;lt;Directory "/var/www/cgit"&amp;gt;
    AllowOverride None
    Options ExecCGI FollowSymLinks
    Require all granted
&amp;lt;/Directory&amp;gt;

# cgid module is required to run the cgit binary
LoadModule cgid_module lib/httpd/modules/mod_cgid.so
&amp;lt;IfModule cgid_module&amp;gt;
        ScriptSock /var/run/cgid.sock
&amp;lt;/IfModule&amp;gt;

# Path to cgit stylesheet, graphics
Alias /cgit-data /usr/share/cgit
&amp;lt;Directory "/usr/share/cgit"&amp;gt;
    AllowOverride None
    Options None
    Require all granted
&amp;lt;/Directory&amp;gt;

# Path to cgit binary
# Next line changed
ScriptAlias / /usr/libexec/cgit/cgi-bin/cgit/
&amp;lt;Directory "/usr/libexec/cgit/cgi-bin"&amp;gt;
    AllowOverride None
    Options None
    Require all granted
&amp;lt;/Directory&amp;gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring CGit
&lt;/h2&gt;

&lt;p&gt;Now to configure &lt;code&gt;cgit&lt;/code&gt; itself, we need to create a file called &lt;code&gt;cgitrc&lt;/code&gt;. Order matters in the declarations, and from what I can gather you should have your &lt;code&gt;scan-path&lt;/code&gt; near the end of the file.&lt;/p&gt;

&lt;p&gt;To enable cloning and have it discoverable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enable-http-clone=1
clone-prefix=https://URL/TO/WEBSITE

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

&lt;/div&gt;



&lt;p&gt;To download snapsots of the references&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;snapshots=tar.gz zip

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

&lt;/div&gt;



&lt;p&gt;To enable the git config to override the owner and description fields&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enable-git-config=1

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

&lt;/div&gt;



&lt;p&gt;Cache up to 1000 output entries&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cache-size=1000

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

&lt;/div&gt;



&lt;p&gt;Root Page configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root-title=Brandon Rozek's Repositories
root-desc=
repository-sort=age

# Start all URLs from the root directory
virtual-root=/

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

&lt;/div&gt;



&lt;p&gt;Server the appropriate mime-types of certain files&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mimetype.gif=image/gif
mimetype.html=text/html
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.pdf=application/pdf
mimetype.png=image/png
mimetype.svg=image/svg+xml

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

&lt;/div&gt;



&lt;p&gt;Styles for the website&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;css=/cgit-data/cgit.css
logo=/cgit-data/cgit.png
favicon=/cgit-data/favicon.ico
source-filter=/usr/libexec/cgit/filters/syntax-highlighting.sh
about-filter=/usr/libexec/cgit/filters/about-formatting.sh
readme=:README.md
readme=:README

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

&lt;/div&gt;



&lt;p&gt;Where to find the repositories&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scan-path=/var/www/cgit/

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up the container
&lt;/h2&gt;

&lt;p&gt;I prefer using &lt;code&gt;docker-compose&lt;/code&gt; to help manage all my containers. The first two volumes map the configuration files we created and the last volume holds our repositories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cgit:
  image: docker.io/clearlinux/cgit
  container_name: cgit
  hostname: cgit
  volumes:
    - /etc/cgitrc:/etc/cgitrc
    - /etc/httpd-cgit.conf:/etc/httpd/conf.d/httpd-cgit.conf
    - /var/www/cgit:/var/www/cgit
  restart: always

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Populating it with Repositories
&lt;/h2&gt;

&lt;p&gt;Within &lt;code&gt;/var/www/cgit&lt;/code&gt;, start cloning your repositories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone --bare REPO_URL

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

&lt;/div&gt;



&lt;p&gt;If you enabled gitinfo then for each repository you can run &lt;code&gt;git config -e&lt;/code&gt; and add the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[gitweb]
    owner = Name &amp;lt;Email&amp;gt;
    description = Insert your project description here

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Aside: Reverse Proxy
&lt;/h3&gt;

&lt;p&gt;In my setup, I have an &lt;code&gt;nginx&lt;/code&gt; container that handles all of the traffic. Therefore, I don’t have users enter to the cgit container directly. I handle this by adding the following reverse proxy configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 80;
    listen [::]:80;
    server_name GIT.SERVER.URL

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name GIT.SERVER.URL;

    ssl_certificate /path/to/chain;
    ssl_certificate_key /path/to/private/key;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://cgit;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $server_name;
        # Needed to get around HTTP2 Streaming Errors
        proxy_hide_header Upgrade;
    }
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After all the configuration, you should be able to pull it up using &lt;code&gt;docker-compose&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;docker-compose up cgit

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;These references talked about setting up cgit outside of docker, but they helped me understand the various configuration files needed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://russellhaering.com/running-cgit-under-nginx/" rel="noopener noreferrer"&gt;https://russellhaering.com/running-cgit-under-nginx/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jakesthoughts.xyz/blog/setting-up-cgit.html" rel="noopener noreferrer"&gt;https://jakesthoughts.xyz/blog/setting-up-cgit.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.yaroslavps.com/weblog/minimal-git-server/" rel="noopener noreferrer"&gt;https://www.yaroslavps.com/weblog/minimal-git-server/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.stefan-koch.name/2020/02/16/installing-cgit-nginx-on-debian" rel="noopener noreferrer"&gt;https://blog.stefan-koch.name/2020/02/16/installing-cgit-nginx-on-debian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bryanbrattlof.com/cgit-nginx-gitolite-a-personal-git-server/" rel="noopener noreferrer"&gt;https://bryanbrattlof.com/cgit-nginx-gitolite-a-personal-git-server/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://matejamaric.com/blog/git-server/" rel="noopener noreferrer"&gt;https://matejamaric.com/blog/git-server/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
    </item>
    <item>
      <title>Bmaptool: A simpler way to copy ISOs</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Wed, 18 Jan 2023 16:08:20 +0000</pubDate>
      <link>https://dev.to/brandonrozek/bmaptool-a-simpler-way-to-copy-isos-155l</link>
      <guid>https://dev.to/brandonrozek/bmaptool-a-simpler-way-to-copy-isos-155l</guid>
      <description>&lt;p&gt;Bmaptool is a project created by Intel for creating and copying data using block maps. It’s meant to be a simpler, faster, and more reliable tool than &lt;code&gt;dd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From their &lt;a href="https://github.com/intel/bmap-tools" rel="noopener noreferrer"&gt;GitHub page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Faster. Depending on various factors, like write speed, image size, how full is the image, and so on, &lt;code&gt;bmaptool&lt;/code&gt; was 5-7 times faster than &lt;code&gt;dd&lt;/code&gt; in the Tizen IVI project.&lt;/li&gt;
&lt;li&gt;Integrity. &lt;code&gt;bmaptool&lt;/code&gt; verifies data integrity while flashing, which means that possible data corruptions will be noticed immediately.&lt;/li&gt;
&lt;li&gt;Usability. &lt;code&gt;bmaptool&lt;/code&gt; can read images directly from the remote server, so users do not have to download images and save them locally.&lt;/li&gt;
&lt;li&gt;Protects user’s data. Unlike &lt;code&gt;dd&lt;/code&gt;, if you make a mistake and specify a wrong block device name, &lt;code&gt;bmaptool&lt;/code&gt; will less likely destroy your data because it has protection mechanisms which, for example, prevent &lt;code&gt;bmaptool&lt;/code&gt; from writing to a mounted block device.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;It comes with two commands &lt;strong&gt;create&lt;/strong&gt; and &lt;strong&gt;copy&lt;/strong&gt;. Create generates the block maps which isn’t required to use the application. However, having a bmap will speed up the copying process. The syntax of the copy command is the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo bmaptool copy SRC DST

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

&lt;/div&gt;



&lt;p&gt;Let’s say that I want to flash the latest Ubuntu ISO to a USB stick located at &lt;code&gt;/dev/sdX&lt;/code&gt;. As the third bullet point claims, I can easily use the URL as SRC and since we don’t have a bmap, we’ll have to specify that with the flag &lt;code&gt;--nobmap&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 bmaptool copy --nobmap https://releases.ubuntu.com/22.04.1/ubuntu-22.04.1-desktop-amd64.iso /dev/sdX

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

&lt;/div&gt;



&lt;p&gt;Example run on my desktop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bmaptool: info: no bmap given, copy entire image to '/dev/sdX'
/
bmaptool: info: synchronizing '/dev/sdX'
bmaptool: info: copying time: 3m 7.3s, copying speed 19.5 MiB/sec

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

&lt;/div&gt;



&lt;p&gt;Now if we have the ISO downloaded on our computer, we can take the time to create a bmap for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo bmaptool create ubuntu-22.04.1-desktop-amd64.iso &amp;gt; ubuntu-22.04.1-desktop-amd64.bmap

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

&lt;/div&gt;



&lt;p&gt;A bmap file is a human readable XML file that shows the block map and the checksums for each block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" ?&amp;gt;
&amp;lt;!-- This file contains the block map for an image file, which is basically
     a list of useful (mapped) block numbers in the image file. In other words,
     it lists only those blocks which contain data (boot sector, partition
     table, file-system metadata, files, directories, extents, etc). These
     blocks have to be copied to the target device. The other blocks do not
     contain any useful data and do not have to be copied to the target
     device.

     The block map an optimization which allows to copy or flash the image to
     the image quicker than copying of flashing the entire image. This is
     because with bmap less data is copied: &amp;lt;MappedBlocksCount&amp;gt; blocks instead
     of &amp;lt;BlocksCount&amp;gt; blocks.

     Besides the machine-readable data, this file contains useful commentaries
     which contain human-readable information like image size, percentage of
     mapped data, etc.

     The 'version' attribute is the block map file format version in the
     'major.minor' format. The version major number is increased whenever an
     incompatible block map format change is made. The minor number changes
     in case of minor backward-compatible changes. --&amp;gt;

&amp;lt;bmap version="2.0"&amp;gt;
    &amp;lt;!-- Image size in bytes: 3.6 GiB --&amp;gt;
    &amp;lt;ImageSize&amp;gt; 3826831360 &amp;lt;/ImageSize&amp;gt;

    &amp;lt;!-- Size of a block in bytes --&amp;gt;
    &amp;lt;BlockSize&amp;gt; 4096 &amp;lt;/BlockSize&amp;gt;

    &amp;lt;!-- Count of blocks in the image file --&amp;gt;
    &amp;lt;BlocksCount&amp;gt; 934285 &amp;lt;/BlocksCount&amp;gt;

    &amp;lt;!-- Count of mapped blocks: 3.6 GiB or 100.0% --&amp;gt;
    &amp;lt;MappedBlocksCount&amp;gt; 934285 &amp;lt;/MappedBlocksCount&amp;gt;

    &amp;lt;!-- Type of checksum used in this file --&amp;gt;
    &amp;lt;ChecksumType&amp;gt; sha256 &amp;lt;/ChecksumType&amp;gt;

    &amp;lt;!-- The checksum of this bmap file. When it is calculated, the value of
         the checksum has be zero (all ASCII "0" symbols). --&amp;gt;
    &amp;lt;BmapFileChecksum&amp;gt; e69f56b4cf11a26fba8700bc66a443a20f667d0d0fe1c2d8028715ac060c402d &amp;lt;/BmapFileChecksum&amp;gt;

    &amp;lt;!-- The block map which consists of elements which may either be a
         range of blocks or a single block. The 'chksum' attribute
         (if present) is the checksum of this blocks range. --&amp;gt;
    &amp;lt;BlockMap&amp;gt;
        &amp;lt;Range chksum="c396e956a9f52c418397867d1ea5c0cf1a99a49dcf648b086d2fb762330cc88d"&amp;gt; 0-934284 &amp;lt;/Range&amp;gt;
    &amp;lt;/BlockMap&amp;gt;
&amp;lt;/bmap&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Once we have generated the bmap, we can copy the ISO to the device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo bmaptool copy ubuntu-22.04.1-desktop-amd64.iso /dev/sdX

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

&lt;/div&gt;



&lt;p&gt;Example run on my desktop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bmaptool: info: discovered bmap file 'ubuntu-22.04.1-desktop-amd64.bmap'
bmaptool: info: block map format version 2.0
bmaptool: info: 934285 blocks of size 4096 (3.6 GiB), mapped 934285 blocks (3.6 GiB or 100.0%)
bmaptool: info: copying image 'ubuntu-22.04.1-desktop-amd64.iso' to block device '/dev/sdX' using bmap file 'ubuntu-22.04.1-desktop-amd64.bmap'
bmaptool: info: 100% copied
bmaptool: info: synchronizing '/dev/sdX'
bmaptool: info: copying time: 2m 49.2s, copying speed 21.6 MiB/sec

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

&lt;/div&gt;



&lt;p&gt;Recall that the &lt;code&gt;bmap&lt;/code&gt; generation isn’t necessary, as you can pass in the &lt;code&gt;--nobmap&lt;/code&gt; flag.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudcomputing</category>
      <category>certification</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Decentralized PGP Keys with WKD</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Wed, 04 Jan 2023 14:06:38 +0000</pubDate>
      <link>https://dev.to/brandonrozek/decentralized-pgp-keys-with-wkd-206h</link>
      <guid>https://dev.to/brandonrozek/decentralized-pgp-keys-with-wkd-206h</guid>
      <description>&lt;p&gt;After creating a PGP key, it is common to distribute it to various keyservers. However, anyone can upload to these keyservers impersonating someone else. One solution is to use a decentralized identities approach, however, if your email is on your own domain that you tell every soul about, why not have your own website host the key? This is where the Web Key Directory (WKD) protocol comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up WKD
&lt;/h2&gt;

&lt;p&gt;To start we need to create a new folder on our webserver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir .well-known/openpgpkey/hu

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

&lt;/div&gt;



&lt;p&gt;In it, add an empty policy file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch .well-known/openpgpkey/hu/policy

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

&lt;/div&gt;



&lt;p&gt;Now we need to add our key to the folder. The key needs to be stored in the file named after the email’s WKD hash. We can get this hash through the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --with-wkd-hash --fingerprint brozek@brandonrozek.com

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

&lt;/div&gt;



&lt;p&gt;Replacing my email with yours. At the current moment, this returns the following for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub ed25519 2022-12-14 [SC] [expires: 2023-12-14]
      5F37 830B FA46 FF78 81F4 7AC7 8DF7 9C3D C5FC 658A
uid [ultimate] Brandon Rozek &amp;lt;brozek@brandonrozek.com&amp;gt;
              o1dbwkdx683fduwgzmrbwa3yip41frdn@brandonrozek.com
uid [ultimate] Brandon Rozek &amp;lt;hello@brandonrozek.com&amp;gt;
              im4cc8qhazwkfsi65a8us1bc5gzk1o4p@brandonrozek.com

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

&lt;/div&gt;



&lt;p&gt;The string starting with&lt;code&gt;o1dbwk&lt;/code&gt; is the WKD hash for &lt;code&gt;brozek@brandonrozek.com&lt;/code&gt; and the string starting with &lt;code&gt;im4cc8qh&lt;/code&gt; is the WKD hash for &lt;code&gt;hello@brandonrozek.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s store that hash in &lt;code&gt;$WKD&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;export WKD="o1dbwkdx683fduwgzmrbwa3yip41frdn"

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

&lt;/div&gt;



&lt;p&gt;The WKD specification says to upload the non-armored (binary) version of our key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --no-armor --export brozek@brandonrozek.com &amp;gt; .well-known/openpgpkey/hu/$WKD

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

&lt;/div&gt;



&lt;p&gt;After uploading it to our webserver, it needs to be configured with the right content type and access control headers.&lt;/p&gt;

&lt;p&gt;In Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location /.well-known/openpgpkey {
    default_type application/octet-stream;
    add_header Access-Control-Allow-Origin "*";
}

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

&lt;/div&gt;



&lt;p&gt;Now we can check our setup using the website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://metacode.biz/openpgp/web-key-directory"&gt;https://metacode.biz/openpgp/web-key-directory&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using WKD
&lt;/h2&gt;

&lt;p&gt;Many applications currently support WKD, though I’ll show how we can use &lt;code&gt;gpg&lt;/code&gt; to search for someone’s key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --auto-key-locate wkd --locate-key brozek@brandonrozek.com

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

&lt;/div&gt;



&lt;p&gt;This will not only locate but import the key into our keystore.&lt;/p&gt;

&lt;p&gt;With WKD, we didn’t have to trust anyone but the DNS provider in order to retrieve the key. The biggest downside with this approach, however, is that most people do not have an email on their own domain. Since nowadays, many people use gmail as their primary provider, they will have to fallback to using a different approach for distributing their keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://shibumi.dev/posts/how-to-setup-your-own-wkd-server/"&gt;https://shibumi.dev/posts/how-to-setup-your-own-wkd-server/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sindastra.de/p/1905/how-to-set-up-pgp-wkd-web-key-directory"&gt;https://www.sindastra.de/p/1905/how-to-set-up-pgp-wkd-web-key-directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shivering-isles.com/Lets-discover-OpenPGP-keys"&gt;https://shivering-isles.com/Lets-discover-OpenPGP-keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.gnupg.org/WKD"&gt;https://wiki.gnupg.org/WKD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gpgpgp</category>
    </item>
    <item>
      <title>Decentralized Identities with PGP Annotations and Keyoxide</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Wed, 04 Jan 2023 14:00:14 +0000</pubDate>
      <link>https://dev.to/brandonrozek/decentralized-identities-with-pgp-annotations-and-keyoxide-f9j</link>
      <guid>https://dev.to/brandonrozek/decentralized-identities-with-pgp-annotations-and-keyoxide-f9j</guid>
      <description>&lt;p&gt;Under asymmetric encryption, for you to send me a message that only I can read you would need to encrypt the message with my public key. I then would have a corresponding private key that can decrypt the message. Public keys are then usually stored onto keyservers for others to query. When querying for a key, how do you know that the public key actually belongs to me? It turns out, you can’t since anyone can upload a key pretending to be me.&lt;/p&gt;

&lt;p&gt;What’s the solution? When PGP first came around, it was built around the &lt;a href="https://en.wikipedia.org/wiki/Web_of_trust" rel="noopener noreferrer"&gt;Web of Trust&lt;/a&gt; concept. The idea is that people would go to key-signing parties and verify in person that they are who they say they are. From a graph would be built out showing who verified who. Sadly this idea didn’t take off. A very small segment of the population attends key signing parties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keybase
&lt;/h2&gt;

&lt;p&gt;In 2014, &lt;a href="https://keybase.io/" rel="noopener noreferrer"&gt;Keybase&lt;/a&gt; was created to help solve this issue. The concept behind it is that we have other identities in the web, and by associating a keybase profiles to these identities others can have a strong confidence on who they are speaking to.&lt;/p&gt;

&lt;p&gt;For example, I own the website brandonrozek.com and am known as &lt;a href="https://fosstodon.org/@brozek" rel="noopener noreferrer"&gt;@brozek@fosstodon.org&lt;/a&gt; on Mastodon. On those platforms, I would create a post using the private key on keybase which claims that I own the user profile on keybase. Similarly on keybase I would link to my website and mastodon profile to say that I claim those.&lt;/p&gt;

&lt;p&gt;For a while, this was working great. Then in 2019 the following article comes out of their blog:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keybase + Stellar is live for everyone!&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://keybase.io/blog/keybase-stellar-launch" rel="noopener noreferrer"&gt;https://keybase.io/blog/keybase-stellar-launch&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The promotion of cryptocurrency makes you wonder how they are doing financially. Then in 2020, we see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keybase joins Zoom&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://keybase.io/blog/keybase-joins-zoom" rel="noopener noreferrer"&gt;https://keybase.io/blog/keybase-joins-zoom&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Upon further reflection, several questions arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why am I depending on Keybase to show which users I’m connected to? Ideally, this should be decentralized.&lt;/li&gt;
&lt;li&gt;Keybase holds access to the private key. While this makes the user experience easier since I don’t need to worry about those details. We should be encouraging people to hold their private keys instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What’s a great alternative to Keybase then? This is where &lt;a href="https://keyoxide.org/" rel="noopener noreferrer"&gt;Keyoxide&lt;/a&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyoxide &amp;amp; PGP Notations
&lt;/h2&gt;

&lt;p&gt;Yarmo Mackenbach wanted to create a project that’s decentralized in nature. This means that Keyoxide doesn’t hold the keys. Instead it depends on either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Key Directory (WKD) protocol where the keys are stored on your own server belonging to the domain.&lt;/li&gt;
&lt;li&gt;HTTP Keyserver Protocol (HKP) where Keyoxide queries keys.openpgp.org&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within the key you upload, you can add a PGP notation. This allows us to provide additional text on what accounts we own.&lt;/p&gt;

&lt;p&gt;For example the notation of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof@ariadne.id=dns:brandonrozek.com?type=TXT

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

&lt;/div&gt;



&lt;p&gt;claims that I own the domain &lt;code&gt;brandonrozek.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To provide the necessary backlink, the &lt;a href="https://docs.keyoxide.org/service-providers/dns/" rel="noopener noreferrer"&gt;Keyoxide documentation&lt;/a&gt; says to create a TXT record with my PGP fingerprint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openpgp4fpr:5F37830BFA46FF7881F47AC78DF79C3DC5FC658A

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

&lt;/div&gt;



&lt;p&gt;Notice how nowhere in the process do we reference Keyoxide or their servers. This only depends upon the keys that I upload onto the Internet and the appropriate backlinks. Keyoxide in this case, only serves as a validator, making sure that the links exist.&lt;/p&gt;

&lt;p&gt;My Keyoxide profile: &lt;a href="https://keyoxide.org/wkd/brozek%40brandonrozek.com" rel="noopener noreferrer"&gt;https://keyoxide.org/wkd/brozek%40brandonrozek.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In fact, Keyoxide is &lt;a href="https://codeberg.org/keyoxide/" rel="noopener noreferrer"&gt;open source&lt;/a&gt; meaning that anyone can host their own instance to perform the validation checks.&lt;/p&gt;

</description>
      <category>gpgpgp</category>
    </item>
    <item>
      <title>Obtaining Multiple Solutions Z3</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Sat, 31 Dec 2022 14:52:00 +0000</pubDate>
      <link>https://dev.to/brandonrozek/obtaining-multiple-solutions-z3-52cb</link>
      <guid>https://dev.to/brandonrozek/obtaining-multiple-solutions-z3-52cb</guid>
      <description>&lt;p&gt;Playing around with Diophantine solvers, I wanted to obtain the solutions of the following equation: $$ 5a + 4b - 3c = 0 $$ Let’s encode that using Z3&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from z3 import *

# Encode Equation
a, b, c = Ints("a, b, c")
s = Solver()
s.add(5 * a + 4 * b - 3 * c == 0)

# Find solution
result = s.model()
if result == sat:
    print(result)

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

&lt;/div&gt;



&lt;p&gt;This code snippet returns&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[a = 0, b = 0, c = 0]

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

&lt;/div&gt;



&lt;p&gt;Now there are multiple solutions to this Diophantine equation, so how do we get the others? It turns out after searching around StackOverflow (see references) the only way is to add the previous solutions as constraints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# This encodes the last solution as a constraint
block = []
for var in m:
    block.append(var() != m[var])
s.add(Or(block))

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

&lt;/div&gt;



&lt;p&gt;Formulaically, this corresponds to: $$ a \ne 0 \vee b \ne 0 \vee c \ne 0 $$ If you look at the references, it’s hard to encode these constraints generally. This is because Z3 is a powerful SMT solver working with many different theories. Though if we restrict ourselves to the Diophantine equations, we can write a function that acts as a generator for all of the solutions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import z3

def get_solutions(s: z3.z3.Solver):
    result = s.check()
    # While we still get solutions
    while (result == z3.sat):
      m = s.model()
      yield m
      # Add new solution as a constraint
      block = []
      for var in m:
          block.append(var() != m[var])
      s.add(z3.Or(block))
      # Look for new solution
      result = s.check()

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

&lt;/div&gt;



&lt;p&gt;Now for our example, this allows us to do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from z3 import *

a, b, c = Ints("a, b, c")
s = Solver()
s.add(5 * a + 4 * b - 3 * c == 0)

solutions = get_solutions(s)
upper_bound = 10
for solution, _ in zip(solutions, range(upper_bound)):
    print(solution)

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

&lt;/div&gt;



&lt;p&gt;The solutions of a linear Diophantine equation can be easily parameterized so I don’t recommend using Z3 in this way. Though I think this exercise is informative for other theories you might be trying to satisfy.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/11867611/z3py-checking-all-solutions-for-equation/70656700#70656700" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/11867611/z3py-checking-all-solutions-for-equation/70656700#70656700&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/11867611/z3py-checking-all-solutions-for-equation/70656700#70656700" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/11867611/z3py-checking-all-solutions-for-equation/70656700#70656700&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/63231398/trying-to-find-all-solutions-to-a-boolean-formula-using-z3-in-python/63232953#63232953" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/63231398/trying-to-find-all-solutions-to-a-boolean-formula-using-z3-in-python/63232953#63232953&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Mastodon/Webfinger Alias using HTTP Redirects</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Sat, 31 Dec 2022 14:50:00 +0000</pubDate>
      <link>https://dev.to/brandonrozek/mastodonwebfinger-alias-using-http-redirects-3fp5</link>
      <guid>https://dev.to/brandonrozek/mastodonwebfinger-alias-using-http-redirects-3fp5</guid>
      <description>&lt;p&gt;Mastodon uses the Webfinger protocol to find users. For example, if you search for &lt;code&gt;@brozek@brandonrozek.com&lt;/code&gt; it will access the url:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://brandonrozek.com/.well-known/webfinger?resource=acct:brozek@brandonrozek.com

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

&lt;/div&gt;



&lt;p&gt;As of the time of writing, I do not have a strong interest in running my own Mastodon server. I instead am active on &lt;code&gt;@brozek@fosstodon.org&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;https://fosstodon.org/.well-known/webfinger?resource=acct:brozek@fosstodon.org

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

&lt;/div&gt;



&lt;p&gt;If you try to access the last URL in your web browser, you’ll notice that it doesn’t return anything. This is because the server will only respond if we specify the content type. In &lt;code&gt;curl&lt;/code&gt;, we can specify that through the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'Accept: application/jrd+json' \
    https://fosstodon.org/.well-known/webfinger?resource=acct:brozek@fosstodon.org

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

&lt;/div&gt;



&lt;p&gt;Currently, it returns the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "subject": "acct:brozek@fosstodon.org",
  "aliases": [
    "https://fosstodon.org/@brozek",
    "https://fosstodon.org/users/brozek"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://fosstodon.org/@brozek"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://fosstodon.org/users/brozek"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://fosstodon.org/authorize_interaction?uri={uri}"
    }
  ]
}

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

&lt;/div&gt;



&lt;p&gt;Some people have been copy pasting the contents of this file, onto their webserver. However, this makes it so that we can’t have multiple aliases on this domain. Instead, we can use our webserver (mine is &lt;code&gt;nginx&lt;/code&gt;) to redirect the user when the specified handle is requested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location /.well-known/webfinger {
    default_type application/jrd+json;
    add_header Access-Control-Allow-Origin "*";
    if ($arg_resource = acct:brozek@brandonrozek.com) {
        return 301 $scheme://fosstodon.org/.well-known/webfinger?resource=acct:brozek@fosstodon.org;
    }
}

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

&lt;/div&gt;



&lt;p&gt;Now this isn’t a perfect alias, you can’t reference &lt;code&gt;@brozek@brandonrozek.com&lt;/code&gt; and have me be notified. Instead, this only works in user discovery.&lt;/p&gt;

</description>
      <category>mastodon</category>
    </item>
    <item>
      <title>Personal Web Archive and How I Archive Single Web pages</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Sat, 24 Dec 2022 14:01:16 +0000</pubDate>
      <link>https://dev.to/brandonrozek/personal-web-archive-and-how-i-archive-single-web-pages-8c5</link>
      <guid>https://dev.to/brandonrozek/personal-web-archive-and-how-i-archive-single-web-pages-8c5</guid>
      <description>&lt;p&gt;The &lt;a href="https://web.archive.org/"&gt;Internet Archive&lt;/a&gt; is great for providing a centralized database of snapshots of websites throughout time. What happens though when you want to have your own offline copy during times of little to no internet access? &lt;a href="https://archivebox.io/"&gt;Archivebox&lt;/a&gt; is one solution to such problem. It behaves similarly to the Internet archive and also allows importing of RSS feeds to save local copies of blog posts. To install it, you can use &lt;code&gt;pipx&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;pipx install archivebox

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

&lt;/div&gt;



&lt;p&gt;For the rest of this post, however, I want to talk about a simpler tool. A combination of &lt;code&gt;wget&lt;/code&gt; and &lt;code&gt;python -m http.server&lt;/code&gt;. In the past, I’ve used &lt;code&gt;wget&lt;/code&gt; to &lt;a href="https://dev.to/blog/archivingsites/"&gt;mirror entire websites&lt;/a&gt;. We can adjust the command slightly so that it doesn’t follow links and instead only looks at a single webpage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget --convert-links \
     --adjust-extension \
     --no-clobber \
     --page-requisites \
     INSERT_URL_HERE

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

&lt;/div&gt;



&lt;p&gt;Now assume we have a folder full of downloaded websites. To view them, we can use any HTTP server. One of the easiest to temporarily setup currently is Python’s built in one.&lt;/p&gt;

&lt;p&gt;To serve all the files in the current directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m http.server OPTIONAL_PORT_NUM

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

&lt;/div&gt;



&lt;p&gt;If you leave the port number field empty, then this returns&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

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

&lt;/div&gt;



&lt;p&gt;A few nice side-effects of using &lt;code&gt;wget&lt;/code&gt; and &lt;code&gt;python&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python’s default webserver shows a list of files in the directory. This can make it easier to browse around the web archive.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;wget&lt;/code&gt; flags make it so that if you want to archive &lt;code&gt;https://brandonrozek.com/blog/personal-simple-web-archive/&lt;/code&gt; then all you need to access is &lt;code&gt;http://localhost:8000/brandonrozek.com/blog/personal-simple-web-archive&lt;/code&gt;. In other words, it preserves URLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now this approach isn’t perfect, if a webpage makes heavy use of javascript or server side features then it’ll be incomplete. Though for the majority of the wiki pages or blog posts I want to save for future reference, this approach works well.&lt;/p&gt;

&lt;p&gt;My full script is below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh

set -o errexit
set -o nounset
set -o pipefail

ARCHIVE_FOLDER="$HOME/webarchive"

show_usage() {
    echo "Usage: archivesite [URL]"
    exit 1
}

# Check argument count
if ["$#" -ne 1]; then
    show_usage
fi

cd "$ARCHIVE_FOLDER"

wget --convert-links \
     --adjust-extension \
     --page-requisites \
     --no-verbose \
     "$1"

# Keep track of requested URLs
echo "$1" &amp;gt;&amp;gt; saved_urls.txt

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

&lt;/div&gt;



</description>
      <category>archive</category>
    </item>
    <item>
      <title>Concatenating PDF files in Linux</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Sun, 18 Dec 2022 23:38:44 +0000</pubDate>
      <link>https://dev.to/brandonrozek/concatenating-pdf-files-in-linux-473d</link>
      <guid>https://dev.to/brandonrozek/concatenating-pdf-files-in-linux-473d</guid>
      <description>&lt;p&gt;Every so often I need to combine several images into a single PDF. First, to convert an image to a PDF we can use &lt;code&gt;imagemagick&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;convert -quality 100 Image.png Scanned.pdf

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

&lt;/div&gt;



&lt;p&gt;To combine or concatenate multiple PDF files, we can use &lt;code&gt;ghostscript&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;gs -sDEVICE=pdfwrite \
   -sOUTPUTFILE=output.pdf \
   -dNOPAUSE \
   -dBATCH \
   input0.pdf input1.pdf input2.pdf

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

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-sDEVICE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device used for processing the output file type. Use &lt;code&gt;pdfwrite&lt;/code&gt; to write to a PDF file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-sOUTPUTFILE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to save the resulting file output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-dNOPAUSE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disables the prompting and pausing at the end of each page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-dBATCH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Finishes interpreting after processing the inputted files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Alternatively you can use &lt;code&gt;pdftk&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;pdftk input0.pdf input1.pdf input2.pdf \
    cat output output.pdf

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

&lt;/div&gt;



&lt;p&gt;Lastly, you can also use &lt;code&gt;imagemagick&lt;/code&gt;. Do note, however, that this program often leads to larger file sizes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;convert input0.pdf input1.pdf input2.pdf output.pdf

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Aside: Pixel Densities
&lt;/h2&gt;

&lt;p&gt;One issue I came across is that the pages were of different sizes. This is often because the pages can be of different pixel densities.&lt;/p&gt;

&lt;p&gt;To check run &lt;code&gt;pdfimages&lt;/code&gt; and look at the 3rd to last and 2nd to last columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pdfimages -list filename.pdf 


page num type width height color comp bpc enc interp object ID x-ppi y-ppi size ratio
--------------------------------------------------------------------------------------------
   1 0 image 613 77 rgb 3 8 jpeg no 8 0 1071 1076 9456B 6.7%
   1 1 image 2692 3496 rgb 3 8 jpeg no 9 0 329 329 418K 1.5%
   2 2 image 613 77 rgb 3 8 jpeg no 8 0 915 919 9456B 6.7%
   2 3 image 2300 3016 rgb 3 8 jpeg no 15 0 282 282 322K 1.6%
   3 4 image 613 77 rgb 3 8 jpeg no 8 0 937 942 9456B 6.7%
   3 5 image 2356 3024 rgb 3 8 jpeg no 21 0 288 288 150K 0.7%
   4 6 image 1686 2200 rgb 3 8 jpeg no 27 0 204 204 622K 5.7%
   5 7 image 5100 7016 rgb 3 8 jpeg no 33 0 600 600 1193K 1.1%
   6 8 image 613 77 rgb 3 8 jpeg no 8 0 1104 1110 9456B 6.7%
   6 9 image 2776 3720 rgb 3 8 jpeg no 38 0 339 339 231K 0.8%
   7 10 image 613 77 rgb 3 8 jpeg no 8 0 939 943 9456B 6.7%
   7 11 image 2360 3072 rgb 3 8 jpeg no 44 0 289 289 151K 0.7%

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

&lt;/div&gt;



&lt;p&gt;We can then use &lt;code&gt;imagemagick&lt;/code&gt; to enforce a certain pixel density. The tradeoff being that the file size might increase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;convert -density 300 input.pdf output.pdf

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

&lt;/div&gt;



&lt;p&gt;If you happen to know a different way to enforce a pixel density that doesn’t have a file size increase tradeoff. Please get in touch.&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>howto</category>
      <category>animation</category>
      <category>library</category>
    </item>
    <item>
      <title>Capturing Quoted Strings in Sed</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Sun, 18 Dec 2022 17:55:32 +0000</pubDate>
      <link>https://dev.to/brandonrozek/capturing-quoted-strings-in-sed-1fhj</link>
      <guid>https://dev.to/brandonrozek/capturing-quoted-strings-in-sed-1fhj</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer: This posts assumes some knowledge about regular expressions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently I was trying to capture an HTML attribute in &lt;code&gt;sed&lt;/code&gt;. For example, let’s say I want to extract the &lt;code&gt;href&lt;/code&gt; attribute in the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="https://brandonrozek.com" rel="me"&amp;gt;&amp;lt;/a&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Advice you commonly see on the Internet is to use a capture group for anything between the quotes of the href.&lt;/p&gt;

&lt;p&gt;In regular expression land, we can represent anything as &lt;code&gt;.*&lt;/code&gt; and define a capture group of some regular expression &lt;code&gt;X&lt;/code&gt; as &lt;code&gt;\(X\)&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;sed "s/.*href=\"\(.*\)\".*/\1/g"

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

&lt;/div&gt;



&lt;p&gt;What does this look like for our input?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo \&amp;lt;a href=\"https://brandonrozek.com\" rel=\"me\"\&amp;gt;\&amp;lt;/a\&amp;gt; |\
sed "s/.*href=\"\(.*\)\".*/\1/g"


https://brandonrozek.com" rel="me

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

&lt;/div&gt;



&lt;p&gt;It matches all the way until the second &lt;code&gt;"&lt;/code&gt;! What we want, is to not match &lt;em&gt;any&lt;/em&gt; character within the quotations, but match any character that is not the quotation itself &lt;code&gt;[^\"]*&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;sed "s/.*href=\"\([^\"]*\)\".*/\1/g"

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

&lt;/div&gt;



&lt;p&gt;This then works for our example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo \&amp;lt;a href=\"https://brandonrozek.com\" rel=\"me\"\&amp;gt;\&amp;lt;/a\&amp;gt; |\
sed "s/.*href=\"\([^\"]*\)\".*/\1/g"


https://brandonrozek.com

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

&lt;/div&gt;



&lt;p&gt;Within a bash script, we can make this a little more readable by using multiple variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUOTED_STR="\"\([^\"]*\)\""
BEFORE_TEXT=".*href=$QUOTED_STR.*"
AFTER_TEXT="\1"
REPLACE_EXPR="s/$BEFORE_TEXT/$AFTER_TEXT/g"

INPUT="\&amp;lt;a href=\"https://brandonrozek.com\" rel=\"me\"\&amp;gt;\&amp;lt;/a\&amp;gt;"

echo "$INPUT" | sed "$REPLACE_EXPR"

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

&lt;/div&gt;



</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Drawing Trees in LaTex with Tikz</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Tue, 06 Dec 2022 16:01:24 +0000</pubDate>
      <link>https://dev.to/brandonrozek/drawing-trees-in-latex-with-tikz-4mm4</link>
      <guid>https://dev.to/brandonrozek/drawing-trees-in-latex-with-tikz-4mm4</guid>
      <description>&lt;p&gt;For the longest time I’ve been avoiding Tikz because I imagined it being too difficult to learn. Usually I create a graphic using a program like &lt;a href="https://draw.io"&gt;Draw.IO&lt;/a&gt; and import it as an image. Though this time around, I decided that I’m going to learn how to make trees in Tikz. It turns out, it’s not as bad as I anticipated.&lt;/p&gt;

&lt;p&gt;I’m only going to provide a few simple examples in this post. To learn more check out the &lt;a href="https://tikz.dev/tikz-trees"&gt;Tikz documentation on trees&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Remember to have &lt;code&gt;\usepackage{tikz}&lt;/code&gt; in the preamble.&lt;/p&gt;

&lt;p&gt;To create a Tikz figure, you’ll need to create a &lt;code&gt;tikzpicture&lt;/code&gt; environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\begin{tikzpicture}
 % Tikz Code Here
\end{tikzpicture}

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

&lt;/div&gt;



&lt;p&gt;Every tree first begins with a root node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\begin{tikzpicture}
    \node {Root Node};
\end{tikzpicture}

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

&lt;/div&gt;



&lt;p&gt;The semicolon at the end denotes the end of a &lt;code&gt;tikz&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Now let’s make the root node have one child node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\begin{tikzpicture}
    \node {Root Node}
        child {node {Child Node}};
\end{tikzpicture}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3bFIbMeE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206111634.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3bFIbMeE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206111634.png" alt="Screenshot of tree with one node on each level" width="194" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the text of the nodes are within the &lt;code&gt;{}&lt;/code&gt; after the node command.&lt;/p&gt;

&lt;p&gt;To create another child for a node, place it in the same level as the existing child.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\begin{tikzpicture}
    \node {Root Node}
        child {node {Left Child}}
        child {node {Right Child}};
\end{tikzpicture}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ffo3FdNi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206111922.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ffo3FdNi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206111922.png" alt="Screenshot of tree with the root node having two child nodes" width="340" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rendered tree may have text overlap as shown in the last screenshot. This is where &lt;code&gt;tikz&lt;/code&gt; options come in. We can define the separation distance between siblings. I don’t have many tips for choosing the value other than to play around and see how it looks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\begin{tikzpicture}
[
    level 1/.style={sibling distance=25mm}
]
    \node {Root Node}
        child {node {Left Child}}
        child {node {Right Child}};
\end{tikzpicture}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KqsZ7SYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206112150.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KqsZ7SYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206112150.png" alt="Improved screenshot of previous tree but with well separated child nodes" width="435" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To show how the child nesting works, I’ll finish by giving the right child two children nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\begin{tikzpicture}
[
    level 1/.style={sibling distance=25mm},
    level 2/.style={sibling distance=15mm},
]
    \node {Root Node}
        child {node {Left Child}}
        child {
            node {Right Child}
            child {node {Child A}}
            child {node {Child B}}
        };
\end{tikzpicture}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8sVQn0PZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206112444.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8sVQn0PZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://brandonrozek.com/files/images/blog/20221206112444.png" alt="Screenshot of a tree similar to the previous tree, but with the right child having two child nodes one with the label A and the other with the label B." width="483" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>latex</category>
    </item>
    <item>
      <title>Deploying my Hugo Website through GitHub Actions</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Mon, 05 Dec 2022 03:02:08 +0000</pubDate>
      <link>https://dev.to/brandonrozek/deploying-my-hugo-website-through-github-actions-3fd1</link>
      <guid>https://dev.to/brandonrozek/deploying-my-hugo-website-through-github-actions-3fd1</guid>
      <description>&lt;p&gt;For the longest time I’ve held out on deploying my website through GitHub actions. My rationale at the time was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I have to execute &lt;code&gt;git push&lt;/code&gt;, I might as well run a &lt;code&gt;./sync&lt;/code&gt; script afterwards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What convinced me otherwise is automated commits. I currently have GitHub actions that sync my &lt;a href="https://dev.to/toots"&gt;Mastodon toots&lt;/a&gt; and &lt;a href="https://dev.to/observations"&gt;iNaturalist observations&lt;/a&gt;. As part of the sync process, a git commit is made. This commit should then trigger a site rebuild.&lt;/p&gt;

&lt;p&gt;How do we create a GitHub action that builds a Hugo website and deploys it via &lt;code&gt;rsync&lt;/code&gt;? The rest of this post will go over the components of the GitHub action that triggers when I update my website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggers
&lt;/h2&gt;

&lt;p&gt;I currently have three triggers for my deployment GitHub action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual (&lt;code&gt;workflow_dispatch&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Pushes to the &lt;code&gt;main&lt;/code&gt; branch&lt;/li&gt;
&lt;li&gt;Daily schedule via &lt;code&gt;cron&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on:
  workflow_dispatch:
  push:
    branches: main
  schedule:
    - cron: "21 11 * * *"

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;p&gt;I call my job &lt;code&gt;build_and_publish&lt;/code&gt; and have it run on top of the latest Ubuntu image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  build_and_publish:
    runs-on: ubuntu-latest

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Checkout the Repository
&lt;/h3&gt;

&lt;p&gt;Here we can rely on Github’s &lt;code&gt;checkout&lt;/code&gt; action to provide the latest version of the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps:
  - name: Checkout
    uses: actions/checkout@v3
    with:
      submodules: true
      fetch-depth: 0

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

&lt;/div&gt;



&lt;p&gt;Since my website relies on submodules, we need to make sure that its included in the checkout. The &lt;code&gt;fetch-depth&lt;/code&gt; flag denotes how many commits to retrieve. By default (&lt;code&gt;fetch-depth: 1&lt;/code&gt;) it only fetches the latest commit, however setting it to &lt;code&gt;0&lt;/code&gt; retrieves all commits. This is needed for Hugo’s last modified feature to work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Update the submodules
&lt;/h3&gt;

&lt;p&gt;Even though we checked out the whole repository with its associated submodules, they may be out of date. This step makes sure that we’re using the latest version of the submodule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Git submodule update
  run: |
    git pull --recurse-submodules
    git submodule update --remote --recursive    

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Setup Hugo
&lt;/h3&gt;

&lt;p&gt;Since Hugo is a static binary, we can pull it straight from their website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Setup Hugo
  env:
    HUGO_VERSION: 0.105.0
  run: |
    curl -L "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz" --output hugo.tar.gz
    tar -xvzf hugo.tar.gz
    sudo mv hugo /usr/local/bin    

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Build the website
&lt;/h3&gt;

&lt;p&gt;We can use a separate step to build the website. This along with the deployment are among the few places where this script can fail, so it’s nice to separate it out in case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Build Hugo Website
  run: hugo

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Install the SSH key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Install SSH Key
  run: |
    install -m 600 -D /dev/null ~/.ssh/id_rsa
    echo "${{ secrets.BUILD_SSH_KEY }}" &amp;gt; ~/.ssh/id_rsa
    echo "${{ secrets.HOST_KEY }}" &amp;gt; ~/.ssh/known_hosts
    echo "Host brandonrozek.com
        Hostname brandonrozek.com
        user build
        IdentityFile ~/.ssh/id_rsa" &amp;gt; ~/.ssh/config    

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

&lt;/div&gt;



&lt;p&gt;At this point in our script we need to handle &lt;a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets"&gt;secrets&lt;/a&gt;. The post I linked to will likely have the most up to date information, but as of this time of writing, you can add secrets by going to the &lt;code&gt;Settings&lt;/code&gt; tab of the repository. A secret is a key-value pair, therefore to access your secret in the GH action, you need to reference the key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;${{ secrets.YOUR_KEY_HERE }}

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

&lt;/div&gt;



&lt;p&gt;We need secrets for the SSH key used to deploy the website and the known hosts file so that I don’t have to do host verification. The first line ensures that the permissions of the SSH key is correct, and the last line makes it so that the &lt;code&gt;rsync&lt;/code&gt; command within my &lt;code&gt;sync.sh&lt;/code&gt; script is simpler. I use my &lt;code&gt;sync.sh&lt;/code&gt; not only in the next step of this action but on my own machine which has a different config associated with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Deploy
  run: ./deploy.sh

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

&lt;/div&gt;



&lt;p&gt;In my repository there is a &lt;code&gt;deploy.sh&lt;/code&gt; with the following contents&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env sh
rsync -Pazc --exclude=*.bak --delete public/ build@brandonrozek.com:brandonrozek/

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

&lt;/div&gt;



&lt;p&gt;This syncs everything within the &lt;code&gt;public&lt;/code&gt; build folder up to my webserver excluding files ended in &lt;code&gt;.bak&lt;/code&gt; and removing any files on the webserver that aren’t in the build folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Other than the &lt;code&gt;checkout&lt;/code&gt; action, each step does not depend on an external library to provide the functionality. I think it’s important to implement each of the steps ourselves, as opposed to relying on a &lt;code&gt;hugo&lt;/code&gt; GH action library or a &lt;code&gt;SFTP&lt;/code&gt; library. Not only does this safeguard us against supply side attacks, it also makes these actions more portable. I am not counting on GitHub to always allow the usage of their build infrastructure for free.&lt;/p&gt;

&lt;p&gt;GitHub action in its entirety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Build and Deploy Hugo Website

on:
  workflow_dispatch:
  push:
    branches: main
  schedule:
    - cron: "21 11 * * *"

jobs:
  build_and_publish:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          submodules: true
          fetch-depth: 0

      - name: Git submodule update
        run: |
          git pull --recurse-submodules
          git submodule update --remote --recursive          

      - name: Setup Hugo
        env:
          HUGO_VERSION: 0.105.0
        run: |
          curl -L "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz" --output hugo.tar.gz
          tar -xvzf hugo.tar.gz
          sudo mv hugo /usr/local/bin          

      - name: Build Hugo Website
        id: build
        run: |
                    hugo

      - name: Install SSH Key
        run: |
          install -m 600 -D /dev/null ~/.ssh/id_rsa
          echo "${{ secrets.BUILD_SSH_KEY }}" &amp;gt; ~/.ssh/id_rsa
          echo "${{ secrets.HOST_KEY }}" &amp;gt; ~/.ssh/known_hosts
          echo "Host brandonrozek.com
              Hostname brandonrozek.com
              user build
              IdentityFile ~/.ssh/id_rsa" &amp;gt; ~/.ssh/config          

      - name: Deploy
        run: ./deploy.sh

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

&lt;/div&gt;



</description>
      <category>hugo</category>
    </item>
    <item>
      <title>Pretty RSS Feeds</title>
      <dc:creator>Brandon Rozek</dc:creator>
      <pubDate>Mon, 05 Dec 2022 02:42:36 +0000</pubDate>
      <link>https://dev.to/brandonrozek/pretty-rss-feeds-jbp</link>
      <guid>https://dev.to/brandonrozek/pretty-rss-feeds-jbp</guid>
      <description>&lt;p&gt;Hi, I have a RSS feed. This allows you to subscribe to my words without me even knowing. How does it work? Well you need an RSS link, here’s mine:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://brandonrozek.com/blog/index.xml"&gt;https://brandonrozek.com/blog/index.xml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you need to input this URL into a &lt;em&gt;newsreader&lt;/em&gt; application (&lt;a href="https://feedly.com/"&gt;Feedly&lt;/a&gt;, &lt;a href="https://feedbin.com"&gt;Feedbin&lt;/a&gt;, &lt;a href="https://netnewswire.com/"&gt;NetNewsWire&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators"&gt;many others&lt;/a&gt;) which checks to see if there are any changes in the XML file and presents it in a visually nice way.&lt;/p&gt;

&lt;p&gt;In fact, many websites come with a RSS feed. You don’t even need to know the exact URL for it to work, if you type in &lt;code&gt;https://brandonrozek.com&lt;/code&gt; into your newsreader, you’ll get options for my various different feeds.&lt;/p&gt;

&lt;p&gt;How does that work? Well the website creator needs to include a tag in their website HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="alternate" 
      type="application/rss+xml" 
      href="https://brandonrozek.com/blog/index.xml" 
      title="Brandon Rozek's Blog" /&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Most RSS feeds don’t come styled. Therefore if you click on the RSS link you’ll see a bunch of XML formatted text. That can be confusing for people when they first visit, can we do better?&lt;/p&gt;

&lt;h2&gt;
  
  
  About Feeds RSS Style
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://interconnected.org/"&gt;Matt Webb&lt;/a&gt; created &lt;a href="https://aboutfeeds.com"&gt;https://aboutfeeds.com&lt;/a&gt; in order to provide a friendly overview into the world of RSS. As part of that he includes a style called &lt;a href="https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl"&gt;pretty-feed.xsl&lt;/a&gt;. The file comes with instructions built in, but it’s mostly as simple as adding a style tag in the beginning of the XML&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;?xml-stylesheet href="https://brandonrozek.com/PATH-TO-YOUR-STYLES/pretty-feed-v3.xsl" type="text/xsl"?&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;I hope more bloggers incorporate the style into their feed. I’m waiting for the day that the largest blogging platform Wordpress includes it by default.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
