<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://blog.kevinnlsamuel.com/techdiary/feed.xml" rel="self" type="application/atom+xml" /><link href="http://blog.kevinnlsamuel.com/techdiary/" rel="alternate" type="text/html" /><updated>2026-04-26T10:04:28+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/feed.xml</id><title type="html">tech diary</title><subtitle>(hopefully) daily journal of tech discoveries, training and the sort</subtitle><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><entry><title type="html">playing with lvmvdo</title><link href="http://blog.kevinnlsamuel.com/techdiary/2026/04/26/playing-with-vdo.html" rel="alternate" type="text/html" title="playing with lvmvdo" /><published>2026-04-26T00:00:00+00:00</published><updated>2026-04-26T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2026/04/26/playing-with-vdo</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2026/04/26/playing-with-vdo.html"><![CDATA[<p>long time no post. also this isn’t really a post. there won’t be any more posts.
this website will henceforth be a bunch of unedited “scribbles” for my own reference</p>

<hr />

<h1 id="what-is-vdo">what is vdo?</h1>
<p>virtual data optimizer. it adds deduplication support to logical volumes created using lvm.
well not vdo itself, but a sort of plugin for lvm that exploits the <code class="language-plaintext highlighter-rouge">dm_vdo</code> kernel module</p>

<h1 id="why-dedupe">why dedupe?</h1>
<p>i want to play around with <a href="https://www.libvirt.org">libvirt</a> and install a bunch of vms
but i don’t want to waste storage space</p>

<h1 id="will-this-work">will this work?</h1>
<p>honestly, no clue. i don’t think so. i think while lvmvdo is cool, libvirt doesn’t support
using predefined lvm logical volumes as libvirt storage volumes. i saw there was a way to
define an lvm volume group as a libvirt storage pool. and then libvirt is supposed to create
logical volumes from said libvirt pool/lvm volume group as needed</p>

<p>but for all this vdo to work, i need to define the volumes outside of libvirt. can i tell libvirt
to use a specifc lvm logical volume? actually yes. i don’t <strong>need</strong> to define the storage medium
as a libvirt volume hmmm. ok but that’s for another day</p>

<h1 id="how-does-lvmvdo-work">how does lvmvdo work?</h1>
<p>uses a “pool” a vdo pool to store the actual data, and the volume group that’s available as a block
device for writing a filesystem onto is a sort of thin volume. so applications see the filesystem on
a virtual block device. the virtual block device is writing data to the “pool” and the pool is deduping
and also compressing as it receives data</p>

<h1 id="how-do-i-want-to-use-it">how do i want to use it?</h1>
<p>i want a single vdo pool to be shared across multiple thin volumes</p>

<h2 id="why-would-i-want-that">why would i want that?</h2>
<p>vms would have similar data. duh</p>

<h1 id="how-do-i-do-that">how do i do that?</h1>
<p>at first i was looking at the docs on <code class="language-plaintext highlighter-rouge">lvmvdo(7)</code> but that was confusing. turns out the instructions on
<code class="language-plaintext highlighter-rouge">lvmthin(1)</code> are simpler</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>lvcreate <span class="nt">--type</span> thin-pool <span class="nt">-n</span> &lt;poolname&gt; <span class="nt">-L</span> &lt;size&gt; <span class="nt">--pooldatavdo</span> y &lt;vg&gt;
</code></pre></div></div>
<p>so this automatically create a vdo pool on &lt;vg&gt; a then a thin pool on the vdo pool</p>

<h2 id="another-thing">another thing</h2>
<p>the automatically created vdo pool is called <code class="language-plaintext highlighter-rouge">vpool</code>. so that ends up being confusing because now i see <code class="language-plaintext highlighter-rouge">vpool0_vpool</code>
in the output of <code class="language-plaintext highlighter-rouge">lsblk</code>. so just for my convenience</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>lvrename &lt;vg&gt;/vpool0 vdo
</code></pre></div></div>

<h2 id="creating-the-thin-volumes">creating the thin volumes</h2>
<p>well almost all everything is done, i still need to create thin volumes that can be used as block devices</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>lvcreate <span class="nt">--type</span> thin <span class="nt">--thin-pool</span> &lt;poolname&gt; <span class="o">[</span><span class="nt">-n</span> &lt;thinvolname&gt;] <span class="nt">-V</span> &lt;virtualsize&gt; &lt;vg&gt;
</code></pre></div></div>

<p>leaving out the <code class="language-plaintext highlighter-rouge">-n &lt;thinvolname&gt;</code> gives me a logical volume called <code class="language-plaintext highlighter-rouge">lvolN</code> where <code class="language-plaintext highlighter-rouge">N</code> is
the next number in the sequence of logical volumes. not bad, but not ideal. i should use the <code class="language-plaintext highlighter-rouge">-n</code> flag</p>

<h1 id="using-thin-volume-thats-on-a-thin-pool-thats-on-a-vdo-pool">using thin volume that’s on a thin pool that’s on a vdo pool</h1>
<p>create a filesystem</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>mkfs /dev/mapper/&lt;vg&gt;-&lt;thinvolname&gt;
</code></pre></div></div>

<p>and then do whatever. it’s not apparent to me/you nor the applications that there
are so many layers of abstraction under this virtual block device called &lt;thinvolname&gt;.</p>

<p>&lt;thinvolname&gt; has its blocks allocated dynamically from a thin pool. and the thin pool in turn
is sitting on a vdo pool that’s deduplicating and compressing the blocks written to it</p>

<p>max space savings hopefully. next step is figuring out how to actually use this with libvirt</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[trying out the virtual data optimizer in lvm]]></summary></entry><entry><title type="html">Terminal for Plebeians</title><link href="http://blog.kevinnlsamuel.com/techdiary/2023/09/18/cli-for-plebeian.html" rel="alternate" type="text/html" title="Terminal for Plebeians" /><published>2023-09-18T00:00:00+00:00</published><updated>2023-09-18T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2023/09/18/cli-for-plebeian</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2023/09/18/cli-for-plebeian.html"><![CDATA[<p>The terminal is oft considered an esoteric tool intended
for programmers and none else. The sight of a terminal is
immediately associated with coding.</p>

<blockquote>
  <p>This one time, a classmate asked me after class “What are
you always coding?”</p>

  <p>I was simply taking lecture notes in vim.</p>
</blockquote>

<p>Okay, most would immediately disqualify my being “plebeian”
on account of my using vim… doesn’t help that I was writing
in markdown.</p>

<p>But this post is more about the terminal being useful in fields
other than programming. In this particular case, a university
literature course.</p>

<h2 id="the-splitting-task">The splitting task</h2>

<p>The instructor was going to go over excerpts from course material.
Some of the material was really long pdf files. But we only
needed a few sections. How do we extract out only the parts we need?</p>

<p>We could <abbr title="sign an eternal contract in money">pay
Adobe</abbr> to let us extract pages. Or my sneaky trick
of opening the pdf file in a browser and printing to file
after typing in the range of pages I need. But repeating
that 12 times with different page ranges each time is
meh.</p>

<h2 id="the-cli-can-split-and-unite">The CLI can split and unite</h2>

<p>On the terminal, I have access to a tool called <code class="language-plaintext highlighter-rouge">pdfseparate</code>
that extracts pages from a pdf. But each page is saved to its
own file. Luckily, there’s also a <code class="language-plaintext highlighter-rouge">pdfunite</code> that does I could
use to combine these files.</p>

<p>But while that solves the question of tools to use, I do still
need to repeatedly specify the page numbers somewhat like</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pdfseparate <span class="nt">-f</span> 4 <span class="nt">-l</span> 5 <span class="s2">"filename.pdf"</span> <span class="s2">"filename-p%d.pdf"</span>
pdfunite <span class="s2">"filename-p4.pdf"</span> <span class="s2">"filename-p5.pdf"</span> <span class="s2">"filename.excerpt.pdf"</span>
</code></pre></div></div>

<p>That is two separate commands for just one file and each
filename and page count needs to be stated so expressly… how is this more
efficient?</p>

<p>It isn’t. But the command line offers so much opportunity
for efficiency through other features such as loops.</p>

<h2 id="loops">Loops</h2>

