<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Manoj's Blog]]></title><description><![CDATA[Manoj's Blog]]></description><link>https://blog.manoj.ch</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 11:35:06 GMT</lastBuildDate><atom:link href="https://blog.manoj.ch/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Regular expressions are awesome!]]></title><description><![CDATA[I think regular expressions is a topic that gets equal love and hate from programmers. We love it when it works, and when it doesn't it makes us want to pull out our hair. (Perhaps, bald programmers are the most experienced Regex users? ;) )
If used ...]]></description><link>https://blog.manoj.ch/regular-expressions-are-awesome</link><guid isPermaLink="true">https://blog.manoj.ch/regular-expressions-are-awesome</guid><category><![CDATA[Productivity]]></category><category><![CDATA[programming]]></category><category><![CDATA[Regex]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Fri, 16 Aug 2024 09:35:24 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://xkcd.com/208/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723800782518/97f2c6c3-1751-4b0d-8432-9b10c8c2e490.png" alt class="image--center mx-auto" /></a></p>
<p>I think regular expressions is a topic that gets <a target="_blank" href="https://dev.to/thibaultduponchelle/thoughts-citations-and-thoughts-about-regex-29h0">equal love and hate</a> from programmers. We love it when it works, and when it doesn't it makes us want to pull out our hair. (Perhaps, bald programmers are the most experienced Regex users? ;) )</p>
<p>If used right, it is an awesome tool to improve your Find/Replace superpower! I have realised that I use it more often than before, especially when refactoring code where I need to make adjustments in multiple places.</p>
]]></content:encoded></item><item><title><![CDATA[Android - Modify hosts file]]></title><description><![CDATA[I use Cordova to bundle a web application as a native app for ease of distribution.
To test the site locally, but on the simulator, I needed to modify the hosts file on the Android simulator to point the test domain to the host machine:

Start the em...]]></description><link>https://blog.manoj.ch/android-modify-hosts-file</link><guid isPermaLink="true">https://blog.manoj.ch/android-modify-hosts-file</guid><category><![CDATA[Android]]></category><category><![CDATA[simulator ]]></category><category><![CDATA[hosts]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Tue, 26 Mar 2024 07:56:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/7td0XPp3ZuQ/upload/516e8a07d4cf196b185f16f41ecbda75.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I use Cordova to bundle a web application as a native app for ease of distribution.</p>
<p>To test the site locally, but on the simulator, I needed to modify the hosts file on the Android simulator to point the test domain to the host machine:</p>
<ul>
<li><p>Start the emulator in writeable mode:</p>
</li>
<li><pre><code class="lang-bash">  emulator &lt;avd-name&gt; -writeable-system
</code></pre>
</li>
<li><p>Set adb to root</p>
</li>
<li><pre><code class="lang-bash">  adb root
</code></pre>
</li>
<li><p>Remount filesystem</p>
</li>
<li><pre><code class="lang-bash">  adb remount
</code></pre>
</li>
<li><p>Log into the simulator shell</p>
</li>
<li><pre><code class="lang-bash">  adb shell
</code></pre>
</li>
<li><p>Edit the hosts file</p>
</li>
<li><pre><code class="lang-bash">  vi /system/etc/hosts
</code></pre>
</li>
</ul>
<p>After this, you should be able to access the FQDN on the simulator, which will now be pointing to the specified IP.</p>
]]></content:encoded></item><item><title><![CDATA[Git - Set SSH private key per repository]]></title><description><![CDATA[I work with git repositories from multiple sources - GitHub, GitLab, and Enterprise GitHub instance. I have a separate SSH keys for each of them and further, I have to keep track of other SSH keys that I use for accessing servers and other resources....]]></description><link>https://blog.manoj.ch/git-set-ssh-private-key-per-repository</link><guid isPermaLink="true">https://blog.manoj.ch/git-set-ssh-private-key-per-repository</guid><category><![CDATA[Git]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[GitLab]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Tue, 06 Apr 2021 08:58:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617698178977/hJ7jHbqEV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I work with git repositories from multiple sources - GitHub, GitLab, and Enterprise GitHub instance. I have a separate SSH keys for each of them and further, I have to keep track of other SSH keys that I use for accessing servers and other resources.</p>
<p>Recently, I got a new Windows machine from work and it started complaining that it couldn't use the right SSH key for my Enterprise GitHub repositories. No matter what I tried, it failed to authenticate correctly.</p>
<p>While, searching for a solution, I came across two ways to solve this:</p>
<h3 id="git-config">Git config</h3>
<p>Git provides a config parameter (<code>git &gt;v2.10</code>) that can override the <code>ssh</code> command that <code>git</code> uses. So, piggybacking on this command, we can pass in the identity file information to it.</p>
<pre><code><span class="hljs-attribute">git</span> config core.sshCommand <span class="hljs-string">"ssh -i ~/.ssh/id_rsa_github -F /dev/null"</span>
</code></pre><p>What this enables is the ability to set a different SSH key for each repository as needed. Further, this method works across platforms.</p>
<h3 id="environment-variable">Environment variable</h3>
<p>Another method is to use the <code>GIT_SSH_COMMAND</code> (<code>git &gt;v2.30</code>) environment variable to do the same. You can add an alias for <code>git</code> based on the site.</p>
<pre><code><span class="hljs-keyword">alias</span> github=<span class="hljs-string">'GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa_github -F /dev/null" git'</span>
</code></pre><p>And then use <code>github clone repo_url</code> instead of <code>git clone repo_url</code>.</p>
<p>I prefer using the git config method as I don't have to worry about which alias to use in which repository.</p>
]]></content:encoded></item><item><title><![CDATA[Thoughts on deadlines and perfection]]></title><description><![CDATA[Deadlines are a double-edged sword, especially for perfectionists and optimizers!
However, good engineers know that any feature can only ever be release-worthy, but never perfect.
The more a feature is delayed beyond what is necessary for it to work,...]]></description><link>https://blog.manoj.ch/thoughts-on-deadlines-and-perfection</link><guid isPermaLink="true">https://blog.manoj.ch/thoughts-on-deadlines-and-perfection</guid><category><![CDATA[work]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Thu, 01 Apr 2021 22:38:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617316634010/Ha1HXwPxO.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Deadlines are a double-edged sword, especially for perfectionists and optimizers!</p>
<p>However, good engineers know that any feature can only ever be release-worthy, but never perfect.</p>
<p>The more a feature is delayed beyond what is necessary for it to work, the more disservice you are causing for people waiting on it.</p>
<p>Perfection is the enemy of utility.</p>
<p>Further, perfection cannot be achieved without feedback. Feedback after using a feature is worth 100x more than any analysis at the time of building it.</p>
]]></content:encoded></item><item><title><![CDATA[Docker - Use docker-compose to initialize database]]></title><description><![CDATA[When using docker-compose.yml to run your application, you will need to ensure that your database is initialized before it is ready.
Although it is expected of the application to manage it’s own database migrations and  seeding there maybe occasions ...]]></description><link>https://blog.manoj.ch/docker-use-docker-compose-to-initialize-database</link><guid isPermaLink="true">https://blog.manoj.ch/docker-use-docker-compose-to-initialize-database</guid><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Sat, 13 Mar 2021 22:41:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615674338939/fiFynxG9X.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When using <code>docker-compose.yml</code> to run your application, you will need to ensure that your database is initialized before it is ready.</p>
<p>Although it is expected of the application to manage it’s own database migrations and  <a target="_blank" href="https://blog.manoj.ch/typeorm-use-querybuilder-for-database-seeding">seeding</a> there maybe occasions where additional setup might be required such as enabling a database plugin, managing users and granting permissions. For such operations, using a <code>Dockerfile</code>, you would <code>COPY</code> an initialize shell script or SQL file to <code>/docker-entrypoint-initdb.d</code>. Any <code>.sh</code> or <code>.sql</code> file present there would be executed upon container start.</p>
<p>But while using <code>docker-compose.yml</code> to run an official database image, you can avoid creating a separate <code>Dockerfile</code> for the database by using the <code>volumes</code> parameter for the database service:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">postgres:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">username</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">password</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./docker/init.sql:/docker-entrypoint-initdb.d/init.sql</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
</code></pre>
<p>And that’s it. Any SQL statements present in <code>init.sql</code> would be executed on container start.</p>
<blockquote>
<p>P.S.: Using <code>POSTGRES_USER</code> <code>POSTGRES_PASSWORD</code> <code>POSTGRES_DB</code> environment variables would automatically create the database and the user and also grant permissions.</p>
<p>Note: Take care to append <code>IF NOT EXISTS</code> to any <code>CREATE</code> queries to avoid errors. </p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Google FontTools - Convert fonts to different formats (TTF, WOFF, WOFF2)]]></title><description><![CDATA[I wanted a way to convert fonts to different formats without having to rely on online tools - it shouldn't be really hard - I guessed. And luckily, it isn't. :)
Install FontTools
Google has released an opensource font conversion program which we'll u...]]></description><link>https://blog.manoj.ch/google-fonttools-convert-fonts-to-different-formats-ttf-woff-woff2</link><guid isPermaLink="true">https://blog.manoj.ch/google-fonttools-convert-fonts-to-different-formats-ttf-woff-woff2</guid><category><![CDATA[fonts]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Sat, 13 Mar 2021 13:23:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615641907177/QM1woB0UF.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I wanted a way to convert fonts to different formats without having to rely on online tools - it shouldn't be really hard - I guessed. And luckily, it isn't. :)</p>
<h2 id="install-fonttools">Install FontTools</h2>
<p>Google has released an opensource font conversion program which we'll use. We'll need python, pip and some libraries - so, let's get them first.</p>
<h3 id="install-python-v3">Install python (v3)</h3>
<p>Download and install <code>python3</code>:</p>
<ul>
<li>Windows (if you use WSL, see Linux installation, else: https://www.python.org/downloads/windows/)</li>
<li>Mac (use homebrew - recommended, or get it here: https://www.python.org/downloads/mac-osx/)</li>
<li>Linux (Debian-based - <code>apt install python3</code>)</li>
</ul>
<p><strong>Verify installation:</strong></p>
<pre><code><span class="hljs-string">$</span> <span class="hljs-string">python3</span> <span class="hljs-string">--version</span>
<span class="hljs-string">Python</span> <span class="hljs-number">3.8</span><span class="hljs-number">.5</span>      <span class="hljs-comment"># &lt;-- expected output</span>
</code></pre><h3 id="install-pip-v3">Install pip (v3)</h3>
<ul>
<li>Linux and Mac: <code>$ python3 -m pip --version</code></li>
<li>Windows: <code>C:\&gt; py -m pip --version</code></li>
</ul>
<p>See: https://pip.pypa.io/en/stable/installing/</p>
<p><strong>Verify installation:</strong></p>
<pre><code><span class="hljs-string">$</span> <span class="hljs-string">pip</span> <span class="hljs-string">--version</span>
<span class="hljs-string">pip</span> <span class="hljs-number">20.0</span><span class="hljs-number">.2</span> <span class="hljs-string">from</span> <span class="hljs-string">/usr/lib/python3/dist-packages/pip</span> <span class="hljs-string">(python</span> <span class="hljs-number">3.8</span><span class="hljs-string">)</span> <span class="hljs-comment"># &lt;-- output</span>
</code></pre><h3 id="install-fonttools-and-brotli">Install FontTools and brotli</h3>
<p><strong>FontTools:</strong></p>
<pre><code>$ pip install fonttools
Collecting fonttools
  Downloading fonttools<span class="hljs-number">-4.21</span><span class="hljs-number">.1</span>-py3-<span class="hljs-keyword">none</span>-<span class="hljs-keyword">any</span>.whl (<span class="hljs-number">849</span> kB)
     |████████████████████████████████| <span class="hljs-number">849</span> kB 
Installing collected packages: fonttools
Successfully installed fonttools<span class="hljs-number">-4.21</span><span class="hljs-number">.1</span>
</code></pre><p><strong>brotli</strong></p>
<pre><code>$ pip install brotli
Collecting brotli
  Downloading Brotli-<span class="hljs-number">1.0</span>.<span class="hljs-number">9</span>-cp38-cp38-manylinux1_x86_64.whl (<span class="hljs-number">357</span> kB)
     <span class="hljs-params">|████████████████████████████████|</span> <span class="hljs-number">357</span> kB 
Installing collected <span class="hljs-symbol">packages:</span> brotli
Successfully installed brotli-<span class="hljs-number">1.0</span>.<span class="hljs-number">9</span>
</code></pre><h2 id="download-notosans">Download NotoSans</h2>
<p>We test with NotoSans, a pretty heavy font, which makes the compressed sizes easy to compare.</p>
<p>Download the font from here: https://www.google.com/get/noto/ and decompress the contents.</p>
<p>We use the <code>python3</code> REPL to try this out:</p>
<pre><code>$ cd /path/NotoSans
$ python3
Python <span class="hljs-number">3.8</span>.<span class="hljs-number">5</span> (default, Jan <span class="hljs-number">27</span> <span class="hljs-number">2021</span>, <span class="hljs-number">15</span><span class="hljs-symbol">:</span><span class="hljs-number">41</span><span class="hljs-symbol">:</span><span class="hljs-number">15</span>)
[GCC <span class="hljs-number">9.3</span>.<span class="hljs-number">0</span>] on linux
Type <span class="hljs-string">"help"</span>, <span class="hljs-string">"copyright"</span>, <span class="hljs-string">"credits"</span> <span class="hljs-keyword">or</span> <span class="hljs-string">"license"</span> <span class="hljs-keyword">for</span> more information.
<span class="hljs-meta">&gt;&gt;</span>&gt; from fontTools.ttLib import TTFont
<span class="hljs-meta">&gt;&gt;</span>&gt; f = TTFont(<span class="hljs-string">'NotoSans-Regular.ttf'</span>)
<span class="hljs-meta">&gt;&gt;</span>&gt; f.flavor=<span class="hljs-string">'woff2'</span>
<span class="hljs-meta">&gt;&gt;</span>&gt; f.save(<span class="hljs-string">'NotoSans-Regular.woff2'</span>)
<span class="hljs-meta">&gt;&gt;</span>&gt; f.flavor=<span class="hljs-string">'woff'</span>
<span class="hljs-meta">&gt;&gt;</span>&gt; f.save(<span class="hljs-string">'NotoSans-Regular.woff'</span>)
<span class="hljs-meta">&gt;&gt;</span>&gt; exit()
</code></pre><p><strong>Verify converted files:</strong></p>
<pre><code><span class="hljs-attribute">ls</span> -alh
  <span class="hljs-attribute">rwxr</span>-xr-x    <span class="hljs-number">2</span>   manoj   manoj      <span class="hljs-number">4</span> KiB   Sat Mar <span class="hljs-number">13</span> <span class="hljs-number">13</span>:<span class="hljs-number">35</span>:<span class="hljs-number">12</span> <span class="hljs-number">2021</span>    ./
  <span class="hljs-attribute">rwxr</span>-xr-x   <span class="hljs-number">16</span>   manoj   manoj      <span class="hljs-number">4</span> KiB   Sat Mar <span class="hljs-number">13</span> <span class="hljs-number">14</span>:<span class="hljs-number">17</span>:<span class="hljs-number">12</span> <span class="hljs-number">2021</span>    ../
  <span class="hljs-attribute">rw</span>-r--r--    <span class="hljs-number">1</span>   manoj   manoj    <span class="hljs-number">444</span> KiB   Wed Sep <span class="hljs-number">20</span> <span class="hljs-number">17</span>:<span class="hljs-number">20</span>:<span class="hljs-number">30</span> <span class="hljs-number">2017</span>    NotoSans-Regular.ttf
  <span class="hljs-attribute">rw</span>-r--r--    <span class="hljs-number">1</span>   manoj   manoj    <span class="hljs-number">233</span> KiB   Sat Mar <span class="hljs-number">13</span> <span class="hljs-number">13</span>:<span class="hljs-number">35</span>:<span class="hljs-number">13</span> <span class="hljs-number">2021</span>    NotoSans-Regular.woff
  <span class="hljs-attribute">rw</span>-r--r--    <span class="hljs-number">1</span>   manoj   manoj    <span class="hljs-number">163</span> KiB   Sat Mar <span class="hljs-number">13</span> <span class="hljs-number">13</span>:<span class="hljs-number">34</span>:<span class="hljs-number">54</span> <span class="hljs-number">2021</span>    NotoSans-Regular.woff<span class="hljs-number">2</span>
</code></pre><p>As you see, woff2 provides the best compression for usage on the web, as long as you don't have to support IE. Check browser support here: https://caniuse.com/?search=woff2</p>
]]></content:encoded></item><item><title><![CDATA[Docker - Use docker-compose to override CMD]]></title><description><![CDATA[I've been dabbling with Docker to run services on my new  Raspberry Pi  and being a beginner at Docker myself, it was hard sometimes to debug why a docker container was failing as soon as it was started. Since a container "stays alive" only until its...]]></description><link>https://blog.manoj.ch/docker-use-docker-compose-to-override-cmd</link><guid isPermaLink="true">https://blog.manoj.ch/docker-use-docker-compose-to-override-cmd</guid><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Thu, 11 Mar 2021 16:00:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615478411724/O41Qjwxb4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been dabbling with Docker to run services on my new  <a target="_blank" href="https://blog.manoj.ch/setting-up-ubuntu-server-on-raspberry-pi4-with-boot-from-usb-ssd">Raspberry Pi</a>  and being a beginner at Docker myself, it was hard sometimes to debug why a docker container was failing as soon as it was started. Since a container "stays alive" only until its <code>CMD</code> process is running, there is no way to do a quick look-see inside the container if it has already Exited.</p>
<p>To change the <code>Dockerfile</code> to perform trial and error means that you will have to rebuild it again - which is time consuming.</p>
<p>One way to keep a container running without having to do this, is to override the <code>Dockerfile</code> command from the <code>docker-compose.yml</code> file.</p>
<p>For example:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis</span>
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">web:v001</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/opt/www</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">redis</span>
</code></pre>
<p>And in the <code>Dockerfile</code> you have a command:</p>
<pre><code class="lang-Dockerfile">FROM node:14-alpine

ENV APP_PATH /opt/www
RUN mkdir -p $APP_PATH

WORKDIR $APP_PATH

COPY package.json ./
COPY yarn.lock ./

RUN yarn

COPY . .

RUN yarn build

CMD yarn start

EXPOSE 3000
</code></pre>
<p>Anytime <code>CMD yarn start</code> fails, the container exits and is no more accessible for debugging.</p>
<p>Modify <code>docker-compose.yml</code> to include <code>command</code> override.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis</span>
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">web:v001</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"tail -f /dev/null"</span>     <span class="hljs-comment">#   &lt;--- &lt;--- &lt;---</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/opt/www</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">redis</span>
</code></pre>
<p>Now, when you start the container using <code>docker-compose up -d</code>, the container stays running.</p>
<p>Next, use <code>docker ps -a</code> to list all containers and obtain the hash for the container id.</p>
<p>Then, run <code>sh</code> within the container by: <code>docker exec -it &lt;sha&gt; sh</code>.</p>
<p>If everything goes well, you should see the shell prompt within the container:</p>
<pre><code class="lang-sh">/opt/www <span class="hljs-comment">#</span>
</code></pre>
<p>Now, you may debug for missing files, env vars, or even try to run <code>yarn start</code> and see why it is failing.</p>
]]></content:encoded></item><item><title><![CDATA[Raspberry Pi4 - Setup Ubuntu Server with boot from USB SSD]]></title><description><![CDATA[The Hardware
The Pi

The device is a Raspberry Pi 4B with 8GB RAM.
https://www.raspberrypi.org/products/raspberry-pi-4-model-b/
The case

owootec RetroFlag ResPi4. This case is an amazing combination of several things - the NES controllers to play Su...]]></description><link>https://blog.manoj.ch/raspberry-pi4-setup-ubuntu-server-with-boot-from-usb-ssd</link><guid isPermaLink="true">https://blog.manoj.ch/raspberry-pi4-setup-ubuntu-server-with-boot-from-usb-ssd</guid><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[Ubuntu]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Sun, 28 Feb 2021 14:28:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1614521498442/Vah9lDeOn.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="the-hardware">The Hardware</h2>
<h3 id="the-pi">The Pi</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614521575971/7TLxqkNbe.png" alt="image.png" /></p>
<p>The device is a Raspberry Pi 4B with 8GB RAM.</p>
<p>https://www.raspberrypi.org/products/raspberry-pi-4-model-b/</p>
<h3 id="the-case">The case</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614521625394/kmNmfSEVD.png" alt="image.png" /></p>
<p>owootec RetroFlag ResPi4. This case is an amazing combination of several things - the NES controllers to play Super Mario and Contra again (using RetroPie!). And also 
inbuilt interface for 2.5" SSD drive through USB 3.0. And a nifty case with a 32GB SD card and fan included. Highly recommend this one!</p>
<p>https://www.amazon.se/gp/product/B08FJ5BG52/</p>
<h3 id="the-ssd">The SSD</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614521675301/Jh_9Qx1FC.png" alt="image.png" /></p>
<p>Crucial CT240BX500SSD1. Although I was a little skeptical because not all SSDs are known to be compatible with the raspberry pi and I have had my share of trouble to get it to work!</p>
<p>https://www.amazon.se/gp/product/B07G3YNLJB/</p>
<h2 id="the-operating-system">The operating system</h2>
<p>The operating system included in the SD Card that comes with the NesPi4 case included the Raspbian OS (default OS for the raspberry pi).</p>
<p>If the card is empty or contains a different OS, you may choose to install Raspbian on it. I used my ASUS laptop which had a SD card reader for this.</p>
<h3 id="raspi-imager-tool">Raspi Imager Tool</h3>
<p>This step is needed if the SD card doesn't already have Raspbian OS.</p>
<p>Get the raspi-imager from https://www.raspberrypi.org/software/ and install it.</p>
<p>Insert the SD Card and run the tool. The interface is pretty straightforward. Choose OS (in our case Raspbian - 32 or 64 bit does not matter), choose device, and click on Write. The tool downloads the <em>prebuilt</em> OS image and writes it onto the card. It also takes care of formatting and partitioning the disk correctly.</p>
<h3 id="configure">Configure</h3>
<p>On first boot, the OS needs some setup. Choose the locale settings and connect to a network. Then choose a password and finish the rest of the setup wizard. Skip the reboot option.</p>
<p>Run the terminal application and:</p>
<p><code>sudo apt update -y</code></p>
<p><code>sudo apt full-upgrade</code></p>
<p>Once the update is complete:</p>
<p><code>sudo reboot</code></p>
<h3 id="verify">Verify</h3>
<p>After reboot, verify that there are no errors and you are able to see the SSD drive correctly:</p>
<p><code>df -h</code></p>
<p>OR</p>
<p><code>lsblk</code></p>
<p>You should be able to see two disk drives - one for the SD card and the other for the SSD disk.</p>
<h2 id="setting-up-the-ssd">Setting up the SSD</h2>
<p>We need to install the Ubuntu server image on the SSD and set it as the default boot option.</p>
<h3 id="install-raspi-imager">Install Raspi Imager</h3>
<p>We need the Raspi Imager tool again. This time, the SSD drive acts as the target for the OS flash.</p>
<p><code>sudo apt install raspi-imager</code></p>
<p>Run it: Applications -&gt; Accessories -&gt; Imager</p>
<p>Choose the OS, choose the SSD drive as target and click Write.</p>
<p>For the OS, choose <strong>Ubuntu Server 20.10</strong> - I faced multiple issues with the Ubuntu 20.04 LTS version of the image. Could be the combination of the SSD make/model or some driver files not working correctly (see note below).</p>
<h3 id="setup-boot-from-usb">Setup boot from USB</h3>
<p><code>sudo raspi-config</code></p>
<p>Choose <strong>Advanced Options</strong> --&gt; <strong>Boot order</strong> --&gt; Select primary boot as USB, fallback to SD card option.</p>
<p>Accept and restart.</p>
<blockquote>
<p>The device should now boot into Ubuntu Server 20.10.</p>
</blockquote>
<h3 id="user-setup">User setup</h3>
<p>The first start, the system prompts for login, use <code>ubuntu</code> for both login and password. It will complain that it is wrong, but it will auto-generate the password and present the prompt again.</p>
<p>Again, use <code>ubuntu</code> as login and password. This time, it will force to set a new password. Once the password is set, you will be able to login.</p>
<h3 id="configure-wifi">Configure Wifi</h3>
<p>Since there is no UI, it is a bit tricky to setup the Wifi. Edit the netplan file:</p>
<p><code>sudo vi /etc/netplan/50-cloud-init.yaml</code></p>
<p>Add the <code>wifis:</code> section as below:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">network:</span>
  <span class="hljs-attr">ethernets:</span>
    <span class="hljs-attr">eth0:</span>
      <span class="hljs-attr">dhcp4:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">match:</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bcmgenet</span> <span class="hljs-string">.....</span>
      <span class="hljs-attr">optional:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">set-name:</span> <span class="hljs-string">eth0</span>
  <span class="hljs-attr">version:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">wifis:</span>
    <span class="hljs-attr">wlan0:</span>
      <span class="hljs-attr">dhcp4:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">optional:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">access-points:</span>
        <span class="hljs-string">"SSID-NAME"</span>
          <span class="hljs-attr">password:</span> <span class="hljs-string">"PASSWORD"</span>
</code></pre>
<p>Save the file and apply changes:</p>
<p><code>sudo netplan apply</code></p>
<p>It will take some time for the network detection and connection to occur. Verify by:</p>
<p><code>ip a</code></p>
<blockquote>
<p>Run the command several times until <code>wlan0</code> status is <code>UP</code> and you see a local IP address assigned by DHCP.</p>
</blockquote>
<h3 id="update-system">Update system</h3>
<p>Run:</p>
<p><code>sudo apt update -y</code></p>
<p>and:</p>
<p><code>sudo apt full-upgrade</code></p>
<p><strong>Note:</strong> At the time of writing, the Ubuntu 20.04 LTS version failed to boot correctly multiple times, each time with a different issue - </p>
<ul>
<li>It failed to boot the first time, always defaulted to the SD card. When booting without the SD card, it failed to read the boot files correctly.</li>
<li>On rewriting the OS again, it failed with "USB failed to respond to voltage check!" - which was possibly because of a power issue, but I knew it wasn't because the SSD had booted correctly with Raspbian OS before.</li>
<li>Yet again, the SSD boot failed sometimes on the multicolor screen of death! Again attributed to power issues - I also tried a different power adapter, with no luck.</li>
<li>I did get it to boot finally, but this time, it failed to recognize the WiFi network interface (service error on startup).</li>
</ul>
<p>Finally, I decided to attribute it to the Ubuntu 20.04 OS image itself, and <em>reluctantly</em> tried the Ubuntu 20.10 version - which to my surprise worked like a breeze! No more issues.</p>
]]></content:encoded></item><item><title><![CDATA[TypeORM - use QueryBuilder for database seeding]]></title><description><![CDATA[TypeORM is a Object Relation Mapping library for TypeScript that allows for abstraction of your database operations in a clean way. It depicts tables as entity classes, provides for automatic synchronization with the database and supports migrations....]]></description><link>https://blog.manoj.ch/typeorm-use-querybuilder-for-database-seeding</link><guid isPermaLink="true">https://blog.manoj.ch/typeorm-use-querybuilder-for-database-seeding</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[orm]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Wed, 13 May 2020 20:29:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1612733390456/7r5yMj6Jz.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://typeorm.io">TypeORM</a> is a Object Relation Mapping library for TypeScript that allows for abstraction of your database operations in a clean way. It depicts tables as entity classes, provides for automatic synchronization with the database and supports migrations.</p>
<p>While most entity changes (data type, column names, etc) are handled effectively by <a target="_blank" href="https://typeorm.io/#/migrations/generating-migrations">generated migrations</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Database_seeding">database seeding</a> using TypeORM can be achieved using custom migrations.</p>
<p>In the official documentation for migrations, the examples show the use of raw queries and <a target="_blank" href="https://typeorm.io/#/migrations/using-migration-api-to-write-migrations">QueryRunner API</a> to create migrations, I noticed that the <code>QueryRunner</code>
instance also exposes the <code>createQueryBuilder()</code> method through the <code>connection</code> parameter. Therefore, you can use the same API that you would query the database from your application code to write the migration queries as well.</p>
<p>For instance,</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> seedTable1589221559012 <span class="hljs-keyword">implements</span> MigrationInterface {

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> up(queryRunner: QueryRunner): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
    <span class="hljs-keyword">await</span> queryRunner.connection
      .createQueryBuilder()
      .insert()
      .into(&lt;Entity&gt;)
      .values([
        {
          id: <span class="hljs-number">1</span>,
          column1: <span class="hljs-string">'abc'</span>,
          column2: <span class="hljs-number">123</span>
        },
        {
          id: <span class="hljs-number">2</span>,
          column1: <span class="hljs-string">'def'</span>,
          column2: <span class="hljs-number">456</span>
        }
      ])
      .execute();
  }

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> down(queryRunner: QueryRunner): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
    <span class="hljs-keyword">await</span> queryRunner.connection
      .createQueryBuilder()
      .delete()
      .from(&lt;Entity&gt;)
      .where(<span class="hljs-string">'id IN (0, 1)'</span>)
      .execute();
  }

}
</code></pre>
<p>This could help avoid having to write raw SQLs and enable type checking while inserting data.</p>
]]></content:encoded></item><item><title><![CDATA[envalid - validate your required environment variables]]></title><description><![CDATA[In the software world - thanks to people who create and maintain open source libraries - there aren't many problems that aren't already solved.
In my quest to manage required environment variables for my Node.js applications, I stumbled upon envalid ...]]></description><link>https://blog.manoj.ch/envalid-validate-your-required-environment-variables</link><guid isPermaLink="true">https://blog.manoj.ch/envalid-validate-your-required-environment-variables</guid><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Manoj Chandrashekar]]></dc:creator><pubDate>Sat, 18 Apr 2020 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1612733149237/KD7adq8-n.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612732537777/aWZTN2W7w.jpeg" alt="Environment Variables (bash)" /></p>
<p>In the software world - thanks to people who create and maintain open source libraries - there aren't many problems that aren't already solved.</p>
<p>In my quest to manage required environment variables for my Node.js applications, I stumbled upon <a target="_blank" href="https://www.npmjs.com/package/envalid">envalid</a> - a neat utility to ensure that all necessary variables are set before bootstrapping the application.</p>
<h4 id="manual-validation">Manual validation</h4>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> cachePath = process.env.CACHE_PATH;
<span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> cachePath === <span class="hljs-string">'string'</span>) {
  <span class="hljs-comment">// Perform other validations</span>
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Missing CACHE_PATH environment variable.'</span>);
  process.exit(<span class="hljs-number">1</span>);
}