<p>I could simply change my command to go over all the pdf files and
run the commands for each one. By using a variable <code class="language-plaintext highlighter-rouge">${file}</code>, I
can let the terminal (technically shell) change the filename
for each command.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for </span>file <span class="k">in</span> <span class="k">*</span>.pdf<span class="p">;</span> <span class="k">do
    </span>pdfseparate <span class="nt">-f</span> XYZ <span class="nt">-l</span> XYZ <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">.pdf"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">-p%d.page"</span> <span class="p">;</span>
    pdfunite <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">-p*.page"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">.excerpt.pdf"</span> <span class="p">;</span>
<span class="k">done</span>
</code></pre></div></div>

<p>Additionally, I set the individual pages to be saved to <code class="language-plaintext highlighter-rouge">.page</code> files
instead of <code class="language-plaintext highlighter-rouge">.pdf</code> file. This lets me use the <code class="language-plaintext highlighter-rouge">${file}-p*.page</code> part
to indicate a pattern of filenames instead of specifying each file
that needs to be combined by <code class="language-plaintext highlighter-rouge">pdfunite</code>; and also won’t interfere
with my loop that is looking for the pattern <code class="language-plaintext highlighter-rouge">*.pdf</code>.</p>

<h3 id="wildcards--globs">Wildcards / Globs</h3>
<p>The <code class="language-plaintext highlighter-rouge">*</code> is called a glob character (or wildcard) to mean that it
can match anything. In this particular case, it is being used
to select all files whose names match a particular pattern.</p>

<h2 id="varying-page-numbers">Varying page numbers</h2>

<p>Yet persists the problem of the required page number ranges
being different for each pdf file. I could use variables again.
But for this one, a little bit of manual typing is needed. So
I created a spreadsheet and <del>pushed off</del> delegated to a
classmate, the work of keying in the first and last page
number for each filename.</p>

<p>Spreadsheets can be easily exported to CSV (comma-separated value)
files. Then, I used a different loop on the CSV file’s rows instead
of working on <code class="language-plaintext highlighter-rouge">*.pdf</code> as I did before.</p>

<pre><code class="language-csv">filename-093.pdf,5,45
fileplace-02.pdf,3,12
fileanima-98.pdf,9,11
filethgns-09.pdf,1,9
</code></pre>
<p>The above is a sample CSV file. Now I could get values for three variables
from each row of the file, the filename, first page and last page.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while </span><span class="nv">IFS</span><span class="o">=</span>, <span class="nb">read </span>file firstp lastp<span class="p">;</span> <span class="k">do
    </span>pdfseparate <span class="nt">-f</span> <span class="k">${</span><span class="nv">firstp</span><span class="k">}</span> <span class="nt">-l</span> <span class="k">${</span><span class="nv">lastp</span><span class="k">}</span> <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">.pdf"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">-p%d.page"</span> <span class="p">;</span>
    pdfunite <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">-p*.page"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">.excerpt.pdf"</span> <span class="p">;</span>
<span class="k">done</span>
</code></pre></div></div>
<p>You could also go further and combine all the excerpts into a single big file
using the handy <code class="language-plaintext highlighter-rouge">*.excerpt.pdf</code> pattern.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pdfunite <span class="s2">"*.exercpt.pdf"</span> <span class="s2">"allmaterial.pdf"</span>
</code></pre></div></div>

<h2 id="cli-for-mooore-than-just-code">CLI for mooore than just code</h2>

<p>And with that I conclude this badly written blog post. The CLI is useful in
situations other than coding—such as a university literature course.</p>

<p>Or to simple take notes in class if you use vim 😎</p>

<p><img src="/assets/post-img/2023/09/18-001-not_code.jpg" alt="that is just how my notepad looks" /></p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[Not just for nerds, the command line is a great tool for regular people too!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://blog.kevinnlsamuel.com/techdiary/assets/post-img/2023/09/18-000-cowsay_term_for_mooore.png" /><media:content medium="image" url="http://blog.kevinnlsamuel.com/techdiary/assets/post-img/2023/09/18-000-cowsay_term_for_mooore.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Padding Numbers In Filenames</title><link href="http://blog.kevinnlsamuel.com/techdiary/2023/04/09/padding-numbers-in-filenames.html" rel="alternate" type="text/html" title="Padding Numbers In Filenames" /><published>2023-04-09T00:00:00+00:00</published><updated>2023-04-09T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2023/04/09/padding-numbers-in-filenames</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2023/04/09/padding-numbers-in-filenames.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for </span>name <span class="k">in</span> <span class="k">*</span>.txt<span class="p">;</span> <span class="k">do
    </span><span class="nb">mv</span> <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
      <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="p">/(#b)(&lt;-&gt;)/</span><span class="k">${</span><span class="p">(l</span>:3::0:<span class="p">)match</span><span class="k">}}</span><span class="s2">"</span>
<span class="k">done</span>
</code></pre></div></div>

<h2 id="numbered-filenames-without-padding">Numbered filenames without padding</h2>

<p>Filenames with numbers in them are not uncommon. But sorting them always
has an unexpected conclusion.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>file1.txt
file100.txt
file2.txt
file200.txt
</code></pre></div></div>

<p>This is because sorting is an operation based on individual
character codes rather than words or numbers which are both
strings of characters (or digits).</p>

<h2 id="pad-it">Pad it</h2>

<p>The well‑known solution is to rename the files such that
the numbers are all of the same length. So if the numbers
go from 1 to 100, all the numbers have to be three digits
long</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>file001.txt
file002.txt
file100.txt
file200.txt
</code></pre></div></div>

<p>But that’s a slow process, so obviously one has to do it
faster</p>

<h2 id="pad-it-programmatically">Pad it programmatically</h2>

<blockquote>
  <p><strong>NB</strong> These are instructions for zsh and most likely will
not work in other shells</p>
</blockquote>

<p>Hence the expanded parameter</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">${</span><span class="nv">name</span><span class="p">/(#b)(&lt;-&gt;)/</span><span class="k">${</span><span class="nv">l</span>:3::0:<span class="p">)match</span><span class="k">}}</span>
</code></pre></div></div>
<p>that is broken down.</p>

<h3 id="substitution">substitution</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">${</span><span class="nv">name</span><span class="p">/pattern/replacement</span><span class="k">}</span>
</code></pre></div></div>
<p>Overall, it is a substituition. <code class="language-plaintext highlighter-rouge">${name}</code> is the parameter
(assuming <code class="language-plaintext highlighter-rouge">name</code> holds a filename). zsh will look for
the first occurence of <code class="language-plaintext highlighter-rouge">pattern</code>
in <code class="language-plaintext highlighter-rouge">name</code> and replace it with the value of <code class="language-plaintext highlighter-rouge">replacement</code>.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span><span class="s1">'John Doe'</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="p">/o/u</span><span class="k">}</span><span class="s2">"</span>
<span class="go">Juhn Doe
</span><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="p">/oe/onne</span><span class="k">}</span><span class="s2">"</span>
<span class="go">John Donne
</span></code></pre></div></div>

<h3 id="number-pattern">number pattern</h3>
<p>The search pattern <code class="language-plaintext highlighter-rouge">(#b)(&lt;-&gt;)</code> has two parts. Let’s first look at the
<code class="language-plaintext highlighter-rouge">&lt;-&gt;</code>.</p>

<p><code class="language-plaintext highlighter-rouge">&lt;n-m&gt;</code> is a glob pattern in zsh to indicate a range of numbers—
not digit characters. This is useful because the more common <code class="language-plaintext highlighter-rouge">[0-9]</code>
pattern only matches one digit. But numbers in filenames are longer.</p>

<p>The pattern <code class="language-plaintext highlighter-rouge">&lt;-&gt;</code> is shorthand to mean any number.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span><span class="s1">'May 1968'</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="p">/&lt;-&gt;/XXXX</span><span class="k">}</span><span class="s2">"</span>
<span class="go">May XXXX
</span></code></pre></div></div>