<span class="hljs-comment">// Repeat for more variables</span>
</code></pre>
<h4 id="with-envalid">With envalid</h4>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> {cleanEnv, str} = <span class="hljs-built_in">require</span>(<span class="hljs-string">'envalid'</span>);

<span class="hljs-comment">// Verify required environment variables</span>
<span class="hljs-keyword">const</span> env = cleanEnv(process.env, {
  <span class="hljs-attr">CACHE_PATH</span>: str(),
});
</code></pre>
<h4 id="custom-validators">Custom validators</h4>
<p>Further, <code>envalid</code> supports custom validators to add additional checks.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { makeValidator } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'envalid'</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
<span class="hljs-keyword">const</span> {cleanEnv, str} = <span class="hljs-built_in">require</span>(<span class="hljs-string">'envalid'</span>);

<span class="hljs-keyword">const</span> exists = makeValidator(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> {
  <span class="hljs-comment">// Correct path according to current platform</span>
  <span class="hljs-keyword">const</span> normalized = path.normalize(x);
  <span class="hljs-keyword">if</span> (fs.existsSync(normalized)) {
    <span class="hljs-keyword">return</span> normalized;
  } <span class="hljs-keyword">else</span> { 
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Directory does not exist.'</span>);
  }
}, <span class="hljs-string">'exists'</span>);

<span class="hljs-keyword">const</span> env = cleanEnv(process.env, {
  <span class="hljs-attr">CACHE_PATH</span>: exists(),
});

<span class="hljs-built_in">console</span>.log(env.CACHE_PATH);
</code></pre>
<h4 id="further">Further</h4>
<p>If you require custom error reporting, you can <a target="_blank" href="https://www.npmjs.com/package/envalid#error-reporting">read more about it</a>.</p>
]]></content:encoded></item></channel></rss>