<p>Once the shell option <code class="language-plaintext highlighter-rouge">extendedglob</code> is enabled in zsh, the other part <code class="language-plaintext highlighter-rouge">(#b)</code>
enables back-referencing for all subsequent patterns
enclosed in parantheses. Back-referencing stores the matched pattern in a
variable called <code class="language-plaintext highlighter-rouge">match</code>.</p>

<p>Hence <code class="language-plaintext highlighter-rouge">(#b)(&lt;-&gt;)</code> finds a number and then stores it in a variable called
<code class="language-plaintext highlighter-rouge">match</code>.</p>

<h3 id="padded-replacement">padded replacement</h3>
<p>The <code class="language-plaintext highlighter-rouge">${(l:3::0:)match}</code> which is used as the replacement in the main expansion
is essentially <code class="language-plaintext highlighter-rouge">${match}</code>—the matched number from earlier—with a
qualifier enclosed in parentheses.</p>

<p>The <code class="language-plaintext highlighter-rouge">(l:3::0:)</code> qualifier to a parameter pads the paramater to 3 characters on the left
with the character 0.</p>

<p><code class="language-plaintext highlighter-rouge">(l:3:)</code> would truncate or pad a parameter as needed to make it three characters long.
By default, padding is done using blank spaces, but in <code class="language-plaintext highlighter-rouge">::0</code> we specify to use zeros.</p>

<h2 id="patternmatchpadreplace">pattern‑match‑pad‑replace</h2>

<p>Bringing it all together, we have
<code class="language-plaintext highlighter-rouge">${name/(#b)(&lt;-&gt;)/${(l:3::0:)name}}</code> in a for-loop</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[file1.txt file100.txt file2.txt &hellip; is hardly ordered and regular. So pattern&#8209;match&#8209;pad&#8209;replace.]]></summary></entry><entry><title type="html">Becoming My Own Certificate Authority</title><link href="http://blog.kevinnlsamuel.com/techdiary/2023/01/19/becoming-my-own-certificate-authority.html" rel="alternate" type="text/html" title="Becoming My Own Certificate Authority" /><published>2023-01-19T00:00:00+00:00</published><updated>2023-01-19T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2023/01/19/becoming-my-own-certificate-authority</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2023/01/19/becoming-my-own-certificate-authority.html"><![CDATA[<p>I have deployed quite a few websites and I know the importance of TLS certificates.
But I hardly ever have to do the work myself.</p>

<p>TLS certificates are handled by GitHub Pages, or Netlify where I usually host my static
sites. And for the few web services I selfhost on a cloud server, I use
<a href="https://github.com/nginx-proxy/nginx-proxy" target="\_blank">nginx-proxy</a>
with its companion service for
requesting and installing certificates from <a href="https://letsencrypt.org" target="\_blank">letsencrypt.org</a>.</p>

<p>I had a vague idea of how to generate self-signed certificates using
<a href="https://openssl.org" target="\_blank"><code class="language-plaintext highlighter-rouge">openssl</code></a>. But my understanding was so vague that I
had to refer to some guide online each time.</p>

<p>And the complexity of generating self-signed certificates is complemented by the fact that they are
not trusted by most clients due to the abscence of signatures from a known and trusted
<strong>Certificate Authority</strong>.</p>

<p>And so began my journey to become my own certificate authority.</p>

<h2 id="what-i-already-know">What I Already Know</h2>

<h3 id="http">HTTP</h3>
<p>The hyper-text transfer protocol is at the application layer of communications that make
up the world wide web – the web. It is built on top of
<abbr title="transmission control protocol">TCP</abbr> and <abbr title="internet protocol">IP</abbr></p>

<h3 id="https">HTTPS</h3>
<p>The secure version of HTTP uses transport layer security – TLS – to encrypt the data packets
that are being trasmitted. This ensures that nobody other than the client and server will understand
the messages being exchanged between themselves.</p>

<h3 id="tls">TLS</h3>
<p>A layer between TCP and HTTP for encrypting HTTP packets. It is deployed by installing <em>something</em> to
your webserver. I almost always use nginx, so to me that meant saving a certificate file, and a key file
and configuring nginx webservers to use them. This meant specifying the <code class="language-plaintext highlighter-rouge">ssl_certificate</code> and
<code class="language-plaintext highlighter-rouge">ssl_certificat_key</code> directives in the nginx configurations.</p>

<h3 id="generating-the-tls-certificate">Generating the TLS certificate</h3>
<p>I have had to keep referring back to online guides to get this done.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req -new -x509 \
    -newkey rsa:4096 \
    -nodes -sha256 \
    -days 365 \
    -keyout key.pem \
    -out cert.pem
</code></pre></div></div>

<p>And this gave me two files – a certificate and a key – that I could install on my server.</p>

<h3 id="trusting-on-the-client">Trusting on the client</h3>
<p>Open client, normally my browser, settings and import <code class="language-plaintext highlighter-rouge">cert.pem</code> from
the previous step. On my android phone, I install the certificate
system-wide.</p>

<h2 id="what-i-want-now">What I Want Now</h2>

<p>I wanted to improve my setup in the following ways</p>

<ul>
  <li>
    <p>use elliptic curve keys; that are supposedly faster and also more secure</p>
  </li>
  <li>
    <p>be a certificate authority; to avoid trusting new certificates each time</p>
  </li>
</ul>

<h2 id="getting-to-what-i-need">Getting To What I need</h2>

<h3 id="the-importance-of-the-key">The importance of the key</h3>
<p>My understanding thus far of most cryptographic operations had been vague.
But now, I finally realised the private key was the more basic element from
which the certificate was derived.</p>

<p>I had come to the realise that the private key is also the encryption key. In
retrospect, that is quite obvious! I just had not thought enough about what I
was doing.</p>

<p>The private key is used to encrypt data. But first, I sign a certificate with it.
Then I get it signed by an authority so my certificate is proven as trustworthy.</p>

<p>And there is usually a verification step to show that I do control the domain name
in the certificate.</p>

<h3 id="elliptic-curve-key">Elliptic Curve key</h3>
<p>Trudging through the documentation, I did not really fully understand, but I found
an example in the manpage <code class="language-plaintext highlighter-rouge">openssl-genpkey(1)</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl genpkey \
    -out privkey.pem \
    -algorithm EC \
    -pkeyopt ec_paramgen_curve:P-384 \
    -pkeyopt ec_param_enc:named_curve
</code></pre></div></div>

<p>Going a bit further, I added the <code class="language-plaintext highlighter-rouge">-aes256</code> flag to encrypt the key output.
The password was collected in the openssl prompts.</p>

<h3 id="becoming-the-certificate-authority">Becoming the certificate authority</h3>
<p>Turns out becoming a certificate authority is pretty simple. I just had to
create a self-signed certificate with they key I wanted to use. And then
use the same certificate to sign other certificates.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req -x509 \
    -key privkey.pem \
    -out CAcert.pem \
    -sha512 -days 365
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">openssl-req</code> is the subcommand to generate certificate requests (more on that later),
but combined with the <code class="language-plaintext highlighter-rouge">-x509</code> flag, it generates self-signed certificates. I do not
need to specify <code class="language-plaintext highlighter-rouge">-new</code> as I did before because it is implied when used with x509.</p>

<p>So now that I had a satisfactory certificate linked to an elliptic key, I
installed it on my devices.</p>

<h3 id="certificate-signing-request">Certificate signing request</h3>
<p>Until now, I had only made certificates that were signed by the key. But now
that I wanted to have it signed by a certificate authority, I had to generate
<abbr title="certificate signing requests">CSRs</abbr>. These are created
using the same command as self-signed certificates with the <code class="language-plaintext highlighter-rouge">-new</code> instead of
the <code class="language-plaintext highlighter-rouge">-x509</code> flag.</p>

<p>First I generate a new private key - using an elliptic curve! Then a CSR using
the key.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req -new \
    -key domainkey.pem \
    -out domain.csr \
    -sha512 -days 365
</code></pre></div></div>

<h3 id="authoritatively-signed-certificates">Authoritatively signed certificates</h3>
<p>Now to use the original key <code class="language-plaintext highlighter-rouge">privkey.pem</code> and its certificate
to sign the CSR and generate a new trusted certificate for the
domain.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl x509 -req \
    -out domaincert.pem \
    -in domain.csr \
    -CA CAcert.pem -CAkey privkey.pem \
    -sha512 -days 365
</code></pre></div></div>

<p>and done!</p>

<hr />

<p>I now have a domaincert.pem and a domainkey.pem that I can use to set up https on
my self-hosted webservers. And I also have a CAcert.pem that I can install on my
devices to trust the current and future domain certificates.</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[I dabble with signing TLS certificates for home web services]]></summary></entry><entry><title type="html">Exposing to the Internet using VPN</title><link href="http://blog.kevinnlsamuel.com/techdiary/2022/08/15/exposing-to-the-internet-using-vpn.html" rel="alternate" type="text/html" title="Exposing to the Internet using VPN" /><published>2022-08-15T00:00:00+00:00</published><updated>2022-08-15T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2022/08/15/exposing-to-the-internet-using-vpn</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2022/08/15/exposing-to-the-internet-using-vpn.html"><![CDATA[<p>Given all the false advertising around virtual private networks
(VPNs), it sounds counter-intuitive that the same thing would
be used to “expose” something to the internet.</p>

<p>But not all VPN advertising is fake, and some aspects of it
are rather useful—in ways that are not often portrayed by
VPN providers.</p>

<p><a href="#using-vpn-to-expose-the-home-network">Skip the ranting tangent</a> (Not recommended)</p>

<h3 id="what-is-a-vpn">What is a VPN?</h3>

<p>In essence, it is exactly what it says. A private network that
is virtual.</p>

<h4 id="what-is-a-network">What is a network?</h4>

<p>Connection between two more devices. In the context of the
internet, the connection is usually established using</p>
<ul>
  <li>copper cables</li>
  <li>radio waves</li>
  <li>fiber optic cables</li>
</ul>

<h4 id="a-virtual-nework">A virtual nework?</h4>

<p>In computing, virtual refers to hardware that does not
exist physically. It is existing hardware
but in software it appears as a device that is distinct
from what exists physically.</p>

<p>A now common example is virtualised CPUs. A physical CPU
that has sixteen cores can be presented in software as two CPUs
with eight cores each.</p>

<p>Likewise, virtual networks use the existing network hardware
but appear as a completely separate network in software.</p>

<h4 id="so-a-virutal-private-network">So a Virutal Private Network?</h4>

<p>To be fair, a lot of networks are private in that they cannot be
accessed by members who are not directly connected to the network.
Every home router of today sets up a LAN—local area network.
And only the devices connected directly to the router can access
resources on the network (printers, other computers, etc.)</p>

<p>Normally such networks are restricted and no other computer from the
internet is allowed access. While it is possible to open the network
to others on the internet, it poses the security risk of unauthorised
access.</p>

<p>Instead, by using virtualised networks one can allow access to
certain devices on a network while using the existing hardware of
the internet. Such networks, at least the modern implementations,
typically use more secure ways to communicate and authenticate
devices to one another.</p>

<h3 id="consumer-vpns">Consumer VPNs</h3>

<blockquote>
  <p>XXXXX VPN encrypts and routes your network through our secure
servers in N global locations…</p>
</blockquote>

<p>It’s exactly what it says. Instead of joining the virtual network
to access other devices on it, all connections to the internet
are made through only one device of the network.</p>

<p>And so,</p>
<ul>
  <li>the internet service provider sees a lot of requests
to XXXXX VPN’s server
    <ul>
      <li>but not the actual site that you visit</li>
    </ul>
  </li>
  <li>The site that you visit sees a lot of requests from the XXXXX
VPN server
    <ul>
      <li>but not from your actual device/IP address</li>
    </ul>
  </li>
</ul>

<p>This functionality is <strong>proxying</strong> – communication between
two devices via an intermediary called a proxy.</p>

<p>It could be sort of anonymous. But VPN services are obliged to
log and share logs with government authorities in some countries.
And if the site has a way for users to login (as do most
common ones today) the site knows who it is, just not your
actual location, unless it already had access to device location
and does not rely on IP address for locating in the first place.</p>

<p>The service offerred by majority of consumer VPN providers is
essentially a web proxy. While they offer partial
anonymitiy, it really depends on how it is being used.
It irks me that advertising today makes it look like an
all in one solution.</p>

<blockquote>
  <p>…allowing you to access geo-restricted content…</p>
</blockquote>

<p>This is possibly the only real use for most consummer VPN services.
And luckily, a lot more advertising is stressing on this more
than the “encryption” and “anonymity” that they offer. Hurray! 🎊</p>

<p>Perhaps this <a href="https://youtu.be/WVDQEoe6ZWY">Tom Scott video on VPN advertising claims</a>
from 2019 shifted the industry. Notably later on in 2022, Tom Scott himself
<a href="https://youtu.be/uAdmzyKagvE?t=351">concedes that VPNs are useful</a>
in the sponsor segment
of a video. And the conecession was that they
are useful forrr pretending you are visiting
a site from a different country. 🥁</p>

<p>Hah. Vindication.</p>

<h3 id="enterprise-vpns">Enterprise VPNs</h3>

<p>But not all VPNs are the same. Many large organisations deploy
and use their own VPNs to allow their staff access to company
resources from outside the premises. Using a VPN adds a layer
of protection against unauthorised access by keeping the
network private.</p>

<h3 id="using-vpn-to-expose-the-home-network">Using VPN to Expose the Home Network</h3>

<p>Today is the third day of writing this post, and I have entered
the main topic only now. Will I ever stop wandering?</p>

<p>I run some network-accessible services on a Raspberry Pi 400
on the home network that acts as my “server.” It’s mostly just
a <a href="https://syncthing.net">Syncthing</a> instance to handle backup
of my phone’s camera folder
and a <a href="https://photoprism.app">Photoprism</a> instance to view
said backups.</p>

<p>I would expand on it some day. But for now, it’s just two
services.</p>

<p>And sometimes, I wished I could access these from outside
home: on my phone, on a 4G connection.</p>

<h3 id="how-does-one-set-that-up">How does one set that up?</h3>

<p>By hosting one’s own VPN. Which often requires
having at least one device with a public IP address.</p>

<h5 id="openvpn">OpenVPN</h5>

<p>I tried setting up OpenVPN first. But I could not
understand any of what I was doing. I did set up a docker
container based on
<a href="https://hub.docker.com/r/kylemanna/openvpn/" target="\_blank">docker.io/kylemann/openvpn</a>.
But not knowing what I was doing was troubling.</p>

<h5 id="wireguard">Wireguard</h5>

<p>Wireguard was supposed to be easier to set up. It’s new
and reportedly more secure out of the box.</p>

<h5 id="others">Others</h5>

<p>Um… I didn’t check 😅</p>

<h3 id="wiring-up-wireguard">Wiring up Wireguard</h3>

<h4 id="installation">Installation</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt update
sudo apt install wireguard-tools
</code></pre></div></div>
<p>It wasn’t too bad with Raspbian, eh? Good thing it’s based on
Debian 11. I won’t be complaining for a while.</p>

<h4 id="education">Education</h4>

<p>Then I went to read the documentation. Okay, I did read some
beforehand too. I read the quickstart guides off the official
website.</p>

<p>I understood a little. Then I read the man pages. And I stopped
trying to understand and just started smashing buttons.</p>

<p>Also it has been a week since I started writing this post and
I don’t remember what I did.</p>

<h4 id="configuration">Configuration</h4>

<p>I started by using <code class="language-plaintext highlighter-rouge">wg set</code> but soon realised it wasn’t easy.
I somehow figured out <code class="language-plaintext highlighter-rouge">wg-quick</code>.</p>

<p>Finally found the systemd
service <code class="language-plaintext highlighter-rouge">wg-quick@.service</code>. So now I had an
<code class="language-plaintext highlighter-rouge">/etc/wireguard/wg0.conf</code> file to manage my configuration.</p>

<p>Eventually got it setup on two devices. Using the systemd
service. But what I didn’t know was that the service had
a <code class="language-plaintext highlighter-rouge">reload</code> command. I could have used
<code class="language-plaintext highlighter-rouge">systemctl reload wg-quick@wg0</code>. But I realised it only
much later.</p>

<p>I used a very complicated <code class="language-plaintext highlighter-rouge">wg syncconf &lt;(wg-quick strip wg0)</code>
that seemed to fail at times</p>

<p>The configuration file itself was simple enough</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Interface]</span>
<span class="py">Address</span> <span class="p">=</span> <span class="s">192.168.2.2/31</span>
<span class="py">PrivateKey</span> <span class="p">=</span> <span class="s">RandomKeyThatIsWeirdlyShort</span>

<span class="nn">[Peer 1]</span>
<span class="py">PublicKey</span> <span class="p">=</span> <span class="s">AnotherKeyButPublicStyle</span>

<span class="nn">[Peer 2]</span>
<span class="py">PublicKey</span> <span class="p">=</span> <span class="s">AnotherKeyButPublicStyle</span>

<span class="nn">[Peer n]</span>
<span class="py">PublicKey</span> <span class="p">=</span> <span class="s">AnotherKeyButPublicStyle</span>
</code></pre></div></div>

<p>I had added every single device’s public key
to every device’s configuration. So every wirguard
instance had the public key of all its peers.</p>

<p>Everything should be working fine, right?</p>

<h3 id="wires-crossed">Wires crossed</h3>

<p>Of course not. The ‘public key’ was not enough to
make a connection. Key exchanges are for authentication.
Wireguard also needs an ‘endpoint.’</p>

<p>At first I had expected there to be some kind of
global signalling server that handled this.
Similar to how Syncthing establishes connections
between devices.</p>

<p>Since that did not happen, I tried giving the
private IP address assigned to each interface
as the public endpoint. Of course it didn’t
work. But I tried it because one of my peers
had a public IP as its endpoint, so I thought
if all peers connected to said public peer
they should also be able to speak to each
other via the public peer. And since the
public peer would know the internal IPs
I hoped they would suffice as endpoints.</p>

<p>Nope. They did not suffice. IP forwarding
and things were needed. Things I did not
know or understand until…</p>

<h3 id="unofficial-wireguard-documentation-to-detangle">Unofficial WireGuard Documentation to Detangle</h3>

<p>I discovered some
<a href="https://github.com/pirate/wireguard-docs">community-created documentation for WireGuard</a>
and found out that the setup I was going for is called
a “bounce server.”</p>

<p>To configure a WireGuard network composed of
a mixture of devices with public and
private IPs, one or more fo the public peers
would be configured as bounce servers.</p>

<h3 id="a-bounce-server">A bounce server</h3>

<p>So far I configured peers as having an internal
IP address with a 32-bit prefix.
But instead a bounce server has a 24-bit (or
smaller prefix)</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Interface]</span>
<span class="py">Address</span> <span class="p">=</span> <span class="s">192.168.2.1/24</span>
</code></pre></div></div>

<p>In doing so, I let wireguard know that the current
peer may receive and forward traffic for other
peers within the same subnet.</p>

<p>A peer that handles connections to the internet
would have a prefix of 0 presumably.</p>

<h3 id="connecting-to-a-peer-via-a-bounce-server">Connecting to a peer via a bounce server</h3>

<p>Normally each peer needs the public key of
every peer it expects to connect to. But
when part of a network that involves a bounce
only the bounce is configured as a peer. I
expect this means the bounce server decrypts
traffic from one peer, then encrypts and
sends it to the next using its own keys.</p>

<p>I am not too concerned by this since I have
complete control over my bounce server. And
also because the alternatives are STUN server
which are more complicated.</p>

<h3 id="bounce-not-forwarding">Bounce not forwarding</h3>

<p>I had set everything up as I should, and
every peer was connected to the bounce.
But I could not connect from one peer
to the next over the wirguard network.</p>

<p>I decided to try enabling the
<code class="language-plaintext highlighter-rouge">net.ipv4.ip_forward</code> kernel parameter
that every guide was talking about. But
it was already enabled.</p>

<h3 id="irc-for-help">IRC for help</h3>

<p>Seeing no other way to resolve and
get it up and running I went to the
recommended IRC room for help.</p>

<p>Turns out my firewall was blocking IP forwards.
My server, running Fedora, used <a href="https://firewalld.org/">firewalld</a>—
the opposite of
<abbr title="reference to ufw&mdash;uncomplicated fire-wall&mdash;the firewall software used by the Ubuntu family">
uncomplicated</abbr>.</p>

<p>It has been a month and half since I started writing this post by the way. I am just going to
get this over with as soon as possible.</p>

<h1 id="future-me-intervenes">Future me intervenes</h1>

<p>Today is 11 January, 2023. It has been more than four months since I started this piece.</p>

<p>I do not even remember the exact details. I got my Wireguard VPN working. And while I
did not have many uses for it at first, it has now become one of the most important (to me)
internet services I manage.</p>

<p>I use it to provide my <a href="https://nextcloud.com/">Nextcloud</a> with an external storage backend via
SSH over Wireguard to my home server. So I can move off old large files that I need access to,
but don’t need to store on my rented
<abbr title="virtual private server; a virtual machine rented from some provider">VPS</abbr>.</p>

<p>Eventually I have come to realise that I sidetracked too much, purs
uing perfection and comprehensiveness that is not suited for this blog. This entry is neither
bite-sized nor <abbr title="in the sense of &quot;daily&quot;">journalish</abbr>.</p>

<p>As such, this ends the entry and I will move on to other ones. And try to stick to the idea of
<em>bitesyzed tech journal</em></p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[Thank you to Wireguard VPN for making this blog possible.]]></summary></entry><entry><title type="html">Logitech Unifying Tech is Cool</title><link href="http://blog.kevinnlsamuel.com/techdiary/2022/08/10/logitech-unifying-tech-is-cool.html" rel="alternate" type="text/html" title="Logitech Unifying Tech is Cool" /><published>2022-08-10T00:00:00+00:00</published><updated>2022-08-10T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2022/08/10/logitech-unifying-tech-is-cool</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2022/08/10/logitech-unifying-tech-is-cool.html"><![CDATA[<p>Recently installed Debian+GNOME on the home media computer.
It’s an aging first-gen Intel i5 machine typically running Windows.
And controlled using a Logitech mouse and keyboard via bluetooth.</p>

<h3 id="the-problem-with-bluetooth-peripherals">The Problem with bluetooth peripherals</h3>

<p>Bluetooth is not too bad. The connection is quite stable once
connected. But connecting is not always the fun part. And sure
there is probably a little bit of latency. Not one that I can
perceive. I just type, point and click on links. And best of
all, no wires!</p>

<p>The real problem for me is that bluetooth connections are made
at the operating system level.</p>

<h3 id="bluetooth-and-the-operating-system">Bluetooth and the operating system</h3>

<p>I did not actually know that bluetooth was OS-level until I
came across this bit in the Arch Linux wiki (the best Linux docs, ftw)
<a href="https://wiki.archlinux.org/title/bluetooth#Dual_boot_pairing">about pairing for dual boot</a></p>

<blockquote>
  <p>To pair devices on dual boot setups you need to change the pairing
keys manually on your Linux install, so that they match in both systems.</p>
</blockquote>

<p>Okay to be clear, the connection is done between hardware, obviously.
But the pairing is handled by the operating system. And pairing keys
are linked to the adapters.</p>

<h4 id="a-tangent-explaining-bluetooth">A tangent explaining bluetooth</h4>
<blockquote>
  <p>not verified</p>
</blockquote>

<ul>
  <li>Bluetooth is a set of rules for exchanging information between devices
using radio waves</li>
  <li>Every bluetooth-enabled device has an “adapter”—a device to
transmit and receive said radio waves</li>
  <li>Each adapter has a unique <abbr title="media access control">MAC</abbr> address
to identify itself to other adapters (devices)</li>
  <li>When two devices are being <strong>paired</strong>—connected for the first time—they exchange pairing keys</li>
  <li>The operating system stores said key and uses it for future connections</li>
</ul>

<p>This is not a problem when your bluetooth headsets have only one invisible
operating system; or one phone with one OS. But with a dual boot system,
you need to copy the pairing keys so both OSes share the same keys since
because assuming that the computer shares the bluetooth hardware, the MAC
would be mapped to a specific pairing key on the other device.</p>

<p>GRR.</p>

<p>But this is easily solvable. I do it all the time on my personal laptop
to connect my headphones to both Fedora and Windows.</p>

<h3 id="the-real-problem-for-me">The real problem for me</h3>

<p>is that I could not switch between OSes without using another keyboard.
I would need to press keys at the GRUB menu to change OSes, but that
would not be possible with a bluetooth keyboard because connections
are OS-level, not at the <abbr title="BIOS/UEFI">firmware</abbr> level.</p>

<p>So GRUB has no way of receiving this input.</p>

<h3 id="logitech-unifying">Logitech Unifying</h3>

<p>Now I already knew both the multi-device mouse and the multi-device keyboard
supported Logitech’s Unifying
receiver and each came with its own tiny one. But I had never used it.</p>

<p>I assumed that connecting or pairing would just be plug and pair. Those are
the kinds of receivers I had used so far. The device came with one dongle
and worked only with that one.</p>

<p>But instead it wanted some <em>software</em>??? Oh no. I thought it meant I needed
the software everywhere, and that it would immediately mean no Linux
compatability and definitely not working in firmware.</p>

<h3 id="stackexchange-to-the-rescue">StackExchange to the rescue</h3>
<blockquote>
  <p>Google played a minor role too</p>
</blockquote>

<p>Looking up “Logitech Unifying + BIOS” gave me the answer I needed. It <em>was</em>
possible to use the the keyboard with the receiver in firmware menus!</p>

<p>I took a look at YouTube also because Logitech has a pretty good stock of
content on their channel. And WOW those videos are OOOLD!</p>

<p><img src="/techdiary/assets/post-img/2022/08/10-000-yearsago.png" alt="video listings on Logitech's YouTube channel from thirteen and nine years
before 2022 (2009 and 2013) on the topic of unifying receiver" loading="lazy" /></p>

<blockquote>
  <p>This has been around since 2009???</p>
</blockquote>

<p>That is so cool!</p>

<h3 id="connecting-via-the-unifying-receiver">Connecting via the Unifying Receiver</h3>

<p>Turns out the special software is needed only to make the connection.
The pairing process so to speak. And this time the pairing information
is stored on the receiver itself; not the OS. So it becomes possible to
use it on any system, it just reports itself as a regular old wireless
keyboard and mouse!</p>

<p>I can use it in firmware, GRUB, Linux, Windows. And even on a completely
different computer because now the device is paired with the receiver,
not the computer.</p>

<h3 id="i-am-disappointed">I am disappointed</h3>

<p>That I did not know this before.</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[I finally write the thing I wanted to write a week ago about the thing from 13 years ago that I tried a week ago]]></summary></entry><entry><title type="html">Venturing into Vite</title><link href="http://blog.kevinnlsamuel.com/techdiary/2022/07/30/venturing-into-vite.html" rel="alternate" type="text/html" title="Venturing into Vite" /><published>2022-07-30T00:00:00+00:00</published><updated>2022-07-30T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2022/07/30/venturing-into-vite</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2022/07/30/venturing-into-vite.html"><![CDATA[<h3 id="why-vite">Why Vite?</h3>

<p>Is a section of the official website of the Vite project.
Trying to read it without understanding ESM was what led
me on <a href="/techdiary/2022/07/27/modules-in-the-browser-and-yaks.html">the previous rabbit hole, where I checked out
modules</a></p>

<p>Since then, I am a bit clearer on JavaScript modules
in the browser. And also no longer in the mood to read
so much documentation. Time to dive in blind and figure
it out.</p>

<h3 id="initialising-a-vite-project">Initialising a Vite Project</h3>

<p>I jumped to the Getting Started section in Vite’s homepage
and found the commands to create a Vite project</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm create vite@latest
</code></pre></div></div>
<p>or</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn create vite
</code></pre></div></div>

<p>I was using a container based on the
<a href="https://hub.docker.com/r/kevinnls/node-unprivileged" target="\_blank"><code class="language-plaintext highlighter-rouge">docker.io/kevinnls/node-unprivileged</code> image</a>
I wrote
(<em>coughs</em>—that <strong>very badly</strong> needs updates and automation)
with a docker-compose spec to mount the project directory to
the correct destination within the container.</p>

<p>So I set these files up before running <code class="language-plaintext highlighter-rouge">yarn create vite .</code>…
and the <code class="language-plaintext highlighter-rouge">vite-create</code> package strongly insisted on removing everything
in <code class="language-plaintext highlighter-rouge">.</code>.</p>

<p>I thought to myself that I could just restore the files using git
since I had already checked them in. Then I chose a vanill + TypeScript
config for the prject.</p>

<p>Ha. Ha. <code class="language-plaintext highlighter-rouge">create-vite</code> deleted the <code class="language-plaintext highlighter-rouge">.git</code>
folder too. This is not a good start.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git clone &lt;url&gt;
<span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> &lt;repo&gt;
<span class="gp">$</span><span class="w"> </span>docker compose run <span class="nt">--rm</span> dev sh
<span class="go">
</span><span class="gp">node@container $</span><span class="w"> </span><span class="nb">cd</span> &lt;mount point&gt;
<span class="gp">node@container $</span><span class="w"> </span>yarn create vite
<span class="gp">node@container $</span><span class="w"> </span><span class="c"># chose vanilla + ts</span>
<span class="gp">node@container $</span><span class="w"> </span><span class="nb">mv </span>vite-app/<span class="k">*</span> vite-app/.<span class="k">*</span> <span class="nb">.</span>
<span class="gp">node@container $</span><span class="w"> </span><span class="nb">rmdir </span>vite-app
<span class="gp">node@container $</span><span class="w"> </span><span class="nb">exit</span>
<span class="go">
</span><span class="gp">$</span><span class="w"> </span>docker compose up <span class="nt">-d</span>
</code></pre></div></div>

<p>Well that was complex. But it did not fix everything</p>

<h3 id="the-container-exited-immediately">The container exited immediately</h3>

<p>And to debug, I ran it again, duh. And when that did not fix it, I
checked the logs.</p>

<p>The container’s command was <code class="language-plaintext highlighter-rouge">yarn run dev</code> and it could not find
a binary for <code class="language-plaintext highlighter-rouge">vite</code>. Oh. I did not install any of the packages.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker run <span class="nt">--rm</span> dev sh
<span class="go">
</span><span class="gp">node@container $</span><span class="w"> </span><span class="nb">cd</span> &lt;mount point&gt;
<span class="gp">node@container $</span><span class="w"> </span>yarn
<span class="gp">node@container $</span><span class="w"> </span><span class="nb">exit</span>
<span class="go">
</span><span class="gp">$</span><span class="w"> </span>docker compose up <span class="nt">-d</span>
</code></pre></div></div>

<p>I had already changed the publish ports to match the server in the
container.</p>

<p>But I still could not access the server from the host.</p>

<h3 id="exposing-the-vite-containers-dev-server">Exposing the Vite container’s dev server</h3>

<p>Turns out the Vite dev server exposes only to localhost (as do most
others) and my command needed the <code class="language-plaintext highlighter-rouge">--host</code> flag</p>

<p>Modify the compose spec further. New command now <code class="language-plaintext highlighter-rouge">yarn run dev --host</code>.</p>

<h3 id="the-file-tree">The file tree</h3>

<p>I went over the files that were created by <code class="language-plaintext highlighter-rouge">create-vite</code>.</p>
<ul>
  <li>
    <h4 id="packagejson"><code class="language-plaintext highlighter-rouge">package.json</code></h4>
    <p>A <code class="language-plaintext highlighter-rouge">scripts</code> object was added with the <code class="language-plaintext highlighter-rouge">dev</code>, <code class="language-plaintext highlighter-rouge">build</code>, <code class="language-plaintext highlighter-rouge">preview</code>
commands and the corresponding <code class="language-plaintext highlighter-rouge">vite</code> commands.</p>
  </li>
  <li>
    <h4 id="indexhtml"><code class="language-plaintext highlighter-rouge">index.html</code></h4>
    <p>Basic HTML that every JS framework generates. An empty div
to be manipulated later by scripts. And a script that is
imported <em>as a module</em>.
I have not really noticed this before. Do all frameworks
use module now?</p>
  </li>
  <li>
    <h4 id="tsconfigjson"><code class="language-plaintext highlighter-rouge">tsconfig.json</code></h4>
    <p>TypeScript configuration options that I don’t really know anything
about. The only thing that stood out to me was the <code class="language-plaintext highlighter-rouge">include</code> key
whose value was <code class="language-plaintext highlighter-rouge">["src"]</code> where the <code class="language-plaintext highlighter-rouge">main.ts</code> file is saved.</p>
  </li>
  <li>
    <h4 id="srcmaints"><code class="language-plaintext highlighter-rouge">src/main.ts</code></h4>
    <p>It was just one very big string that got inserted into the container
div. And it calls a setup function definied in another module</p>
  </li>
  <li>
    <h4 id="srccounterts"><code class="language-plaintext highlighter-rouge">src/counter.ts</code></h4>
    <p>Exports the setup function I mentioned earlier that adds a click
listener on a button that’s a counter</p>
  </li>
</ul>

<blockquote>
  <p>Looks like counters are the demo of every framework</p>
</blockquote>

<ul>
  <li>
    <h4 id="srcvite-envdts"><code class="language-plaintext highlighter-rouge">src/vite-env.d.ts</code></h4>
    <p>No iead what this is. But I don’t know enough TS for this to make
sense. Yet.</p>
  </li>
  <li>
    <h4 id="a-bunch-of-static-files">a bunch of static files</h4>
    <p>Styles, SVGs. But why are we importing styles into the modules?
I don’t know. We don’t import it in the <code class="language-plaintext highlighter-rouge">index.html</code> file I
realised later (as in, just now). I suppose that’s part of
the vite magic? Applying CSS through module imports? Or did
I just not realise it was possible?</p>
  </li>
</ul>

<h4 id="and-it-serves-typescript-files">And it serves TypeScript files…?</h4>

<p>I saw the imported script file in <code class="language-plaintext highlighter-rouge">index.html</code> was still <code class="language-plaintext highlighter-rouge">src/main.ts</code>.
Interesting… At the moment, the browser is not a runtime that
can execute TypeScript (and I don’t think it ever will be).</p>

<p>Heading over to <code class="language-plaintext highlighter-rouge">view-source://localhost:8080/src/main.ts</code> and I read
pure JavaScript. So it’s serving a file with <code class="language-plaintext highlighter-rouge">.ts</code> but it’s a JS file.
But browsers wouldn’t know what to do with the <code class="language-plaintext highlighter-rouge">.ts</code> extension–
oh wait. Browsers don’t care about extensions. Only media types.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>curl localhost:8080/src/main.ts <span class="nt">-I</span>
<span class="c">...
</span><span class="go">Content-Type: application/javascript
</span><span class="c">...
</span></code></pre></div></div>
<p>And it’s the server that sets the media type. MIME type. That’s the
word. I have been writing this for too long. Procrastination. And
I cannot speak nerd anymore.</p>

<h3 id="so-what-does-vite-do">So what does Vite do?</h3>

<p>It serves the project source. That’s it. Well, that’s all I know.</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[I check out Vite]]></summary></entry><entry><title type="html">Modules in the Browser and Yaks</title><link href="http://blog.kevinnlsamuel.com/techdiary/2022/07/27/modules-in-the-browser-and-yaks.html" rel="alternate" type="text/html" title="Modules in the Browser and Yaks" /><published>2022-07-27T00:00:00+00:00</published><updated>2022-07-27T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2022/07/27/modules-in-the-browser-and-yaks</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2022/07/27/modules-in-the-browser-and-yaks.html"><![CDATA[<h3 id="building-websites">Building websites</h3>

<p>So far every website I have ever built was either created using
a JavaScript framework’s default tools, Jekyll, or plain
HTML/CSS/JS.</p>

<p>I decided I wanted to learn to build websites using build tools
that I configured myself.
<a href="https://vitejs.dev">Vite</a> has been the most popular as far as I
know.
So I went to their homepage. The helpful
<a href="https://vitejs.dev/guide/why.html">‘Why Vite?’</a> link caught my
attention. Navigating to the next page, I soon lost attention.</p>

<p>I had no idea what ‘modules’ were. And that is all that the page
was talking about.</p>

<h3 id="what-are-modules">What are modules?</h3>

<p>Is the question I embarked to answer. Of course by modules, I
mean ESM–ECMAScript Modules.</p>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">MDN’s article on JavaScript Modules</a>
is where I begin my journey. To play around with the concepts
I started building a website that was definitely not inspired
by Svelte’s tutorial.</p>

<p>It was simple enough. A numeric counter that updates based on
user input. The user is given two buttons: one to increment
the counter and the other to decrement it.</p>

<p><img src="/techdiary/assets/post-img/2022/07/27-001-counter.png" alt="increment decrement buttons with a counter in the center" style="border: 2px solid grey; border-radius: 23px;" /></p>

<p>But what’s the point of having modules if there’s only one thing
done?</p>

<h3 id="observing-the-counter">Observing the counter</h3>
<h4 style="margin-top: 0px;" id="aka-yak-1">aka <code class="language-plaintext highlighter-rouge">yak-1</code></h4>

<p>To keep experimenting with modules, I did add a few <code class="language-plaintext highlighter-rouge">console.log()</code>
statements because duh. But I also went a bit further and
decided to persist the counter between sessions.</p>

<p>Normally, I would use <code class="language-plaintext highlighter-rouge">localStorage.setItem()</code> as part of the button’s
click event handler. This time I decided to try something different.
Hoping that doing this would not block the main thread, I used the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver</a>
API to watch the DOM change and store to localStorage when it does.</p>

<p>Might be better to use a promise or some mechanism to debounce.
But those are things I thought of now; not when I was working on it.</p>

<h3 id="a-spinner">A Spinner</h3>
<h4 style="margin-top: 0px;" id="aka-yak-2">aka <code class="language-plaintext highlighter-rouge">yak-2</code></h4>

<p>Because I was storing to localStorage, I had to read from localStorage
on page loads. While the browser storage is fast, it is visible
when the value changes at initial load.</p>

<p>So I wrote a spinner SVG. COMPLETELY BY HAND! Okay I mean manually.</p>

<p>I even added animation! TADA!</p>

<figure style="text-align: center; border: 2px solid grey; border-radius:23px; padding: 2ch;">
<svg width="100px" height="100px" id="spinner" fill="none" viewbox="0 0 11 11">
    <circle stroke="white" stroke-opacity="30%" cx="5.5" cy="5.5" r="5" />
    <path stroke="hotpink" d="
	    M 5.5 .5
	    a 5 5 90 0 1 5 5
	      ">
	<animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" repeatCount="indefinite" from="0 5.5 5.5" to="360 5.5 5.5" />
    </path>
</svg>
<figcaption>The spinner I manually drew and animated using SVG</figcaption>
</figure>

<p>The inspiration was a recent episode of HTTP203 titled <a href="https://youtu.be/9qen5CKjUe8"><em>“Demystifying SVG Paths”</em></a></p>

<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark"><p lang="en" dir="ltr">the episode worked! i wrote some SVG manually! 🐢<br />then i forced the user to see it because i wanted to show off</p>&mdash; Kevin Samuel (@kevinnlsamuel) <a href="https://twitter.com/kevinnlsamuel/status/1551932848763985921?ref_src=twsrc%5Etfw">July 26, 2022</a></blockquote>

<h3 id="i-now-know-modules">I now know modules</h3>
<h4 style="margin-top: 0px;" id="aka-finish-shaving-yak-0">aka finish shaving <code class="language-plaintext highlighter-rouge">yak-0</code></h4>

<p>I realised only while writing this post that my entire journey
learning about modules was a yak shaving trip in itself.</p>

<p>I had started because I wanted to try out Vite…</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[Diving deep into the murky shallows of the web and shaving yaks]]></summary></entry><entry><title type="html">The tag in HTML</title><link href="http://blog.kevinnlsamuel.com/techdiary/2022/07/18/the-base-tag-in-html.html" rel="alternate" type="text/html" title="The tag in HTML" /><published>2022-07-18T00:00:00+00:00</published><updated>2022-07-18T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2022/07/18/the-base-tag-in-html</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2022/07/18/the-base-tag-in-html.html"><![CDATA[<script>
    document.title = 'The <base> tag in HTML'
    document.querySelector('article h1.post-title.p-name').innerHTML = 'The <code>&lt;base&gt;</code> tag in HTML'
</script>

<h3 id="how-i-open-links">How I open links</h3>

<p>I almost always open links in a new tab.
Be it with primary click while holding down <kbd>ctrl</kbd>;
or as I do nowadays with a middle click emulated by the scroll
wheel or a three-finger tap on the touchpad. That was a long
sentence that I most certainly could have framed better.</p>

<h3 id="does-everyone-else-open-links-like-this">Does everyone else open links like this?</h3>

<p>I have no idea. I know my mother uses the secondary-click →
open in new tab flow. But I have not watched a lot of other people
use their computers.</p>

<p>Maybe I should start watching people who code stream and observe.</p>

<h3 id="what-do-i-want-links-to-do">What do I want links to do?</h3>

<p>When I have an <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> tag in my site, during the initial stages
of making this blog I thought</p>

<blockquote>
  <p>I want all links to always have <code class="language-plaintext highlighter-rouge">target=_blank</code></p>
</blockquote>

<p>But now I am wiser. I know that</p>
<ol>
  <li>I want links embedded in my post to have <code class="language-plaintext highlighter-rouge">_blank</code> as their target</li>
  <li>I want navigational links to targe <code class="language-plaintext highlighter-rouge">_self</code></li>
</ol>

<p>Today I discovered that it may be possible to achieve this using
HTML!</p>

<h3 id="whats-a-blog-post-by-me-without-a-long-and-unnecessary-tangent">What’s a blog post by me without a long and unnecessary tangent?</h3>

<p>I was trying to think of a semantic tag that I could use to group
a set of action buttons in my website’s header. Normally elements
in a header are grouped using <code class="language-plaintext highlighter-rouge">&lt;nav</code>. But I wanted to use buttons
for print and contact. And neither was destined to be a navigation.</p>

<p>I could think of <code class="language-plaintext highlighter-rouge">&lt;section&gt;</code> but it did not seem right. Maybe I
should just use a <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code>. Nonetheless, I was on mdn &lt;3 —
<a href="https://developer.mozilla.org">the Mozilla Developer Network Web Docs</a>
verifying the semantics of the section tag. Then I had the idea
of looking at all tags. So I went to the
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element">HTML Elements Reference</a></p>

<h4 id="breadcrumbs-a-tangent-from-the-tangent">Breadcrumbs: A tangent from the tangent</h4>

<p><img src="/techdiary/assets/post-img/2022/07/18-000-breadcrumbs_to_section.png" alt="breadcrumbs on the MDN page leading to the section tag's page" /></p>

<p>The feature that many sites have started to adopt to show the logical
path in the website that leads to the current page. I learnt recently
it’s called “breadcrumbs.” Such a cute name. I forget where I learnt
it from. This is what I get for not writing my “diary” regulary.</p>

<p>Also props to Google Chrome’s DevTools finally introducing node
screenshots. Makes things a bit easier while writing pieces like
this.</p>

<h3 id="the-base-tag">The <code class="language-plaintext highlighter-rouge">&lt;base&gt;</code> tag</h3>

<p>While perusing the list of elements, I chanced upon the base tag.
I had never heard of it before. Opening the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base">MDN reference page for
<code class="language-plaintext highlighter-rouge">&lt;base&gt;</code></a>
I find out it’s used to specify a canonical uri that serves as the
base for relative uris within the page.</p>

<p>But it can also be used to define the default <code class="language-plaintext highlighter-rouge">target</code> behaviour for anchors
that do not have anything specified.</p>

<p>This exactly what I wanted!</p>

<h3 id="but">But</h3>

<p>But the <code class="language-plaintext highlighter-rouge">target</code> defined using base does not differentiate between
internal navigation and external embeds. So everything opens in a new
context :(</p>

<p>Maybe this isn’t the right solution. But it was a fun discovery!</p>

<h3 id="other-news">Other news</h3>

<h4 id="getting-better-at-vim">Getting better at vim</h4>

<p>I use vim as my main editor most of the time. And I recently watched
a set of videos on YouTube by The Primeagen—
<a href="https://youtube.com/playlist?list=PLm323Lc7iSW_wuxqmKx_xxNtJC_hJbQ7R"><em>“Vim as your editor”</em></a>.
And I have got three videos in and started to use some of the tricks.
Slowly incorporating it into my work.</p>

<p>The <code class="language-plaintext highlighter-rouge">F</code> command has been useful. I used to keep doing <code class="language-plaintext highlighter-rouge">f</code> and <code class="language-plaintext highlighter-rouge">,</code> to
go search backwards in the line to jump. Horizontal movement. It was
not until recently that I even started using the <code class="language-plaintext highlighter-rouge">f</code> command. Used to
<code class="language-plaintext highlighter-rouge">hl</code> like a caveperson.</p>

<p>Also discovered <code class="language-plaintext highlighter-rouge">:set relativenumber</code> from the videos. So I am
also trying to use relative vertical movements instead of smashing
down on <code class="language-plaintext highlighter-rouge">jk</code>. I even added it to my <code class="language-plaintext highlighter-rouge">~/.vimrc</code>. I have not got
around to using it as effectively as I would like. But baby steps, right?</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[I discover a way to make links work as I want!]]></summary></entry><entry><title type="html">Polish My Own Gem</title><link href="http://blog.kevinnlsamuel.com/techdiary/2022/06/25/polish-my-own-gem.html" rel="alternate" type="text/html" title="Polish My Own Gem" /><published>2022-06-25T00:00:00+00:00</published><updated>2022-06-25T00:00:00+00:00</updated><id>http://blog.kevinnlsamuel.com/techdiary/2022/06/25/polish-my-own-gem</id><content type="html" xml:base="http://blog.kevinnlsamuel.com/techdiary/2022/06/25/polish-my-own-gem.html"><![CDATA[<p>and deny that I took another break</p>

<h3 id="github-pages-and-jekyll">GitHub Pages and Jekyll</h3>

<p>This blog has been hosted on
<a href="https://pages.github.com">GitHub Pages</a>
from the very beginning. Pages is the free
static-hosting offered by GitHub.</p>

<p>It was Pages that introduced me to
<a href="https://jekyllrb.com">Jekyll</a> – a
static site generator that runs on Ruby.
And I have since had a soft spot for Jekyll.
I would use it anywhere I could.</p>

<p>I find it easy to use and understand.
The major factor for me is possibly the
direct relation between source file and html.
Something I had not seen in the
Javascript-frameworks I had experienced until
then. Abstractations frighten me. Well, some
of them. I have no knowledge in Ruby after all.</p>

<h3 id="local-development">Local Development</h3>

<p>As I write the posts in watch them rendered
in my browser using Jekyll’s server—
<code class="language-plaintext highlighter-rouge">jekyll serve</code>.</p>

<p>But I don’t like installing development
dependencies to my host operating systems
so I run Jekyll in a Docker container.</p>

<p>Long ago, I used the
<a href="https://hub.docker.com/r/jekyll/jekyll"><code class="language-plaintext highlighter-rouge">jekyll/jekyll</code></a>
image. Took a lot of fiddling and customising
to get it to work the way I wanted (cannot
remember why). But I had evenually
discovered GitHub published its own
<a href="https://github.com/github/pages-gem/pkgs/container/pages-gem">Docker image for GitHub Pages</a>.
This image used the correct gem (ruby module)
and installed all the other dependencies too.</p>

<p>Just one problem. It was <strong>HUGE</strong>. Sizes beyond
a gigabyte. Something I did not prefer.</p>

<p>They did have a Dockerfile for Alpine images.
But they just never built or published it.</p>

<h4 id="local-development-on-a-pi">Local Development on a Pi</h4>

<p>And another hitch I ran into was when I ran
the server off my Rapsberry Pi, a computer
running Linux on the ARM architecture.</p>

<p>The image – the whole gigabyte of it
– downloaded without complaints. But
in the end it wouldn’t run because it was
not the supported architecture. Well that
was disappointing.</p>

<h3 id="new-images-from-the-gem">New Images from the Gem</h3>

<p>So I forked and cloned the entire
<a href="https://github.com/github/pages-gem">repository for the Pages gem and image</a>.</p>

<p>It had a Makefile with a defined target
<code class="language-plaintext highlighter-rouge">image_alpine</code></p>

<p>But I didn’t want to have to clone the entire
repo each time on each system I needed the
image. Not even a shallow clone. I really
don’t like downloading development things
I don’t need.</p>

<p>So I took it upon myself to use GitHub Actions—the
CI/CD service offerred by them and build
and publish the Alpine image for myself.</p>

<p>The original repo already had some configuration
for this since that was how the images were built
upstream too.</p>

<p>After 16 commits and lots of failed builds I finally
got the images I needed building automatically on
<a href="https://github.com/kevinnls/pages-gem">my fork</a>
using GitHub’s infrastructure.</p>

<h3 id="thoughts-on-github">Thoughts on GitHub</h3>

<p>But with all this, it certainly does seem like
I am locking myself to the vendor (read Microsoft).
Hosting my code, my site, building my images,
publishing my images. Everything on GitHub.</p>

<p>But I am not necessarily a fan of the platform.
At least not as much as I used to be.</p>

<p>The recent questions of ethics that have been raised
with GitHub charging for their Co-Pilot service
is a minor reason for it. I also find the Actions
platform rather complicated. GitLab CI was quite
simpler. But I do enjoy that the yaml files are
in a subdirectory and that I can have more than one,
something GitLab doesn’t seem to support.</p>

<p>I do enjoy that they have a CLI app. But I have
been using it less and less. And overall
GitHub does feel less refined and more cluttered
that it used to be when I started using it.</p>

<p>I do plan to eventually go completely self-hosted
for my own repositories. But that’s a way off in
the future. Until then, GitHub it is.</p>]]></content><author><name>Kevin Samuel</name><email>developer@kevinnlsamuel.com</email></author><summary type="html"><![CDATA[I fork and build my own Docker image for GitHub Pages using GitHub's resources]]></summary></entry></feed>