<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mahmud Ridwan (hjr265) on hjr265.me</title><link>https://hjr265.me/</link><description>Recent content in Mahmud Ridwan (hjr265) on hjr265.me</description><generator>Hugo</generator><language>en-us</language><managingEditor>m@hjr265.me (Mahmud Ridwan)</managingEditor><webMaster>m@hjr265.me (Mahmud Ridwan)</webMaster><lastBuildDate>Mon, 23 Mar 2026 00:00:00 +0600</lastBuildDate><atom:link href="https://hjr265.me/index.xml" rel="self" type="application/rss+xml"/><item><title>Papyrus</title><link>https://hjr265.me/projects/papyrus/</link><pubDate>Tue, 23 Feb 2016 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/papyrus/</guid><description>&lt;p&gt;Papyrus is a real-time collaborative Markdown editor and document repository with simple organization and project-based management. Built during GopherGala 2016, it features operational transformation for conflict-free concurrent editing.&lt;/p&gt;</description></item><item><title>Toph</title><link>https://hjr265.me/projects/toph/</link><pubDate>Wed, 01 Apr 2015 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/toph/</guid><description>&lt;p&gt;Toph is the leading competitive programming platform in Bangladesh. Top institutions have hosted all noteworthy Bangladeshi programming contests on Toph since 2015.&lt;/p&gt;</description></item><item><title>CodeMarshal</title><link>https://hjr265.me/projects/codemarshal/</link><pubDate>Wed, 01 Aug 2012 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/codemarshal/</guid><description>&lt;p&gt;CodeMarshal is a programming contest hosting platform. The platform successfully hosted the preliminary mock, preliminary, live, and semi-live contests of ACM ICPC 2014 Dhaka Regional.&lt;/p&gt;
&lt;p&gt;I built CodeMarshal while employed at Mukto Software under the leadership of then Chairman and CEO Mahmudur Rahman.&lt;/p&gt;</description></item><item><title>Redsync</title><link>https://hjr265.me/projects/redsync/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/redsync/</guid><description>&lt;p&gt;Redsync is a Redis-based distributed lock implementation in Go. It is also mentioned on the &lt;a href="https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/"&gt;official Redis website&lt;/a&gt; as a reference Go implementation of the Redlock algorithm.&lt;/p&gt;</description></item><item><title>Drafts</title><link>https://hjr265.me/projects/drafts/</link><pubDate>Wed, 01 Apr 2015 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/drafts/</guid><description>&lt;p&gt;Drafts is an all-in-one tool for designing and testing programming problems.&lt;/p&gt;</description></item><item><title>Bullet</title><link>https://hjr265.me/projects/bullet/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/bullet/</guid><description>&lt;p&gt;Bullet is a fast Twelve-Factor application deploy tool. Bullet deploys your application over SSH and runs it within Docker. You can configure the environment of your application by specifying per-process Dockerfiles/images.&lt;/p&gt;</description></item><item><title>Cactus</title><link>https://hjr265.me/projects/cactus/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/cactus/</guid><description>&lt;p&gt;Cactus is a modern, lightweight programming contest control system in a single binary. Deploying it is as simple as downloading its binary to a server on the local network. In some ways, Cactus is the precursor to Toph.&lt;/p&gt;</description></item><item><title>Node WHOIS</title><link>https://hjr265.me/projects/node-whois/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/node-whois/</guid><description>&lt;p&gt;Node WHOIS is a WHOIS client for Node.js.&lt;/p&gt;</description></item><item><title>Trace</title><link>https://hjr265.me/projects/trace/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/trace/</guid><description>&lt;p&gt;Trace is an interactive algorithm visualizer. It provides step-by-step animated visualizations for algorithms and data structures across graph theory, number theory, search, and sorting, with annotated source code that highlights in sync with each execution step.&lt;/p&gt;</description></item><item><title>μTools</title><link>https://hjr265.me/projects/utools/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/utools/</guid><description>&lt;p&gt;μTools is a developer utility suite built with Rust and GPUI. It provides quick access to commonly needed conversion and formatting tools, including Base64 and HTML encoding/decoding, Unix timestamp conversion, JSON formatting, text diffing, and more.&lt;/p&gt;</description></item><item><title>CodeMirror LanguageServer</title><link>https://hjr265.me/projects/codemirror-languageserver/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/codemirror-languageserver/</guid><description>&lt;p&gt;This is a CodeMirror 6 plugin to enable code completion, hover tooltips, and linter functionality by connecting the editor with a language server over WebSocket.&lt;/p&gt;</description></item><item><title>Loadcat</title><link>https://hjr265.me/projects/loadcat/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/loadcat/</guid><description>&lt;p&gt;Loadcat is an Nginx configurator that allows you to use Nginx as a load balancer.&lt;/p&gt;</description></item><item><title>GitTop</title><link>https://hjr265.me/projects/gittop/</link><pubDate>Sun, 01 Mar 2026 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/gittop/</guid><description>&lt;p&gt;GitTop is an htop-like TUI dashboard for Git repositories. It provides seven pages of insights: summary with braille charts, activity heatmaps, contributor rankings, branch comparisons, file statistics, release timelines, and a scrollable commit log with diff viewer and fuzzy search. It also includes a filter DSL for structured queries like &lt;code&gt;author:&amp;quot;alice&amp;quot; and path:*.go&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Printd</title><link>https://hjr265.me/projects/printd/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/printd/</guid><description>&lt;p&gt;Printd is a print server daemon for Toph contests. It runs on a computer connected to a printer and lets contest participants request physical prints directly from the platform, generating PDFs with customized headers before sending them to the printer.&lt;/p&gt;</description></item><item><title>Scanlib</title><link>https://hjr265.me/projects/scanlib/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/scanlib/</guid><description>&lt;p&gt;Scanlib is a metaparser that validates input files and generates input code in different programming languages.&lt;/p&gt;</description></item><item><title>Too</title><link>https://hjr265.me/projects/too/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/too/</guid><description>&lt;p&gt;Too is a collaborative memory-based recommendation engine implementation in Go built on top of Redis and simple set mathematics.&lt;/p&gt;</description></item><item><title>Brdgd</title><link>https://hjr265.me/projects/brdgd/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/brdgd/</guid><description>&lt;p&gt;Brdgd is a WebRTC-based file transfer tool.&lt;/p&gt;</description></item><item><title>Dots</title><link>https://hjr265.me/projects/dots/</link><pubDate>Sun, 01 Jan 2006 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/projects/dots/</guid><description>&lt;p&gt;Dots is a simple DNS toolkit.&lt;/p&gt;</description></item><item><title>Now</title><link>https://hjr265.me/now/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/now/</guid><description>&lt;p&gt;Software Engineer in Dhaka, Bangladesh 🇧🇩&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Family&lt;/li&gt;
&lt;li&gt;Software Engineer / CEO at &lt;a href="https://furqansoftware.com/"&gt;Furqan Software&lt;/a&gt; — Sep &amp;lsquo;15 - present
&lt;ul&gt;
&lt;li&gt;Spending as much time as I can crafting &lt;a href="https://toph.co/"&gt;Toph&lt;/a&gt;, the premier Bangladeshi sport programming platform&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Freelance software engineer
&lt;ul&gt;
&lt;li&gt;Networks: &lt;a href="https://www.toptal.com/resume/mahmud-ridwan/N8D73N/worlds-top-talent"&gt;Toptal&lt;/a&gt; and &lt;a href="https://www.upwork.com/freelancers/~01551cbdc32d9260be"&gt;Upwork&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Strong Suits: Go, TypeScript, JavaScript, Node.js, Rust, Python, React, Svelte, PostgreSQL, MongoDB, Redis, RabbitMQ, Docker, Terraform, Ansible&lt;/li&gt;
&lt;li&gt;Building a geospatial intelligence platform for a client, through Toptal&lt;/li&gt;
&lt;li&gt;Helping revamp a disability-focused social &amp;amp; activity application for a client, through Toptal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Maintaining &lt;a href="https://hjr265.me/projects/"&gt;open-source projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Reading &lt;a href="https://www.goodreads.com/en/book/show/42855446-begum-khaleda-zia"&gt;Begum Khaleda Zia: Her Life, Her Story&lt;/a&gt; by Mahfuz Ullah&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Can AI Take a Software Engineer's Job?</title><link>https://hjr265.me/blog/can-ai-take-a-software-engineers-job/</link><pubDate>Thu, 19 Mar 2026 20:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/can-ai-take-a-software-engineers-job/</guid><description>&lt;p&gt;I had an interesting exchange with a coding agent this evening.&lt;/p&gt;
&lt;p&gt;Are coding agents useful? Absolutely. But can it take a software engineer&amp;rsquo;s job? You tell me.&lt;/p&gt;
&lt;div class="box" open&gt;
	&lt;div class="box__body"&gt;
		&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; In &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, can we set the workflow name only if the &lt;code&gt;PLAYBOOK&lt;/code&gt; variable is set?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;workflow&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Playbook ${PLAYBOOK}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;if&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;$PLAYBOOK != &amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;when&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;workflow.name&lt;/code&gt; only takes effect when matched by a rule, so on push pipelines (where &lt;code&gt;$PLAYBOOK&lt;/code&gt; is empty) it falls through to &lt;code&gt;when: always&lt;/code&gt; which uses the default pipeline name. When &lt;code&gt;$PLAYBOOK&lt;/code&gt; is set, it shows e.g. &amp;ldquo;shape playbook&amp;rdquo;.&lt;/p&gt;</description></item><item><title>My First Fully Agentic Coding Project: GitTop</title><link>https://hjr265.me/blog/building-gittop-with-agentic-coding/</link><pubDate>Sat, 14 Mar 2026 14:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/building-gittop-with-agentic-coding/</guid><description>&lt;p&gt;I have been running &lt;a href="https://toph.co/"&gt;Toph&lt;/a&gt; for over ten years. Somewhere along the way, I started wondering: what hours of the day do I actually work on it?&lt;/p&gt;
&lt;p&gt;Commit timestamps felt like the right place to look. A quick one-off script could answer the question, and there are Git stats tools that spit out HTML reports. But I thought: this could be a good excuse to build a TUI application. Something like htop, but for a Git repository instead of system metrics.&lt;/p&gt;</description></item><item><title>Resurrecting a 12-Year-Old Node.js Project With Claude Code</title><link>https://hjr265.me/blog/resurrecting-12-year-old-node-project-with-claude/</link><pubDate>Sat, 07 Mar 2026 17:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/resurrecting-12-year-old-node-project-with-claude/</guid><description>&lt;p&gt;This weekend, I set out to get some high-resolution screenshots of zebra-algo (you may remember it as the now-defunct &lt;a href="https://hjr265.me/projects/#codemarshal"&gt;CodeMarshal&lt;/a&gt; or algo.codemarshal.org). My motivation was to document its design and features, as zebra-algo is a competitive programming contest platform I built back in 2014.&lt;/p&gt;
&lt;p&gt;The plan was straightforward: run it locally, open it in a browser, take the screenshots, and be done.&lt;/p&gt;
&lt;p&gt;Simple, except the codebase is twelve years old, making things tricky.&lt;/p&gt;</description></item><item><title>Ditching MongoDB Text Indexes for Edge N-Grams</title><link>https://hjr265.me/blog/ditching-mongodb-text-indexes-for-edge-n-grams/</link><pubDate>Mon, 02 Mar 2026 11:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/ditching-mongodb-text-indexes-for-edge-n-grams/</guid><description>&lt;p&gt;I like software that just works. If I type &amp;ldquo;North South&amp;rdquo; into a search box, I expect to find &amp;ldquo;North South University&amp;rdquo;. But if I type &amp;ldquo;North So&amp;rdquo;, I should still find it. Maybe not at the top, but it should be there.&lt;/p&gt;
&lt;p&gt;For a while, Toph&amp;rsquo;s institution search did not work that way.&lt;/p&gt;
&lt;h2 id="the-problem-with-mongodb-text-indexes"&gt;
	The Problem With MongoDB Text Indexes
	
	&lt;a class="hlink" href="#the-problem-with-mongodb-text-indexes"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The original implementation used a MongoDB text index on the &lt;code&gt;name&lt;/code&gt; field. It is the obvious first choice. MongoDB makes it easy to set up, and for simple use cases, it works fine.&lt;/p&gt;</description></item><item><title>Strangest AMD Ryzen 7950x Bug</title><link>https://hjr265.me/blog/strangest-amd-7950x-bug/</link><pubDate>Sat, 08 Mar 2025 19:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/strangest-amd-7950x-bug/</guid><description>&lt;p&gt;I upgraded my primary computer to the AMD AM5 platform sometime in mid-2023. Before the upgrade, I had been using an Intel Core i7 4790 with 32 GB of RAM for about a decade. For the upgrade, I went with an AMD Ryzen 7950x with 128 GB of RAM. Given that my work lately has started to involve a lot of virtual machines, this upgrade was worth every bit.&lt;/p&gt;
&lt;p&gt;However, the upgrade came with a few annoyances. One of them, and the worst one, in my opinion, was random reboots.&lt;/p&gt;</description></item><item><title>All Our Customer Care Agents Are Busy at This Moment</title><link>https://hjr265.me/blog/all-our-customer-care-agents-are-busy-at-this-moment/</link><pubDate>Thu, 19 Sep 2024 19:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/all-our-customer-care-agents-are-busy-at-this-moment/</guid><description>&lt;p&gt;It was the second week of August 2024. We realized that the television in our family space had suddenly stopped working. It would start, flash blue for a moment, and restart.&lt;/p&gt;
&lt;p&gt;If we left it running for a while, the boot logo would appear with parts of the panel garbled.&lt;/p&gt;
&lt;p&gt;This was no cheap, no-name, no-brand &amp;ldquo;smart&amp;rdquo; TV—at least that is what the salesperson told us when we bought it. It was a well-known brand.&lt;/p&gt;</description></item><item><title>How Hard Can It Be: Use JavaScript to Close Web Browser Window After Print</title><link>https://hjr265.me/blog/web-browser-javascript-how-hard-can-it-be-close-window-after-print/</link><pubDate>Thu, 09 May 2024 12:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/web-browser-javascript-how-hard-can-it-be-close-window-after-print/</guid><description>&lt;p&gt;So here is a &lt;em&gt;simple&lt;/em&gt; JavaScript task I had to tackle for &lt;a href="https://toph.co"&gt;Toph&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;When a user clicks the Print button, open a new tab/window and activate the print dialog. Close the window when the user confirms the print or cancels the dialog.&lt;/p&gt;
&lt;h2 id="attempt-1-call-windowclose-immediately-after-windowprint"&gt;
	Attempt 1: Call &lt;code&gt;window.close()&lt;/code&gt; Immediately After &lt;code&gt;window.print()&lt;/code&gt;
	
	&lt;a class="hlink" href="#attempt-1-call-windowclose-immediately-after-windowprint"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I used the &lt;code&gt;onclick&lt;/code&gt; attribute on the Print button to open the page to be printed on a new tab/window.&lt;/p&gt;</description></item><item><title>Remmina SPICE SSH Tunnel Bug and a Workaround</title><link>https://hjr265.me/blog/remmina-spice-ssh-tunnel-bug-and-a-workaround/</link><pubDate>Mon, 15 Jan 2024 19:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/remmina-spice-ssh-tunnel-bug-and-a-workaround/</guid><description>&lt;p&gt;I seem to come across the strangest of bugs.&lt;/p&gt;
&lt;h2 id="bug"&gt;
	Bug
	
	&lt;a class="hlink" href="#bug"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Remmina SPICE over an SSH tunnel fails to handle keyboard-mouse interactions.&lt;/p&gt;
&lt;p&gt;I have set up several virtual machines with desktop operating systems using &lt;a href="https://libvirt.org/"&gt;Libvirt&lt;/a&gt; on my primary computer. I can access these virtual machines over the network using Libvirt on my laptop. However, I wanted to use &lt;a href="https://www.remmina.org/"&gt;Remmina&lt;/a&gt; as the SPICE client since it is more configurable.&lt;/p&gt;
&lt;p&gt;All worked well until I tried to access a Manjaro Gnome virtual machine with the SSH tunnel feature on Remmina.&lt;/p&gt;</description></item><item><title>#100DaysToOffload Milestone: The 100th Blog Post</title><link>https://hjr265.me/blog/100daystooffload-milestone-100th-blog-post/</link><pubDate>Thu, 23 Nov 2023 14:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/100daystooffload-milestone-100th-blog-post/</guid><description>&lt;p&gt;And one hundred.&lt;/p&gt;
&lt;p&gt;I have posted &lt;a href="https://hjr265.me/tags/100daystooffload/"&gt;100 blog posts&lt;/a&gt; in the last 365 days.&lt;/p&gt;
&lt;p&gt;Why? Because I took on this &lt;a href="https://100daystooffload.com/"&gt;#100DaysToOffload&lt;/a&gt; Internet challenge.&lt;/p&gt;
&lt;p&gt;My brain, trained on decades of video games, is tuned to enjoy challenges, no questions asked.&lt;/p&gt;
&lt;p&gt;But this challenge is very different from those social media challenges that die as fast as they become popular. How?&lt;/p&gt;
&lt;p&gt;First, the #100DaysToOffload challenge is not as popular. At least, it is not as popular as it should be.&lt;/p&gt;</description></item><item><title>Android Emulator Slow As a Snail; Reason BTRFS</title><link>https://hjr265.me/blog/android-emulator-slow-as-a-snail-reason-btrfs/</link><pubDate>Thu, 23 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/android-emulator-slow-as-a-snail-reason-btrfs/</guid><description>&lt;p&gt;I have been writing software professionally for over a decade. I have been writing software for even longer than that.&lt;/p&gt;
&lt;p&gt;This week was the first time I wrote an Android program. Some journey it was. But that is a story for another day.&lt;/p&gt;
&lt;p&gt;Today, in this blog post, I want to share a strange issue I encountered with Android Emulator and a fix.&lt;/p&gt;
&lt;p&gt;If you are using BTRFS, you probably have copy-on-write (COW) enabled for the files on it. If your Android Emulator boot cache is on a BTRFS partition, you will notice the emulator running as slow as a snail.&lt;/p&gt;</description></item><item><title>Check If a WireGuard Connection Is Up</title><link>https://hjr265.me/blog/check-if-a-wireguard-connection-is-up/</link><pubDate>Tue, 21 Nov 2023 15:55:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/check-if-a-wireguard-connection-is-up/</guid><description>&lt;p&gt;I have several scripts and automation on my primary computer at home that can run when connected to the local area network of my workspace through a WireGuard connection.&lt;/p&gt;
&lt;p&gt;These scripts are for routine tasks for my servers at my workspace, like backing them up to remote storage.&lt;/p&gt;
&lt;p&gt;When the WireGuard connection is not running, the scripts fail at different points.&lt;/p&gt;
&lt;p&gt;I wanted the scripts to fail right when they start if the WireGuard connection is not running.&lt;/p&gt;</description></item><item><title>Scanning a Website for Broken Links in Go</title><link>https://hjr265.me/blog/scanning-a-website-for-broken-links-in-go/</link><pubDate>Mon, 20 Nov 2023 23:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/scanning-a-website-for-broken-links-in-go/</guid><description>&lt;p&gt;Yes, I know there are paid and free tools for doing this. And yes, I know there are tools for this that I can run locally.&lt;/p&gt;
&lt;p&gt;But this exercise allowed me to try out the well-designed Go package &lt;a href="https://pkg.go.dev/github.com/gocolly/colly"&gt;github.com/gocolly/colly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Colly is a web scraping framework for Go.&lt;/p&gt;
&lt;p&gt;Here is how I used it to quickly scan my website (the one you are on right now) for broken links.&lt;/p&gt;
&lt;p&gt;First I defined a type for links to check and the URL of the page they appear on:&lt;/p&gt;</description></item><item><title>When Was the Last Time Technology Blew Your Mind?</title><link>https://hjr265.me/blog/when-was-the-last-time-technology-blew-your-mind/</link><pubDate>Sun, 19 Nov 2023 16:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/when-was-the-last-time-technology-blew-your-mind/</guid><description>&lt;p&gt;I read a blog post by Kev Quirk this morning.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kevquirk.com/when-was-the-last-time-tech-blew-your-mind"&gt;When Was the Last Time Tech Blew Your Mind?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was a refreshing read.&lt;/p&gt;
&lt;p&gt;The world has come a long way as far as technology is concerned. But I think somewhere along the lines, the definition of innovation changed.&lt;/p&gt;
&lt;p&gt;I upgraded my desktop computer a couple of months ago.&lt;/p&gt;
&lt;p&gt;Before the upgrade, I used an Intel CPU from 2014 with as much RAM as the platform could handle. It was working fine. But I needed a few extra CPU cores and more RAM. So, I got a top-of-the-line AMD Ryzen CPU.&lt;/p&gt;</description></item><item><title>Go Web Server for Remotely Powering on a Desktop Computer With a Raspberry Pi</title><link>https://hjr265.me/blog/go-web-server-for-remotely-powering-on-a-desktop-computer-with-a-raspberry-pi/</link><pubDate>Sat, 18 Nov 2023 16:40:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-web-server-for-remotely-powering-on-a-desktop-computer-with-a-raspberry-pi/</guid><description>&lt;p&gt;Last month, I wrote a blog post on how to use a Raspberry Pi and a 5V 2-channel relay to remotely power on or reset a desktop computer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hjr265.me/blog/powering-on-a-desktop-computer-remotely-with-a-raspberry-pi/"&gt;Powering on a Desktop Computer Remotely With a Raspberry Pi&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To keep the blog post simple, I used the &lt;code&gt;gpio&lt;/code&gt; command to interact with the GPIO pins on the Raspberry Pi.&lt;/p&gt;
&lt;p&gt;That works well. But by deploying a little web server you can have an easier user interface to power on or reset your desktop computer remotely.&lt;/p&gt;</description></item><item><title>7 Useful io.Reader and io.Writer Wrappers in Go</title><link>https://hjr265.me/blog/7-useful-io-reader-and-io-writer-wrappers-in-go/</link><pubDate>Fri, 17 Nov 2023 09:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/7-useful-io-reader-and-io-writer-wrappers-in-go/</guid><description>&lt;p&gt;Whenever I think about Go in comparison to other programming languages, the first thing that comes to my mind is how Go simplifies the concepts around concurrency and writing concurrent programs.&lt;/p&gt;
&lt;p&gt;After all, concurrency is one of the features touted on the homepage:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Built-in concurrency and a robust standard library&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But there is something else that Go simplifies and makes it very easy to wrap one&amp;rsquo;s head around.&lt;/p&gt;
&lt;p&gt;The type system.&lt;/p&gt;</description></item><item><title>Forwarding a Port Over SSH in Go</title><link>https://hjr265.me/blog/forwarding-a-port-over-ssh-in-go/</link><pubDate>Thu, 16 Nov 2023 12:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/forwarding-a-port-over-ssh-in-go/</guid><description>&lt;p&gt;In the day and age where Kubernetes is the go-to tool for orchestrating your applications on the cloud, I have been spending time building &lt;a href="https://github.com/FurqanSoftware/bullet"&gt;Bullet&lt;/a&gt;. I enjoy working on this tool, building out features for it little by little. And it also allows me to learn so many details.&lt;/p&gt;
&lt;p&gt;For example I just added the ability to forward ports from the remote server to the local over SSH. Bullet, being built using Go, I had to figure out how to forward a port over SSH in Go.&lt;/p&gt;</description></item><item><title>Strange Hugo Bug and How to Work Around It</title><link>https://hjr265.me/blog/strange-hugo-bug-and-how-to-work-around-it/</link><pubDate>Wed, 15 Nov 2023 10:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/strange-hugo-bug-and-how-to-work-around-it/</guid><description>&lt;p&gt;I was about to deploy my site with the latest blog post this morning and found Hugo broken. Or, my Hugo &lt;code&gt;config.toml&lt;/code&gt; is broken. It depends on how you want to look at it.&lt;/p&gt;
&lt;p&gt;I ran &lt;code&gt;hugo deploy&lt;/code&gt; and bam! I see an internal template error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ERROR render of &amp;#34;taxonomy&amp;#34; failed: template: _internal/_default/rss.xml:3:9: executing &amp;#34;_internal/_default/rss.xml&amp;#34; at &amp;lt;site&amp;gt;: can&amp;#39;t evaluate field email in type string
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;According to &lt;code&gt;pacman&lt;/code&gt;, the package manager on Arch Linux, I received an update for Hugo yesterday.&lt;/p&gt;</description></item><item><title>Serving JSON in Go with http.ServeContent</title><link>https://hjr265.me/blog/serving-json-in-go-with-http-servecontent/</link><pubDate>Wed, 15 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/serving-json-in-go-with-http-servecontent/</guid><description>&lt;p&gt;I know many will start with something like &lt;a href="https://gin-gonic.com/"&gt;Gin&lt;/a&gt; whenever they are working on a JSON/HTTP-based backend in Go.&lt;/p&gt;
&lt;p&gt;I, not entirely sure if the minority, try to stick to Go&amp;rsquo;s built-in &lt;code&gt;net/http&lt;/code&gt; package and, at most, use &lt;a href="https://pkg.go.dev/github.com/gorilla/mux"&gt;Gorilla Mux&lt;/a&gt; in most of my Go projects.&lt;/p&gt;
&lt;p&gt;And so serving something simple like JSON is no different from the package&amp;rsquo;s point of view as any other content type: whatever it is, write it out to the &lt;code&gt;w&lt;/code&gt;, the &lt;code&gt;http.ResponseWriter&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Editing an SVG Icon to Be Resize-friendly</title><link>https://hjr265.me/blog/editing-an-svg-icon-to-be-resize-friendly/</link><pubDate>Tue, 14 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/editing-an-svg-icon-to-be-resize-friendly/</guid><description>&lt;p&gt;This blog post is about something that I think I have absolutely no expertise in: graphics. But that also makes exploring this field and the aha moments much more rewarding.&lt;/p&gt;
&lt;p&gt;A few weeks ago, I was working on adding an icon to the &lt;a href="https://github.com/FurqanSoftware/toph-printd"&gt;Toph Printd&lt;/a&gt; executable. I started to look for a suitable vector image on a paid vector icon and sticker repository I subscribe to.&lt;/p&gt;
&lt;p&gt;I found one that I liked.&lt;/p&gt;</description></item><item><title>Testing a Go Package That Depends on Redis</title><link>https://hjr265.me/blog/testing-a-go-package-that-depends-on-redis/</link><pubDate>Mon, 13 Nov 2023 13:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/testing-a-go-package-that-depends-on-redis/</guid><description>&lt;p&gt;Redsync, one of my open-source Go packages, implements a distributed lock using Redis. It is an implementation of the &lt;a href="https://redis.io/docs/manual/patterns/distributed-locks/"&gt;Redlock algorithm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This Go package has tests that run against multiple real Redis servers. And it is an example of how you can use the &lt;code&gt;TestMain&lt;/code&gt; function to customize your Go tests.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://pkg.go.dev/testing#hdr-Main"&gt;&lt;code&gt;TestMain&lt;/code&gt; function&lt;/a&gt;, if defined in your Go tests, will allow you to run the Go code before and after the actual tests, which is really ideal for setup and teardown work.&lt;/p&gt;</description></item><item><title>Tracking io.Copy Progress in Go</title><link>https://hjr265.me/blog/tracking-io-copy-progress-in-go/</link><pubDate>Sun, 12 Nov 2023 09:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/tracking-io-copy-progress-in-go/</guid><description>&lt;p&gt;If you are writing Go code for any period, you must have used the &lt;code&gt;io.Copy&lt;/code&gt; function. It takes an &lt;code&gt;io.Writer&lt;/code&gt; and an &lt;code&gt;io.Reader&lt;/code&gt; and copies everything from the reader to the writer until it reaches the end of file (EOF).&lt;/p&gt;
&lt;p&gt;The function returns the number of bytes copied and an error (if any, other than &lt;code&gt;io.EOF&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;But this function blocks until the copy completes. How do you track the progress of &lt;code&gt;io.Copy&lt;/code&gt;?&lt;/p&gt;</description></item><item><title>Responsive Activity Chart With Chart.js</title><link>https://hjr265.me/blog/responsive-activity-chart-with-chart-js/</link><pubDate>Sat, 11 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/responsive-activity-chart-with-chart-js/</guid><description>&lt;p&gt;A few weeks ago I worked on improving the &lt;a href="https://blog.toph.co/changelog/2023-10-06-improved-activity-chart/"&gt;activity chart shown on Toph profiles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Among adding legends and tweaking the look and feel of the chart, I paid some attention to how the chart behaves in terms of responsiveness.&lt;/p&gt;
&lt;p&gt;Chart.js has built-in responsive features. But in this case, it meant the chart would scale as a whole. What I wanted instead is for the number of columns to increase/decrease depending on the space available.&lt;/p&gt;</description></item><item><title>Multi-threaded Downloads in Go</title><link>https://hjr265.me/blog/multi-threaded-downloads-in-go/</link><pubDate>Thu, 09 Nov 2023 14:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/multi-threaded-downloads-in-go/</guid><description>&lt;p&gt;The word multi-threaded here is an artifact of how download managers in the past worked.&lt;/p&gt;
&lt;p&gt;The idea is to download a large file in parts, in parallel, over multiple TCP streams at once. In certain circumstances this can speed up the download significantly.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with a naive way of downloading a file in Go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Error handling omitted for brevity.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Perform a GET request.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Create the output file.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;output.ext&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Copy from the response body to the file.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Copy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The code above is downloading the entire file in a single stream.&lt;/p&gt;</description></item><item><title>Building Advanced Search With Go and MongoDB</title><link>https://hjr265.me/blog/building-advanced-search-with-go-and-mongodb/</link><pubDate>Tue, 07 Nov 2023 13:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/building-advanced-search-with-go-and-mongodb/</guid><description>&lt;p&gt;I like software that allows advanced search. Advanced search is where you can use flags to indicate what you want.&lt;/p&gt;
&lt;p&gt;Take email software as an example.&lt;/p&gt;
&lt;p&gt;It may allow you to enter &lt;code&gt;from:Bloo to:Mac subject:Imagination&lt;/code&gt; and find all emails that were sent from Bloo to Mac and has the word &amp;ldquo;Imagination&amp;rdquo; in the subject line.&lt;/p&gt;
&lt;p&gt;But how do you implement something like this in Go? That is what this blog post is about.&lt;/p&gt;</description></item><item><title>Backing up Self-hosted GitLab With Ansible</title><link>https://hjr265.me/blog/backing-up-self-hosted-gitlab-with-ansible/</link><pubDate>Mon, 06 Nov 2023 12:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/backing-up-self-hosted-gitlab-with-ansible/</guid><description>&lt;p&gt;I have been making software for over a decade now. And one thing I have learned to love through this is automation.&lt;/p&gt;
&lt;p&gt;After all, it is only a programmer who will spend hours automating a task that takes a few minutes to do.&lt;/p&gt;
&lt;p&gt;There are good reasons for this.&lt;/p&gt;
&lt;p&gt;I have a self-hosted GitLab instance for &lt;a href="https://furqansoftware.com"&gt;Furqan Software&lt;/a&gt;. And if you are self-hosting tools and services, the critical thing to do after ensuring security is automating backups.&lt;/p&gt;</description></item><item><title>JavaScript window.close Won't Close the Window</title><link>https://hjr265.me/blog/javascript-window-close-wont-close-the-window/</link><pubDate>Sat, 04 Nov 2023 19:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/javascript-window-close-wont-close-the-window/</guid><description>&lt;p&gt;I learned something new today.&lt;/p&gt;
&lt;p&gt;In JavaScript within the web browser, &lt;code&gt;window.close&lt;/code&gt; will not close the window if it was not opened using &lt;code&gt;window.open&lt;/code&gt; or is a top-level window (or tab) with at one history entry. That is what the &lt;a href="(https://developer.mozilla.org/en-US/docs/Web/API/Window/close)"&gt;documentation of &lt;code&gt;window.close&lt;/code&gt;&lt;/a&gt; says.&lt;/p&gt;
&lt;p&gt;It got in the way.&lt;/p&gt;
&lt;p&gt;I was adding a page endpoint that I would link to. The link would open the page in a new tab with &lt;code&gt;target=&amp;quot;_blank&amp;quot;&lt;/code&gt;. And it would call &lt;code&gt;window.print&lt;/code&gt; and &lt;code&gt;window.close&lt;/code&gt; back to back.&lt;/p&gt;</description></item><item><title>Adding Anchor Links Next to Headings in Hugo</title><link>https://hjr265.me/blog/adding-anchor-links-next-to-headings-in-hugo/</link><pubDate>Fri, 03 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/adding-anchor-links-next-to-headings-in-hugo/</guid><description>&lt;p&gt;I like that the Markdown renderer in Hugo automatically adds an &lt;code&gt;id&lt;/code&gt; attribute to the headings in the content.&lt;/p&gt;
&lt;p&gt;This allows you to link to a specific section in a long article.&lt;/p&gt;
&lt;p&gt;But, I wanted to make it easy for people to get that link.&lt;/p&gt;
&lt;p&gt;Hugo doesn&amp;rsquo;t do that by default, but makes it very easy to do with Markdown render hooks.&lt;/p&gt;
&lt;p&gt;By using the following as the render hook for headings, I am able to show a small link icon next to the headings in my blog posts:&lt;/p&gt;</description></item><item><title>Setting Up Prometheus DNS-SRV Discovery with Terraform and DNSimple</title><link>https://hjr265.me/blog/setting-up-prometheus-dns-srv-discovery-with-terraform-and-dnsimple/</link><pubDate>Thu, 02 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/setting-up-prometheus-dns-srv-discovery-with-terraform-and-dnsimple/</guid><description>&lt;p&gt;If you are using Prometheus to collect metrics from your server, and you don&amp;rsquo;t have a static set of servers, then you should set up automated discovery.&lt;/p&gt;
&lt;p&gt;There are many ways you can set up automated discovery in Prometheus. However, one of my preferred vendor-agnostic ways of doing this is to use DNS-SRV records.&lt;/p&gt;
&lt;p&gt;How does it work?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say you are using Terraform to manage your infrastructure.&lt;/p&gt;
&lt;p&gt;You will be creating a DNS-SRV record for each of your servers. As you create or destroy servers, these records will be created and destroyed.&lt;/p&gt;</description></item><item><title>Waiting for an HTTP Service in GitLab CI/CD</title><link>https://hjr265.me/blog/waiting-for-an-http-service-in-gitlab-ci-cd/</link><pubDate>Wed, 01 Nov 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/waiting-for-an-http-service-in-gitlab-ci-cd/</guid><description>&lt;p&gt;Last weekend, I was setting up a Cypress test pipeline in GitLab for Toph for the 5th time.&lt;/p&gt;
&lt;p&gt;I have no idea why this pipeline keeps breaking over time. It&amp;rsquo;s like bread left in the open. Cypress is such a fantastic end-to-end testing tool. But it seems to need a lot of &lt;em&gt;extra love&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Something that I needed to do was, in the CI/CD script, wait for Toph to start handling HTTP requests before starting Cypress.&lt;/p&gt;</description></item><item><title>SOCKS Proxy Over SSH</title><link>https://hjr265.me/blog/socks-proxy-over-ssh/</link><pubDate>Tue, 31 Oct 2023 13:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/socks-proxy-over-ssh/</guid><description>&lt;p&gt;To test some of Toph&amp;rsquo;s IP-based access control features, I needed to access it from a few different IP addresses than mine.&lt;/p&gt;
&lt;p&gt;I thought I finally needed to get one of those VPN subscriptions YouTube content creators keep rambling about.&lt;/p&gt;
&lt;p&gt;Fortunately, I remembered an easier way to do this.&lt;/p&gt;
&lt;p&gt;You see, it is possible to run a SOCKS proxy that tunnels your connection over SSH. And it is built right into the &lt;code&gt;ssh&lt;/code&gt; command:&lt;/p&gt;</description></item><item><title>Are There Three Types of #100DaysToOffload Challengers?</title><link>https://hjr265.me/blog/are-there-three-types-of-100daystooffload-challengers/</link><pubDate>Tue, 31 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/are-there-three-types-of-100daystooffload-challengers/</guid><description>&lt;p&gt;A couple of weeks ago, I wrote &lt;a href="https://hjr265.me/blog/100daystooffload-milestone-67th-blog-post/"&gt;my second #100DaysToOffload milestone blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I mentioned how I feel like a remnant characteristic of my days in the university has played a role in taking this challenge. I have been writing more frequently as I am nearing the end of the challenge.&lt;/p&gt;
&lt;p&gt;It got me wondering: are there three types of #100DaysToOffload challengers?&lt;/p&gt;
&lt;p&gt;I took a quick look at some of the recent entries in the &lt;a href="https://100daystooffload.com/#-hall-of-fame-"&gt;hall of fame&lt;/a&gt; and analyzed how the publication dates of the blog posts were spread out over a year of the challenge by these other challengers.&lt;/p&gt;</description></item><item><title>Uploading Files Over SSH in Go</title><link>https://hjr265.me/blog/uploading-files-over-ssh-in-go/</link><pubDate>Sun, 29 Oct 2023 14:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/uploading-files-over-ssh-in-go/</guid><description>&lt;p&gt;If you access servers remotely over SSH connections, you are bound to have come across &lt;code&gt;scp&lt;/code&gt;. It is what you use to upload files to these remote servers.&lt;/p&gt;
&lt;p&gt;If you want to programmatically upload files like &lt;code&gt;scp&lt;/code&gt; over an SSH connection to a remote server using Go, then you can use an &lt;a href="https://pkg.go.dev/golang.org/x/crypto/ssh"&gt;&lt;code&gt;ssh.Client&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Error handling omitted for brevity.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uploadFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;sshClient&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ssh&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;filename&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;mode&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;FileMode&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;size&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int64&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Reader&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Set up a new SSH session.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sess&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sshClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewSession&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sess&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Write the file&amp;#39;s metadata and contents to the stdin pipe.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sess&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StdinPipe&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Write &amp;#34;C{mode} {size} {filename}\n&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fprintf&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;C%#o %d %s\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;mode&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;size&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Base&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;filename&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Write the file&amp;#39;s contents.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Copy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// End with a null byte.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fprint&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;\x00&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sess&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Stdout&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Stdout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sess&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Stderr&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Run `scp -t {filename}` on the server-side.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sess&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Run&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;scp -t %s&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;filename&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can use this function like so:&lt;/p&gt;</description></item><item><title>Steam Link Black Screen on a Retro Pie Raspberry Pi 4</title><link>https://hjr265.me/blog/steam-link-black-screen-on-a-retro-pie-raspberry-pi-4/</link><pubDate>Fri, 27 Oct 2023 08:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/steam-link-black-screen-on-a-retro-pie-raspberry-pi-4/</guid><description>&lt;p&gt;Last night, I encountered a strange issue setting up Steam Link on a Raspberry Pi running Retro Pie.&lt;/p&gt;
&lt;p&gt;Here is my attempt at a proper description of what I was seeing.&lt;/p&gt;
&lt;p&gt;After installing Steam Link using Retro Pie&amp;rsquo;s package manager and rebooting the Raspberry Pi, I could see &amp;ldquo;Ports&amp;rdquo; show up on the Emulation Station. Inside was &amp;ldquo;Steam Link&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I could start Steam Link just fine, have it test my network, and then pair it with Steam running on my gaming virtual machine.&lt;/p&gt;</description></item><item><title>Synchronization Constructs in the Go Standard Library</title><link>https://hjr265.me/blog/synchronization-constructs-in-go-standard-library/</link><pubDate>Thu, 26 Oct 2023 15:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/synchronization-constructs-in-go-standard-library/</guid><description>&lt;p&gt;Go provides &lt;code&gt;sync.Mutex&lt;/code&gt; as its implementation of a mutual exclusion lock. However, it is not the only synchronization construct that is a part of the standard library.&lt;/p&gt;
&lt;p&gt;This blog post will look at four synchronization constructs that we can use instead of a &lt;a href="https://pkg.go.dev/sync#Mutex"&gt;&lt;code&gt;sync.Mutex&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="counter"&gt;
	Counter
	
	&lt;a class="hlink" href="#counter"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;You may often see code using a &lt;code&gt;sync.Mutex&lt;/code&gt; to synchronize access to a counter variable from multiple goroutines.&lt;/p&gt;
&lt;p&gt;Like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Mutex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &amp;lt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Elsewhere&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Instead of this, you can use an &lt;a href="https://pkg.go.dev/sync/atomic#Int32"&gt;&lt;code&gt;atomic.Int32&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://pkg.go.dev/sync/atomic#Int64"&gt;&lt;code&gt;atomic.Int64&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;</description></item><item><title>Powering on a Desktop Computer Remotely With a Raspberry Pi</title><link>https://hjr265.me/blog/powering-on-a-desktop-computer-remotely-with-a-raspberry-pi/</link><pubDate>Wed, 25 Oct 2023 20:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/powering-on-a-desktop-computer-remotely-with-a-raspberry-pi/</guid><description>&lt;p&gt;I access my primary computer remotely for various reasons.&lt;/p&gt;
&lt;p&gt;Take playing video games using Steam Remote Play, for example. I have a Windows virtual machine with a GPU passed through. With Steam running on it, I can connect from my phone, my laptop, or any device with the Steam Link app and play remotely. For games that are not on Steam, I can use Moonlight.&lt;/p&gt;
&lt;p&gt;But imagine the frustration when the computer is not powered on, and I am not at home. Only a fellow video gamer may understand it.&lt;/p&gt;</description></item><item><title>Serving hjr265.me From an S3-like Bucket Using a Caddy Module</title><link>https://hjr265.me/blog/serving-hjr265-me-from-an-s3-like-bucket-using-a-caddy-module/</link><pubDate>Tue, 24 Oct 2023 15:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/serving-hjr265-me-from-an-s3-like-bucket-using-a-caddy-module/</guid><description>&lt;p&gt;I serve hjr265.me from an S3-like bucket hosted on Linode Object Storage. I have a Caddy instance that serves some of my Hugo-built websites, including this one.&lt;/p&gt;
&lt;p&gt;I use Hugo&amp;rsquo;s deployment function and &lt;code&gt;s3cmd&lt;/code&gt; to deploy these websites.&lt;/p&gt;
&lt;h2 id="why-both"&gt;
	Why Both?
	
	&lt;a class="hlink" href="#why-both"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Hugo&amp;rsquo;s deployment function uses the &lt;a href="https://gocloud.dev/howto/blob/"&gt;blob package from the Go Cloud Development Kit&lt;/a&gt;. This package comes with a limitation by design. It cannot set S3 access control lists (ACLs) for the uploaded objects. So neither can Hugo.&lt;/p&gt;</description></item><item><title>Switch Monitor Input from Linux Command Line</title><link>https://hjr265.me/blog/switch-monitor-input-from-linux-command-line/</link><pubDate>Tue, 24 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/switch-monitor-input-from-linux-command-line/</guid><description>&lt;p&gt;For the longest time, I have looked at computer monitors as these &lt;em&gt;dumb&lt;/em&gt; devices. All they do is turn video signals into colours on the screen.&lt;/p&gt;
&lt;p&gt;I was out of touch with the progress.&lt;/p&gt;
&lt;p&gt;Most modern computer monitors come with what is known as DDC/CI. It may be disabled by default, so you need to enable it using the monitor&amp;rsquo;s on-screen display (OSD) settings.&lt;/p&gt;
&lt;p&gt;Then, use a tool like &lt;code&gt;ddcutil&lt;/code&gt; to switch the current input of the monitor.&lt;/p&gt;</description></item><item><title>Making a Real-time "Last N Days" Leaderboard with MongoDB Aggregation Framework</title><link>https://hjr265.me/blog/making-a-real-time-last-n-days-leaderboard-with-mongodb-aggregation-framework/</link><pubDate>Sun, 22 Oct 2023 18:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/making-a-real-time-last-n-days-leaderboard-with-mongodb-aggregation-framework/</guid><description>&lt;p&gt;On Toph, there is a &lt;a href="https://toph.co/leaderboard"&gt;leaderboard of top solvers&lt;/a&gt;. Without any filters, this leaderboard shows the list of programmers who solved the most programming problems in the last seven days. Toph updates the leaderboard in real-time.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/making-a-real-time-last-n-days-leaderboard-with-mongodb-aggregation-framework/overview.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/making-a-real-time-last-n-days-leaderboard-with-mongodb-aggregation-framework/overview_hu_6daca520633530a7.png 1x, https://hjr265.me/blog/making-a-real-time-last-n-days-leaderboard-with-mongodb-aggregation-framework/overview.png 2x" src="https://hjr265.me/blog/making-a-real-time-last-n-days-leaderboard-with-mongodb-aggregation-framework/overview_hu_6daca520633530a7.png" alt="Real-time &amp;#34;Last N Days&amp;#34; Leaderboard with MongoDB Aggregation Framework" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;/figure&gt; 

&lt;p&gt;There are a few ways to build a leaderboard like this one.&lt;/p&gt;
&lt;h2 id="the-naive-way"&gt;
	The Naive Way
	
	&lt;a class="hlink" href="#the-naive-way"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The easy way is to run a daily or hourly cron that aggregates all the solutions submitted over the last seven days and regenerates the leaderboard. However, the leaderboard will be far from real-time with this approach.&lt;/p&gt;</description></item><item><title>Windows Is Weird</title><link>https://hjr265.me/blog/windows-is-weird/</link><pubDate>Fri, 20 Oct 2023 12:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/windows-is-weird/</guid><description>&lt;p&gt;I know we could list a thousand reasons why Windows is weird. But this particular reason is my favourite.&lt;/p&gt;
&lt;p&gt;Here are the N steps to my favourite Windows 10 weirdness:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Copy the URL to this blog post.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;https://hjr265.me/blog/windows-is-weird/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste the URL in a Notepad window. You see the entire URL to this blog post. Good.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open File Explorer. Right-click on an empty area in the window. Click on &lt;code&gt;New › Folder&lt;/code&gt;. Paste the URL as the name of the folder.&lt;/p&gt;</description></item><item><title>Show a Log Throbber in Terminal with Go</title><link>https://hjr265.me/blog/show-a-log-throbber-in-terminal-with-go/</link><pubDate>Thu, 19 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/show-a-log-throbber-in-terminal-with-go/</guid><description>&lt;p&gt;Show a Log Throbber in the Terminal with Go&lt;/p&gt;
&lt;p&gt;A long-running program made to run in a terminal window should indicate what it is doing. Judicious logging is the first step.&lt;/p&gt;
&lt;p&gt;While developing &lt;a href="https://github.com/FurqanSoftware/toph-printd"&gt;Printd&lt;/a&gt; for Toph, we needed a way to indicate the program status without outputting loglines repeatedly.&lt;/p&gt;
&lt;p&gt;Printd, a print server daemon, waits for print requests from Toph and prints out the contents of the request to a connected printer. Until these requests arrive, Printd is mostly sitting idle.&lt;/p&gt;</description></item><item><title>Not a "High-rated" Person</title><link>https://hjr265.me/blog/not-a-high-rated-person/</link><pubDate>Wed, 18 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/not-a-high-rated-person/</guid><description>&lt;p&gt;I came across a post in a competitive programming community. The post read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Always felt like [competitive programming] is no different from any other sport. Trying to get to the top in any sport requires the ability to keep being persistent in your work irrespective of the situation and what people say. [&amp;hellip;] I personally have trained myself so far so that I do the work irrespective of any situation and no matter what emotions I go through, But hasn&amp;rsquo;t yet completely grasped it. Like how to build that mentality very strongly that how I feel has no impact on my work. Would be helpful if some very high rated people would share their opinions.&lt;/p&gt;</description></item><item><title>Gnome Not Reporting Bluetooth Earbuds Battery? Enable the D-Bus Interface</title><link>https://hjr265.me/blog/gnome-not-reporting-bluetooth-earbuds-battery-enable-the-dbus-interface/</link><pubDate>Tue, 17 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/gnome-not-reporting-bluetooth-earbuds-battery-enable-the-dbus-interface/</guid><description>&lt;p&gt;I use one of these true wireless Edifier earbuds.&lt;/p&gt;
&lt;p&gt;I noticed how Android reports the battery level of these earphones. But Gnome doesn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Turns out, you need to enable the experimental D-Bus interface in the Bluetooth daemon on Linux for Gnome to know the battery level of the connected wireless earbuds.&lt;/p&gt;
&lt;p&gt;On Arch Linux (which I use, btw), I had to modify &lt;code&gt;/etc/bluetooth/main.conf&lt;/code&gt; and set:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Experimental = true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You will probably find a &lt;code&gt;# Experimental = false&lt;/code&gt; line in that file that you can uncomment and change from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>#100DaysToOffload Milestone: The 67th Blog Post</title><link>https://hjr265.me/blog/100daystooffload-milestone-67th-blog-post/</link><pubDate>Mon, 16 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/100daystooffload-milestone-67th-blog-post/</guid><description>&lt;p&gt;It is my second milestone blog post of the &lt;a href="https://100daystooffload.com/"&gt;#100DaysToOffload&lt;/a&gt; challenge. I posted my &lt;a href="https://hjr265.me/blog/100daystooffload-milestone-34th-blog-post/"&gt;first milestone blog post&lt;/a&gt; on September 3, 2023.&lt;/p&gt;
&lt;p&gt;I will keep this one short.&lt;/p&gt;
&lt;p&gt;But I will say that I have posted as many &lt;a href="https://hjr265.me/tags/100daystooffload/"&gt;#100DaysToOffload blog posts&lt;/a&gt; in the last 43 days as I have since I started the challenge.&lt;/p&gt;
&lt;p&gt;It is probably a remnant characteristic of my days in the university. Something I should work on changing. Maybe I will get to this after I finish this challenge.&lt;/p&gt;</description></item><item><title>10 Forms of Bash Shell Parameter Expansion</title><link>https://hjr265.me/blog/10-forms-of-bash-shell-parameter-expansion/</link><pubDate>Sun, 15 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/10-forms-of-bash-shell-parameter-expansion/</guid><description>&lt;p&gt;If I look at my search history with the word &amp;ldquo;bash&amp;rdquo; in it, the most frequently searched phrases turn out to be like &amp;ldquo;trim suffix bash&amp;rdquo; and &amp;ldquo;set bash variable if empty&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;It seems I write Bash scripts frequently enough to need these, but not frequently enough to remember these simple Bash shell parameter expansion forms.&lt;/p&gt;
&lt;p&gt;In this blog post I am going to keep a list of 10 forms of Bash shell parameter expansion, that I hope will save me a visit to google.com.&lt;/p&gt;</description></item><item><title>Parsing Social Media URLs in Go With Slinky</title><link>https://hjr265.me/blog/parsing-social-media-urls-in-go-with-slinky/</link><pubDate>Sat, 14 Oct 2023 10:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/parsing-social-media-urls-in-go-with-slinky/</guid><description>&lt;p&gt;Toph now allows programmers to show up to 5 social media URLs on their profile pages.&lt;/p&gt;
&lt;p&gt;Instead of showing the entire URL, I wanted to show the important bits from the URL.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/parsing-social-media-urls-in-go-with-slinky/screen.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/parsing-social-media-urls-in-go-with-slinky/screen.png 1x, https://hjr265.me/blog/parsing-social-media-urls-in-go-with-slinky/screen.png 2x" src="https://hjr265.me/blog/parsing-social-media-urls-in-go-with-slinky/screen.png" alt="Screenshot of a profile panel from Toph" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;figcaption&gt;
				Screenshot of a profile panel from Toph
			&lt;/figcaption&gt;&lt;/figure&gt; 

&lt;p&gt;To do that, I had to parse the social media URLs and extract information like the username or profile ID (when it is a GitHub, Twitter, LinkedIn, Facebook, etc. profile URL) or the instance of Mastodon.&lt;/p&gt;</description></item><item><title>Fixing Creality CR-10 Smart Touch Screen Orientation Issue</title><link>https://hjr265.me/blog/fixing-creality-cr-10-smart-touch-screen-orientation-issue/</link><pubDate>Fri, 13 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/fixing-creality-cr-10-smart-touch-screen-orientation-issue/</guid><description>&lt;p&gt;The Creality CR-10 Smart is a very &lt;em&gt;sensitive&lt;/em&gt; 3D printer, especially when updating firmware.&lt;/p&gt;
&lt;p&gt;Are you using an SD card that is too small? The printer won&amp;rsquo;t update. Too large? Again, the printer won&amp;rsquo;t update.&lt;/p&gt;
&lt;p&gt;You formatted it to FAT32 but used an allocation size that isn&amp;rsquo;t exactly 4096 KB. Tough luck: the printer won&amp;rsquo;t update.&lt;/p&gt;
&lt;p&gt;You successfully updated the firmware to &lt;code&gt;CR-10 Smart Marlin2.0.6SWV1.0.14HWCRC2405V1.2.zip&lt;/code&gt;? Well, now your touch screen is going to behave weirdly.&lt;/p&gt;</description></item><item><title>Bash Script to Auto-archive Downloads by Date</title><link>https://hjr265.me/blog/bash-script-to-auto-archive-downloads-by-date/</link><pubDate>Wed, 11 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/bash-script-to-auto-archive-downloads-by-date/</guid><description>&lt;p&gt;Finding the files you are looking for without combing through hundreds of directories is a true time-saver and an easy productivity move.&lt;/p&gt;
&lt;p&gt;I try to keep my files and directories in order, named and organized neatly. I don&amp;rsquo;t have stale files at the base of my home directory. I have separate directories for my projects, my company stuff, and the work I do for my clients.&lt;/p&gt;
&lt;p&gt;But my &lt;code&gt;~/Downloads&lt;/code&gt; directory is always a colossal mess.&lt;/p&gt;</description></item><item><title>Stardew Valley and Eric Barone</title><link>https://hjr265.me/blog/stardew-valley-and-eric-barone/</link><pubDate>Tue, 10 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/stardew-valley-and-eric-barone/</guid><description>&lt;p&gt;It is no secret that I &lt;a href="https://steamcommunity.com/id/hjr265/"&gt;play video games&lt;/a&gt;. Over the years, I, no doubt, have learned a lot from video games.&lt;/p&gt;
&lt;p&gt;But one particular video game and its developer stand out the most.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/stardew-valley-and-eric-barone/cover.jpg" target="_blank"&gt;
		&lt;picture&gt;&lt;source type="image/webp" media="(max-width: 575.98px)" srcset="https://hjr265.me/blog/stardew-valley-and-eric-barone/cover_hu_2bf6e22bd008547e.webp 1x, https://hjr265.me/blog/stardew-valley-and-eric-barone/cover_hu_246f2189ad00a967.webp 2x"&gt;
				&lt;source type="image/webp" media="(min-width: 576px)" srcset="https://hjr265.me/blog/stardew-valley-and-eric-barone/cover_hu_3f85038510c09c9f.webp 1x, https://hjr265.me/blog/stardew-valley-and-eric-barone/cover_hu_780866e79f5bbdc2.webp 2x"&gt;&lt;img srcset="https://hjr265.me/blog/stardew-valley-and-eric-barone/cover_hu_189618f6e843c387.jpg 1x, https://hjr265.me/blog/stardew-valley-and-eric-barone/cover.jpg 2x" src="https://hjr265.me/blog/stardew-valley-and-eric-barone/cover_hu_189618f6e843c387.jpg" alt="Screenshot of the title screen of Stardew Valley" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;figcaption&gt;
				Stardew Valley
			&lt;/figcaption&gt;&lt;/figure&gt; 

&lt;h2 id="stardew-valley"&gt;
	Stardew Valley
	
	&lt;a class="hlink" href="#stardew-valley"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A friend of mine at school introduced me to &lt;a href="https://en.wikipedia.org/wiki/Harvest_Moon_(video_game)"&gt;Harvest Moon&lt;/a&gt;. I had tried it before, but he introduced me to the mechanics and quirks of Harvest Moon, which got me hooked on the genre.&lt;/p&gt;</description></item><item><title>Building a Blog With Hugo</title><link>https://hjr265.me/blog/building-a-blog-with-hugo/</link><pubDate>Mon, 09 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/building-a-blog-with-hugo/</guid><description>&lt;p&gt;A few weeks ago, I came across a blog post on my RSS reader: &lt;a href="https://til.simonwillison.net/django/building-a-blog-in-django"&gt;Building a blog in Django&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This simple tutorial by Simon Willison was an enjoyable read.&lt;/p&gt;
&lt;p&gt;I understand the topic is not novel, but I liked how it reminds you of the simple features that enhance the implementation of a blog.&lt;/p&gt;
&lt;p&gt;Quoting from the blog post by Simon Willison:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Here are the features I consider to be essential for a blog in 2023 (though they haven&amp;rsquo;t changed much in over a decade):&lt;/p&gt;</description></item><item><title>Setup HTTP3 with NGINX Mainline</title><link>https://hjr265.me/blog/setup-http3-with-nginx-mainline/</link><pubDate>Sun, 08 Oct 2023 10:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/setup-http3-with-nginx-mainline/</guid><description>&lt;p&gt;HTTP3 is here. Well, almost.&lt;/p&gt;
&lt;p&gt;If you are using NGINX, you can update to the mainline version and start using HTTP3 today experimentally.&lt;/p&gt;
&lt;h2 id="installing-nginx-mainline"&gt;
	Installing NGINX Mainline
	
	&lt;a class="hlink" href="#installing-nginx-mainline"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;As of writing this blog post, NGINX v1.24 is the latest stable version. But, HTTP3 is available in v1.25.&lt;/p&gt;
&lt;p&gt;On Ubuntu/Debian-esque servers, the easiest way to install the mainline NGINX version is to &lt;a href="https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#installing-a-prebuilt-debian-package-from-the-official-nginx-repository"&gt;use NGINX&amp;rsquo;s official repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Import the official NGINX signing key.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | tee /usr/share/keyrings/nginx-archive-keyring.gpg &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Add the APT repository.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;http://nginx.org/packages/mainline/debian `lsb_release -cs` nginx&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | tee /etc/apt/sources.list.d/nginx.list
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Install NGINX.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt update
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install nginx&lt;span style="color:#f92672"&gt;=&lt;/span&gt;1.25.*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nginx -v &lt;span style="color:#75715e"&gt;# Verify installation.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Alternatively, you can also &lt;a href="https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-docker/"&gt;do this in Docker&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Prevent Git Commits with Unformatted Go Code</title><link>https://hjr265.me/blog/prevent-git-commits-with-unformatted-go-code/</link><pubDate>Sat, 07 Oct 2023 10:10:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/prevent-git-commits-with-unformatted-go-code/</guid><description>&lt;p&gt;Git has this great feature that I think is well-known but under-used. I am talking about &lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;Git hooks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With Git hooks, you can run scripts during different Git actions.&lt;/p&gt;
&lt;p&gt;Like this one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GOFILES&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;git diff --name-only --cached | grep -e &lt;span style="color:#e6db74"&gt;&amp;#39;.go$&amp;#39;&lt;/span&gt; | grep -ve &lt;span style="color:#e6db74"&gt;&amp;#39;vendor/&amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;UNFMTFILES&lt;span style="color:#f92672"&gt;=()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; f in $GOFILES; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -n &lt;span style="color:#e6db74"&gt;&amp;#34;`gofmt -l -s ./&amp;#34;&lt;/span&gt;$f&lt;span style="color:#e6db74"&gt;&amp;#34;`&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UNFMTFILES&lt;span style="color:#f92672"&gt;+=(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$f&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; &lt;span style="color:#e6db74"&gt;${#&lt;/span&gt;UNFMTFILES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt; -gt &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo You have staged unformatted Go files. Please run &lt;span style="color:#ae81ff"&gt;\`&lt;/span&gt;go fmt&lt;span style="color:#ae81ff"&gt;\`&lt;/span&gt; first.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; f in &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;UNFMTFILES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34; &lt;/span&gt;$f&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This script will take a list of all the staged Go files. It will then run &lt;code&gt;gofmt&lt;/code&gt; to determine if these Go files are not formatted.&lt;/p&gt;</description></item><item><title>Mattermost (or Slack) Message on SSH Login</title><link>https://hjr265.me/blog/mattermost-or-slack-message-on-ssh-login/</link><pubDate>Thu, 05 Oct 2023 15:05:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/mattermost-or-slack-message-on-ssh-login/</guid><description>&lt;p&gt;You have a server that you access over SSH. You have hardened it following the necessary best practices.&lt;/p&gt;
&lt;p&gt;Now you can do one small thing for a little additional peace of mind: Set up Linux Pluggable Authentication Modules (PAM) to send a message to Mattermost (or Slack) on every successful SSH login.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/mattermost-or-slack-message-on-ssh-login/example.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/mattermost-or-slack-message-on-ssh-login/example_hu_c30819dbde8780d7.png 1x, https://hjr265.me/blog/mattermost-or-slack-message-on-ssh-login/example.png 2x" src="https://hjr265.me/blog/mattermost-or-slack-message-on-ssh-login/example_hu_c30819dbde8780d7.png" alt="" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;/figure&gt; 

&lt;p&gt;Here is how you can do it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add a script to &lt;code&gt;/usr/local/bin/&lt;/code&gt; to send the notification message. Name it &lt;code&gt;sshnotify.sh&lt;/code&gt;. Make it executable.&lt;/p&gt;</description></item><item><title>Allow External Keyboard to Wake Laptop</title><link>https://hjr265.me/blog/allow-external-keyboard-to-wake-laptop/</link><pubDate>Wed, 04 Oct 2023 13:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/allow-external-keyboard-to-wake-laptop/</guid><description>&lt;p&gt;My setup at my workspace isn&amp;rsquo;t much: a display and a few peripherals connected to a USB-C hub. This way, I can come in, plug in the hub to my laptop, and start working.&lt;/p&gt;
&lt;p&gt;But if my laptop goes to sleep, I can wake it up only by pressing a key on the internal keyboard or the trackpad. Since I keep the lid of my laptop closed, I have to take it out of the stand first, then open the lid (which causes the laptop to wake up anyway).&lt;/p&gt;</description></item><item><title>Over-engineered URLs vs. A Little Script</title><link>https://hjr265.me/blog/over-engineered-urls-vs-a-little-script/</link><pubDate>Wed, 04 Oct 2023 08:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/over-engineered-urls-vs-a-little-script/</guid><description>&lt;p&gt;One of the local courier services where I live will send you this helpful notification SMS with a tracking URL whenever they pick up a parcel for you. It is useful. At least compared to the hundreds of spam text messages we get here daily.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/over-engineered-urls-vs-a-little-script/sms.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/over-engineered-urls-vs-a-little-script/sms_hu_a3be57c79aae7e03.png 1x, https://hjr265.me/blog/over-engineered-urls-vs-a-little-script/sms.png 2x" src="https://hjr265.me/blog/over-engineered-urls-vs-a-little-script/sms_hu_a3be57c79aae7e03.png" alt="Screenshot of SMS from the local courier service" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;figcaption&gt;
				It reads: [{Courier}] We have collected your parcel {ID} from {Vendor}. Track {MaskedURL}.
			&lt;/figcaption&gt;&lt;/figure&gt; 

&lt;p&gt;The tracking URL is masked, much like a shortened URL, but not exactly short. The URL will redirect you to the real tracking URL only if you are not accessing it from a mobile device.&lt;/p&gt;</description></item><item><title>Adding Icons for Go-built Windows Executable</title><link>https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/</link><pubDate>Sat, 30 Sep 2023 19:55:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/</guid><description>&lt;p&gt;I have been using Windows for video games only for several years now. But that changed a little as I started working on &lt;a href="https://github.com/FurqanSoftware/toph-printd"&gt;Printd&lt;/a&gt;, Toph&amp;rsquo;s print server daemon.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/splash.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/splash_hu_de7316d9e3612857.png 1x, https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/splash.png 2x" src="https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/splash_hu_de7316d9e3612857.png" alt="Adding icons for Go-built Windows executable" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;/figure&gt; 

&lt;p&gt;An executable file (&lt;code&gt;.exe&lt;/code&gt;) on Windows can provide its icons. If you build a Go program for Windows you get the generic executable icon, which is fine. But sometimes fine isn&amp;rsquo;t enough.&lt;/p&gt;</description></item><item><title>Detect If Go Built Windows Executable Is Run From Command Prompt or File Explorer</title><link>https://hjr265.me/blog/detect-if-go-built-windows-executable-is-run-from-command-prompt-or-file-explorer/</link><pubDate>Sat, 30 Sep 2023 11:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/detect-if-go-built-windows-executable-is-run-from-command-prompt-or-file-explorer/</guid><description>&lt;p&gt;In Windows, unlike the Unix-like POSIX-compatible operating systems, there is this notion of an application subsystem: &lt;code&gt;console&lt;/code&gt; vs. &lt;code&gt;windows&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you build a Go program for Windows, it will, by default, use the &lt;code&gt;console&lt;/code&gt; subsystem. When you start this program from File Explorer (e.g. by double-clicking its icon), Windows will show a console (like the Command Prompt window) and run the program inside the console.&lt;/p&gt;
&lt;p&gt;When running a &lt;code&gt;console&lt;/code&gt; subsystem program that finishes quickly, you may notice the console window appears and disappears quickly. You may not even see it flash.&lt;/p&gt;</description></item><item><title>Go Tidbit: Retain Cache to Speed up Go Builds in Docker</title><link>https://hjr265.me/blog/go-tidbit-retain-cache-to-speed-up-go-builds-in-docker/</link><pubDate>Fri, 29 Sep 2023 20:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-retain-cache-to-speed-up-go-builds-in-docker/</guid><description>&lt;p&gt;If you are building Go binaries inside a Docker container, you can speed up the builds by retaining the cache that &lt;code&gt;go build&lt;/code&gt; creates between builds.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/go-tidbit-retain-cache-to-speed-up-go-builds-in-docker/splash.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/go-tidbit-retain-cache-to-speed-up-go-builds-in-docker/splash_hu_2453e0f48ae0c3b1.png 1x, https://hjr265.me/blog/go-tidbit-retain-cache-to-speed-up-go-builds-in-docker/splash.png 2x" src="https://hjr265.me/blog/go-tidbit-retain-cache-to-speed-up-go-builds-in-docker/splash_hu_2453e0f48ae0c3b1.png" alt="Speed up Go Builds in Docker" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;/figure&gt; 

&lt;p&gt;This tidbit is especially effective for large projects with several packages.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take one of my Go projects as an example.&lt;/p&gt;
&lt;p&gt;Toph Platform, the Go project that powers the web application at &lt;a href="https://toph.co/"&gt;toph.co&lt;/a&gt; has 55 packages inside the repository. If I include dependencies (first and third-party), the total number of packages comes to 813.&lt;/p&gt;</description></item><item><title>Go Tidbit: Listing All Time Zones in Go</title><link>https://hjr265.me/blog/go-tidbit-listing-all-time-zones-in-go/</link><pubDate>Sun, 24 Sep 2023 19:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-listing-all-time-zones-in-go/</guid><description>&lt;figure&gt;&lt;a href="https://hjr265.me/blog/go-tidbit-listing-all-time-zones-in-go/splash.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/go-tidbit-listing-all-time-zones-in-go/splash_hu_165bc726f291118f.png 1x, https://hjr265.me/blog/go-tidbit-listing-all-time-zones-in-go/splash_hu_deb6ef4b01fbed54.png 2x" src="https://hjr265.me/blog/go-tidbit-listing-all-time-zones-in-go/splash_hu_165bc726f291118f.png" alt="Go Programming Language and Time Zones" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;/figure&gt; 

&lt;p&gt;I wanted to list all the time zones in Go.&lt;/p&gt;
&lt;p&gt;The standard library in Go comes with the &lt;code&gt;time&lt;/code&gt; package. It also comes with the &lt;code&gt;time/tzdata&lt;/code&gt; package.&lt;/p&gt;
&lt;p&gt;The standard &lt;code&gt;time&lt;/code&gt; package in Go is very well-thought-out. It makes date-time manipulation deceptively simple.&lt;/p&gt;
&lt;p&gt;Yet, I could not find a way to list all the time zones.&lt;/p&gt;
&lt;h2 id="time-zone-database"&gt;
	Time Zone Database
	
	&lt;a class="hlink" href="#time-zone-database"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The documentation for &lt;code&gt;time.LoadLocation&lt;/code&gt; describes how Go uses four sources of time zone data:&lt;/p&gt;</description></item><item><title>Setup Dnsmasq and Systemd-resolved for *.local Hostnames</title><link>https://hjr265.me/blog/setup-dnsmasq-and-systemd-resolved-for-local-hostnames/</link><pubDate>Sat, 23 Sep 2023 17:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/setup-dnsmasq-and-systemd-resolved-for-local-hostnames/</guid><description>&lt;p&gt;I have lines like these in my &lt;code&gt;/etc/hosts&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;127.0.0.1 toph.local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;127.0.0.1 drafts.toph.local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;127.0.0.1 quiz.toph.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can run development servers locally and access them over &lt;code&gt;.local&lt;/code&gt; hostnames (e.g. &lt;code&gt;toph.local&lt;/code&gt;) instead of the loopback IP addresses (e.g. &lt;code&gt;127.0.0.1&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;It works fine. But every time I start working on a new project, I needed to add a new line to the &lt;code&gt;/etc/hosts&lt;/code&gt; file. It didn&amp;rsquo;t sit right with me.&lt;/p&gt;
&lt;p&gt;I should be able to point all &lt;code&gt;*.local&lt;/code&gt; hostnames to a loopback IP address without having to enter each possible hostname. Right?&lt;/p&gt;</description></item><item><title>Making an Auto Scroll Bookmarklet</title><link>https://hjr265.me/blog/making-an-auto-scroll-bookmarklet/</link><pubDate>Fri, 22 Sep 2023 18:05:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/making-an-auto-scroll-bookmarklet/</guid><description>&lt;p&gt;We have extensions and addons that add features and customizations to our web browsers. But it wasn&amp;rsquo;t always the case.&lt;/p&gt;
&lt;p&gt;During the the early 2000s (and even later) there were bookmarklets. Bookmarklets are short JavaScript scripts stored as bookmarks in your web browser.&lt;/p&gt;
&lt;p&gt;These were popular during the 2000s. However they started to decline in popularity when browser extensions became easier to build and more and more websites started to implement Content Security Policy (CSP).&lt;/p&gt;</description></item><item><title>HTML &lt;img&gt; Markup (and Hugo Shortcode) for the Modern Web</title><link>https://hjr265.me/blog/html-img-markup-for-the-modern-web/</link><pubDate>Fri, 22 Sep 2023 10:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/html-img-markup-for-the-modern-web/</guid><description>&lt;p&gt;I am by no means an expert in HTML and CSS. But I have been tweaking and tuning a few of my Hugo-powered websites, including this one. And, I had the opportunity to explore a few of the modern HTML and CSS features.&lt;/p&gt;
&lt;p&gt;Starting last month I made it a point to use more images in my blog posts.&lt;/p&gt;
&lt;p&gt;And, I am glad I did. When I wrote the &lt;a href="https://hjr265.me/blog/hiding-files-in-zip-archives/"&gt;blog post on hiding files in ZIP archives&lt;/a&gt;, I enjoyed preparing the ZIP format illustrations.&lt;/p&gt;</description></item><item><title>Go Tidbit: Detect When the Terminal Is Resized</title><link>https://hjr265.me/blog/go-tidbit-detect-when-the-terminal-is-resized/</link><pubDate>Wed, 20 Sep 2023 21:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-detect-when-the-terminal-is-resized/</guid><description>&lt;p&gt;I have been meaning to do a few short-form blog posts lately. This blog post is going to be one of them.&lt;/p&gt;
&lt;p&gt;In Go, on Linux, if you want to know when the terminal window is resized, you can listen for the &lt;code&gt;SIGWINCH&lt;/code&gt; signal using the &lt;code&gt;signal.Notify&lt;/code&gt; (or the &lt;code&gt;signal.NotifyContext&lt;/code&gt;) function.&lt;/p&gt;
&lt;p&gt;The code will look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ch&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Signal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;signal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Notify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ch&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;syscall&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SIGWINCH&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ch&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// The terminal has been resized.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For a bit more complete example, you can try out this Go code:&lt;/p&gt;</description></item><item><title>Go Tidbit: Putting The Terminal Into Raw Mode</title><link>https://hjr265.me/blog/go-tidbit-putting-the-terminal-into-raw-mode/</link><pubDate>Wed, 20 Sep 2023 00:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-putting-the-terminal-into-raw-mode/</guid><description>&lt;p&gt;I learned something new today. It helped solve a long-standing bug in &lt;a href="https://github.com/FurqanSoftware/bullet"&gt;Bullet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bullet is an application deployment tool that I wrote several years ago. It is a simple tool that SSHs into a server and uses Docker to run applications.&lt;/p&gt;
&lt;p&gt;I use it in production for some of my projects.&lt;/p&gt;
&lt;h2 id="about-the-bug"&gt;
	About the Bug
	
	&lt;a class="hlink" href="#about-the-bug"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Bullet can SSH into a remote server, spin up a one-off container and attach the terminal to it. You could run commands in that container as if you were SSH&amp;rsquo;ed directly into that environment.&lt;/p&gt;</description></item><item><title>Go Tidbit: Waiting on Go Routines</title><link>https://hjr265.me/blog/go-tidbit-waiting-on-go-routines/</link><pubDate>Sun, 17 Sep 2023 21:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-waiting-on-go-routines/</guid><description>&lt;p&gt;Concurrency is one of the central features of Go. And, to build concurrent programs in Go, you need goroutines.&lt;/p&gt;
&lt;p&gt;A goroutine is like a thread, but lighter. Much lighter. And, like any other built-in feature of Go, using it is dead simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; println(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello World&amp;#34;&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// Print &amp;#34;Hello World&amp;#34; from a different goroutine.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Wait. That didn&amp;rsquo;t print anything.&lt;/p&gt;</description></item><item><title>Hiding Files in ZIP Archives</title><link>https://hjr265.me/blog/hiding-files-in-zip-archives/</link><pubDate>Sat, 16 Sep 2023 10:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/hiding-files-in-zip-archives/</guid><description>&lt;p&gt;I remember seeing a tool many years ago that could hide other files in BMP image files. I was a bit too young to understand how it worked, but I think I understand the trick now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the structure of the file type at the byte level.&lt;/li&gt;
&lt;li&gt;Find a spot that can hold an arbitrary length of data.&lt;/li&gt;
&lt;li&gt;Sneak in whatever you want in there.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="box" onclick="this.setAttribute('open', '')"&gt;
	&lt;div class="box__body"&gt;
		&lt;p&gt;As a proof of concept, I used Go to make &lt;a href="https://github.com/hjr265/sneak"&gt;Sneak&lt;/a&gt; - a program to add or extract a hidden file in a ZIP archive.&lt;/p&gt;</description></item><item><title>Make a Part of Form Non-interactable With CSS</title><link>https://hjr265.me/blog/make-a-part-of-form-non-interactable-with-css/</link><pubDate>Fri, 15 Sep 2023 17:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/make-a-part-of-form-non-interactable-with-css/</guid><description>&lt;p&gt;I have an HTML form. I want to make part of it non-interactable depending on certain conditions. I don&amp;rsquo;t want to remove that part entirely.&lt;/p&gt;
&lt;p&gt;There are so many reasons why you may want to do this.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/make-a-part-of-form-non-interactable-with-css/screen.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/make-a-part-of-form-non-interactable-with-css/screen_hu_86beb54595bad2ef.png 1x, https://hjr265.me/blog/make-a-part-of-form-non-interactable-with-css/screen_hu_79165ea384ae4e48.png 2x" src="https://hjr265.me/blog/make-a-part-of-form-non-interactable-with-css/screen_hu_86beb54595bad2ef.png" alt="Screenshot of Contest Branding Settings Form from Toph" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;figcaption&gt;
				Screenshot of Contest Branding Settings Form from &lt;a href="https://toph.co/"&gt;Toph&lt;/a&gt;
			&lt;/figcaption&gt;&lt;/figure&gt; 

&lt;p&gt;In this screenshot, the form allows advanced features to pay customers only. Making that part non-interactable, instead of hiding it, works as a teaser of what the paid tiers offer.&lt;/p&gt;</description></item><item><title>5 Upkeep Tips for Arch Linux</title><link>https://hjr265.me/blog/5-upkeep-tips-for-arch-linux/</link><pubDate>Wed, 13 Sep 2023 00:16:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/5-upkeep-tips-for-arch-linux/</guid><description>&lt;p&gt;Yes, I am one of those guys: &amp;ldquo;I use Arch btw&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;After distro-hopping for almost my entire time at the university, I found Arch Linux. I did momentarily switch to a MacBook after that, but I don&amp;rsquo;t think I have tried any other Linux distribution for over a decade.&lt;/p&gt;
&lt;p&gt;Arch Linux doesn&amp;rsquo;t do everything the way I like on a Linux device. But it doesn&amp;rsquo;t make me feel like switching to another distribution.&lt;/p&gt;</description></item><item><title>Setup Multiple Passphrases for a LUKS Device</title><link>https://hjr265.me/blog/setup-multiple-passphrases-for-a-luks-device/</link><pubDate>Wed, 13 Sep 2023 00:10:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/setup-multiple-passphrases-for-a-luks-device/</guid><description>&lt;p&gt;Let&amp;rsquo;s say you have a computer at home shared by multiple people. And, you want to encrypt your hard drive with LUKS but not have to use the same passphrase.&lt;/p&gt;
&lt;p&gt;You can do that. LUKS has 8 key slots (LUKS1 does, LUKS2 can support more).&lt;/p&gt;
&lt;p&gt;When you set up a LUKS encrypted device you are configuring the first key slot only.&lt;/p&gt;
&lt;p&gt;But by running the following command you can set up an additional passphrase:&lt;/p&gt;</description></item><item><title>Building Multi-platform Docker Image for Go Applications</title><link>https://hjr265.me/blog/building-multi-platform-docker-image-for-go-applications/</link><pubDate>Sun, 10 Sep 2023 21:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/building-multi-platform-docker-image-for-go-applications/</guid><description>&lt;p&gt;I began the weekend writing a silly program: MGHSIAC. It&amp;rsquo;s the elegant abbreviation of &amp;ldquo;My GitHub Status Is A Clock&amp;rdquo;. It turns my GitHub status into a working clock. You can read more about it &lt;a href="https://hjr265.me/blog/my-github-status-is-a-clock/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But as silly as it is, I am now committed to keep it running.&lt;/p&gt;
&lt;p&gt;I have an always-on Raspberry Pi with Portainer running on it already. If I could make a Docker image and host it on Docker Hub, I could easily pull it to that Raspberry Pi and have it continuously update my GitHub status with clock emojis and messages.&lt;/p&gt;</description></item><item><title>Private Cloud on a Raspberry Pi: Hardware</title><link>https://hjr265.me/blog/private-cloud-on-a-raspberry-pi-hardware/</link><pubDate>Sat, 09 Sep 2023 18:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/private-cloud-on-a-raspberry-pi-hardware/</guid><description>&lt;p&gt;I don&amp;rsquo;t think I take privacy as seriously as people should. It is hard.&lt;/p&gt;
&lt;p&gt;Where I live, banks ask you to send documents over WhatsApp. I don&amp;rsquo;t use WhatsApp. When I ask for an email address instead, bank employees often take a few seconds before responding.&lt;/p&gt;
&lt;p&gt;But I also believe in taking baby steps.&lt;/p&gt;
&lt;p&gt;For the last few years, I have been storing bits of my life on a Raspberry Pi instead of the Internet. Bits include contacts, to-do lists, passwords, photos and videos, and more.&lt;/p&gt;</description></item><item><title>My GitHub Status Is A Clock</title><link>https://hjr265.me/blog/my-github-status-is-a-clock/</link><pubDate>Sat, 09 Sep 2023 00:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/my-github-status-is-a-clock/</guid><description>&lt;p&gt;On GitHub, you can set an emoji and a short message as your status. I don&amp;rsquo;t browse GitHub much. But through what little I do, I see so many profiles with &amp;ldquo;:dart: Focusing&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Why not make it a clock? An almost functional clock.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s exactly what I did.: &lt;a href="https://github.com/hjr265"&gt;github.com/hjr265&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://hjr265.me/blog/my-github-status-is-a-clock/cover.png" target="_blank"&gt;
		&lt;picture&gt;&lt;img srcset="https://hjr265.me/blog/my-github-status-is-a-clock/cover_hu_b6c391ad9d7744c2.png 1x, https://hjr265.me/blog/my-github-status-is-a-clock/cover_hu_7bddc10bb0dc9f04.png 2x" src="https://hjr265.me/blog/my-github-status-is-a-clock/cover_hu_b6c391ad9d7744c2.png" alt="Screenshot of hjr265&amp;#39;s GitHub profile" loading="lazy"&gt;
		&lt;/picture&gt;
	&lt;/a&gt;&lt;figcaption&gt;
				Screenshot of &lt;a href="https://github.com/hjr265"&gt;my GitHub profile&lt;/a&gt;
			&lt;/figcaption&gt;&lt;/figure&gt; 

&lt;p&gt;I wrote a Go program that I can leave running. The program updates my GitHub status with one of the clock emojis (one that is close to the current time) and a message saying something like &amp;ldquo;Twelve o&amp;rsquo;clock&amp;rdquo; or &amp;ldquo;Half past twelve&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Pregenerate Colorful Letter Avatars With Go</title><link>https://hjr265.me/blog/pregenerate-colorful-letter-avatars-with-go/</link><pubDate>Fri, 08 Sep 2023 16:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/pregenerate-colorful-letter-avatars-with-go/</guid><description>&lt;p&gt;Avatars (also known as profile images) are a great way to represent users in web applications (especially in the ones with social or collaborative features). It&amp;rsquo;s like icons, but for human beings.&lt;/p&gt;
&lt;p&gt;But as your application grows, you start to realize that not everyone is quick to upload an avatar for their account.&lt;/p&gt;
&lt;p&gt;If your application uses one of those silhouette images as a fallback avatar, then soon you will have more &amp;ldquo;John Doe&amp;rdquo; avatars than personalized ones. Not cool to look at when there are 10 avatars side-by-side and they are all this grey human silhouette.&lt;/p&gt;</description></item><item><title>Making a Gnome Background Slide Show</title><link>https://hjr265.me/blog/making-a-gnome-background-slide-show/</link><pubDate>Thu, 07 Sep 2023 11:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/making-a-gnome-background-slide-show/</guid><description>&lt;p&gt;&amp;ldquo;Background Slide Show&amp;rdquo;. That is what &lt;a href="https://www.gnome.org/"&gt;Gnome&lt;/a&gt; uses in code to refer to its time-based dynamic backgrounds feature.&lt;/p&gt;
&lt;p&gt;Instead of having a static background, a Gnome Background Slide Show allows you to have a set of images that Gnome selects from based on the current time of the day. You can also configure the dynamic background to use transitions when changing from one picture to the next.&lt;/p&gt;
&lt;p&gt;There are tools that you can install to build dynamic backgrounds. But I wanted to read some documentation and figure out how it works.&lt;/p&gt;</description></item><item><title>Client-side Search in Hugo with Fuse.js</title><link>https://hjr265.me/blog/client-side-search-in-hugo-with-fuse-js/</link><pubDate>Wed, 06 Sep 2023 19:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/client-side-search-in-hugo-with-fuse-js/</guid><description>&lt;p&gt;&lt;a href="https://help.toph.co/"&gt;Toph Help&lt;/a&gt; is built with Hugo - a static site generator.&lt;/p&gt;
&lt;p&gt;As you would expect with static sites, the pages are all generated ahead of time and hosted as plain HTML. You get all the benefits of static websites, but what about search?&lt;/p&gt;
&lt;p&gt;Client-side search is one way to work around this limitation of static websites.&lt;/p&gt;
&lt;p&gt;You build an array of objects describing all your pages on your website. You serve it to the client as JSON. You use the JSON with client-side JavaScript to provide fast search functionality.&lt;/p&gt;</description></item><item><title>Shell Script for the #100DaysToOffload Challenge</title><link>https://hjr265.me/blog/shell-script-for-100daystooffload-challenge/</link><pubDate>Wed, 06 Sep 2023 10:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/shell-script-for-100daystooffload-challenge/</guid><description>&lt;p&gt;I am getting quite close to my 1-year mark since I started the #100DaysToOffload challenge. Some &lt;em&gt;quick mafs&lt;/em&gt; tell me that I can&amp;rsquo;t skip that many days if I want to make it to 100 blog posts by the end of the 365 days.&lt;/p&gt;
&lt;p&gt;I wrote a script that takes the date when I made my &lt;a href="https://hjr265.me/blog/passing-a-vector-of-redismodulestring-to-redismodule-call/"&gt;first #100DaysToOffload blog post&lt;/a&gt; and the number of blog posts I have written through for challenge so far. The script then calculates some numbers to&amp;hellip; motivate.&lt;/p&gt;</description></item><item><title>#100DaysToOffload Milestone: The 34th Blog Post</title><link>https://hjr265.me/blog/100daystooffload-milestone-34th-blog-post/</link><pubDate>Sun, 03 Sep 2023 12:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/100daystooffload-milestone-34th-blog-post/</guid><description>&lt;h2 id="what-is-100daystooffload"&gt;
	What is #100DaysToOffload?
	
	&lt;a class="hlink" href="#what-is-100daystooffload"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://100daystooffload.com/"&gt;#100DaysToOffload&lt;/a&gt; is an Internet challenge created by Kev Quirk.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t quite recall how I came across this challenge, probably through a front-page entry on Hacker News. But, I am glad I did.&lt;/p&gt;
&lt;p&gt;The goal of this challenge is simple: To encourage you to write.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The whole point of #100DaysToOffload is to challenge you to publish 100 posts on your personal blog in a year. &lt;a href="https://100daystooffload.com/"&gt;[&amp;hellip;]&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Configuring Docker Health Check for Go Web Applications</title><link>https://hjr265.me/blog/configuring-docker-health-check-for-go-web-applications/</link><pubDate>Sat, 02 Sep 2023 12:45:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/configuring-docker-health-check-for-go-web-applications/</guid><description>&lt;p&gt;Docker has been providing a health check mechanism for quite some time. It is useful in identifying issues with programs that can fail in ways other than just outright crashing.&lt;/p&gt;
&lt;p&gt;And it is easy to set up.&lt;/p&gt;
&lt;p&gt;Docker health checks work periodically running a program within the container and observing its exit status. If it exits with a 0, the container is considered healthy. If it exits with a 1, the container is considered to be unhealthy.&lt;/p&gt;</description></item><item><title>Certbot-Name.com DNS Challenge Hooks</title><link>https://hjr265.me/blog/certbot-name-com-dns-challenge-hooks/</link><pubDate>Fri, 01 Sep 2023 17:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/certbot-name-com-dns-challenge-hooks/</guid><description>&lt;p&gt;With Certbot, for SSL certificates, I tend to use its DNS challenge over the HTTP challenge method. I find it a lot easier to automate. And, I can keep the certificate renewal details and mechanisms off the balancer or application servers.&lt;/p&gt;
&lt;p&gt;But I also have domains that are on &lt;a href="https://name.com"&gt;Name.com&lt;/a&gt;. And, for some of them, I have their DNS records in the same place.&lt;/p&gt;
&lt;p&gt;How do you then use Certbot&amp;rsquo;s DNS challenge method with Name.com domains?&lt;/p&gt;</description></item><item><title>Schedule MongoDB Backups with GitLab CI/CD</title><link>https://hjr265.me/blog/schedule-mongodb-backups-with-gitlab-ci-cd/</link><pubDate>Wed, 30 Aug 2023 15:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/schedule-mongodb-backups-with-gitlab-ci-cd/</guid><description>&lt;p&gt;GitLab CI/CD let&amp;rsquo;s you schedule pipelines. And, in a way, I find it such a convenient way to manage my Internet crons.&lt;/p&gt;
&lt;p&gt;One use of GitLab CI/CD schedules that I make is to backup MongoDB data. The pipeline, when run, SSHs into a server holding the MongoDB instance, runs &lt;code&gt;mongodump&lt;/code&gt; and pipes it straight to &lt;code&gt;s3cmd&lt;/code&gt; that then stores the dumped archived to an S3-esque bucket.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what the pipeline looks like in &lt;code&gt;.gitlab-ci.yaml&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Spam Story 01: A Not-Quite-Ethical Hacker</title><link>https://hjr265.me/blog/spam-story-a-not-quite-ethical-hacker/</link><pubDate>Tue, 29 Aug 2023 10:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/spam-story-a-not-quite-ethical-hacker/</guid><description>&lt;p&gt;On &lt;strong&gt;April 3, 2023&lt;/strong&gt;, I received an email from a &amp;ldquo;security researcher&amp;rdquo;. The &amp;ldquo;security researcher&amp;rdquo; and his/her &amp;ldquo;expert team&amp;rdquo; scanned one of my sites and found a &amp;ldquo;critical urgent&amp;rdquo; vulnerability.&lt;/p&gt;
&lt;p&gt;&lt;img src="screen01.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The web application in question links to a subdomain under the same domain from the footer without the &lt;code&gt;rel=&amp;quot;noreferer noopener&amp;quot;&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;Some will say that a subdomain is not an internal link and it must have that attribute. Fine.&lt;/p&gt;</description></item><item><title>Makefile Recipe: Build with Docker and Export To A Gzipped Tarball</title><link>https://hjr265.me/blog/makefile-recipe-build-with-docker-and-export-gzipped-tarball/</link><pubDate>Mon, 28 Aug 2023 17:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/makefile-recipe-build-with-docker-and-export-gzipped-tarball/</guid><description>&lt;p&gt;I have been writing a lot of Makefiles lately. I find them simple and easy to like. And, as with all old-school things, I am starting to overlook its quirks.&lt;/p&gt;
&lt;p&gt;I needed to write a Makefile that lets me build something with Docker and then export the entire contents of the image to a gzipped tarball.&lt;/p&gt;
&lt;p&gt;The first part is easy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Makefile" data-lang="Makefile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;.PHONY&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;build&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;cd &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;APP&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;; docker build -t thing-&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;APP&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;:latest .&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;When run as &lt;code&gt;make build APP=something&lt;/code&gt;, it will build the &lt;code&gt;Dockerfile&lt;/code&gt; within the &lt;code&gt;something/&lt;/code&gt; directory.&lt;/p&gt;</description></item><item><title>Presence Tracking with Redis</title><link>https://hjr265.me/blog/presence-tracking-with-redis/</link><pubDate>Sun, 16 Jul 2023 18:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/presence-tracking-with-redis/</guid><description>&lt;p&gt;Let&amp;rsquo;s say you are building a chat application. You have everything chalked out. The only thing left is that pesky green-yellow-grey indicator next to everyone&amp;rsquo;s avatar.&lt;/p&gt;
&lt;p&gt;&lt;img src="presence.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;With Redis, there is an easy way to track and display the presence of your users. All you need is a Redis sorted set.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Redis sorted set is a collection of unique strings (members) ordered by an associated score. &lt;a href="https://redis.io/docs/data-types/sorted-sets/"&gt;&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="track-last-seen-with-zadd"&gt;
	Track Last Seen with ZADD
	
	&lt;a class="hlink" href="#track-last-seen-with-zadd"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Using &lt;a href="https://redis.io/commands/zadd/"&gt;&lt;code&gt;ZADD&lt;/code&gt;&lt;/a&gt; you can store your user&amp;rsquo;s identifier as a member in a sorted set and the time the user was &amp;ldquo;last seen&amp;rdquo; as its score:&lt;/p&gt;</description></item><item><title>Organizing Libvirt Hooks for Qemu</title><link>https://hjr265.me/blog/organizing-libvirt-hooks-for-qemu/</link><pubDate>Sun, 16 Jul 2023 16:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/organizing-libvirt-hooks-for-qemu/</guid><description>&lt;p&gt;Something I have been meaning to write about for a while is my KVM/VFIO-based gaming setup. Yes, I run Linux. And I run Windows, on Linux, in a virtual machine (VM). And it works! I game on it almost every day.&lt;/p&gt;
&lt;p&gt;But the longer post has to wait for another day.&lt;/p&gt;
&lt;p&gt;Today I am just sharing the short script that lets me keep my Libvirt hooks for Qemu a tad more organized:&lt;/p&gt;</description></item><item><title>Hugo Footnote for the 100 Days to Offload Challenge</title><link>https://hjr265.me/blog/hugo-footnote-for-100-days-to-offload-challenge/</link><pubDate>Sat, 01 Jul 2023 19:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/hugo-footnote-for-100-days-to-offload-challenge/</guid><description>&lt;p&gt;Yesterday I posted my 25th blog post for the &lt;a href="https://hjr265.me/tags/100daystooffload/"&gt;#100DaysToOffload&lt;/a&gt;. That&amp;rsquo;s 25% of the challenge.&lt;/p&gt;
&lt;p&gt;If it wasn&amp;rsquo;t clear by the post &lt;a href="https://hjr265.me/blog/showing-github-stars-with-static-site-generator-hugo/"&gt;Showing GitHub Stars With Static Site Generator Hugo&lt;/a&gt;, I use Hugo for this site.&lt;/p&gt;
&lt;p&gt;All this time, I was manually adding a footnote to each of the blog posts:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This post is {n}th of my #100DaysToOffload challenge. Want to get involved? Find out more at 100daystooffload.com.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Figuring out what &lt;code&gt;{n}&lt;/code&gt; is for each blog post wasn&amp;rsquo;t fun.&lt;/p&gt;</description></item><item><title>Simple Fixed-window Rate Limiter With Redis</title><link>https://hjr265.me/blog/simple-rate-limiter-with-redis/</link><pubDate>Sat, 01 Jul 2023 19:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/simple-rate-limiter-with-redis/</guid><description>&lt;p&gt;A while ago I needed a very quick rate limiter implementation. The application I was working on was already using Redis.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fixed-window rate limiting:&lt;/strong&gt; This is a straightforward algorithm that counts the number of requests received within a fixed time window, such as one minute. Once the maximum number of requests is reached, additional requests are rejected until the next window begins. &lt;a href="https://redis.com/glossary/rate-limiting/"&gt;&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With a small Redis script, I was able to implement a fixed-window rate limiter:&lt;/p&gt;</description></item><item><title>Makeshift CDN for Linode Object Storage with NGINX for Discourse</title><link>https://hjr265.me/blog/makeshift-cdn-for-linode-object-storage-with-nginx-for-discourse/</link><pubDate>Mon, 26 Jun 2023 09:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/makeshift-cdn-for-linode-object-storage-with-nginx-for-discourse/</guid><description>&lt;p&gt;If you use S3-like object storage for Discourse uploads, you will quickly realize that Discourse strongly recommends a CDN.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not using a CDN (or entering the bucket URL as the CDN URL) is likely to cause problems and is not supported. &lt;a href="https://meta.discourse.org/t/configure-an-s3-compatible-object-storage-provider-for-uploads/148916"&gt;[&amp;hellip;]&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you were to use Amazon S3, their Cloudfront service would be the easy solution. But not all cloud providers (e.g. &lt;a href="https://www.linode.com/lp/refer/?r=8d4f388136825d3d04a90d3f7b0ce6b29732a835"&gt;Linode&lt;/a&gt; &lt;em&gt;referral&lt;/em&gt;) providing S3-like services has CDN offerings. For such cases &lt;a href="https://www.nginx.com/resources/wiki/start/topics/examples/reverseproxycachingexample/"&gt;NGINX with reverse proxy caching&lt;/a&gt; can work well as a makeshift CDN.&lt;/p&gt;</description></item><item><title>Memory Game in Less Than 100 Lines of JavaScript</title><link>https://hjr265.me/blog/memory-game-in-less-than-100-lines-of-javascript/</link><pubDate>Sat, 24 Jun 2023 13:06:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/memory-game-in-less-than-100-lines-of-javascript/</guid><description>&lt;p&gt;With Toph, for me, the line between work and fun blurs.&lt;/p&gt;
&lt;p&gt;I wanted to add a small in-browser game to Toph that would allow contest participants to pass the time while waiting for the contest to begin.&lt;/p&gt;
&lt;p&gt;The thing about 100 lines of JavaScript is arbitrary. I wanted to make the most out of modern CSS features and use JavaScript only where necessary.&lt;/p&gt;
&lt;p&gt;Without further ado, here&amp;rsquo;s the game:&lt;/p&gt;
&lt;iframe src="https://hjr265.github.io/memflip/" width="100%" height="650px" style="border: none;"&gt;&lt;/iframe&gt;
&lt;p&gt;You can find the &lt;a href="https://github.com/hjr265/memflip"&gt;source code for this game on GitHub&lt;/a&gt;. It is also &lt;a href="https://www.npmjs.com/package/memflip"&gt;available on NPM&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Go Tidbit: Update Checker with GitHub Releases</title><link>https://hjr265.me/blog/go-tidbit-check-for-updates-with-github-releases/</link><pubDate>Sat, 24 Jun 2023 12:11:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-check-for-updates-with-github-releases/</guid><description>&lt;p&gt;After building &lt;a href="https://github.com/FurqanSoftware/toph-printd"&gt;Printd&lt;/a&gt;, Toph&amp;rsquo;s print daemon, it became necessary to ensure that contest organizers were using the latest version of the software. Since Printd is open-source we host both the code and the release artifacts on GitHub.&lt;/p&gt;
&lt;p&gt;The following function uses the Go client library for GitHub to check the latest release and compare the tag with the current version.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;github.com/google/go-github/v53/github&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;golang.org/x/mod/semver&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;buildTag&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;v0.3.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;repoOwner&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;FurqanSoftware&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;repoName&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;toph-printd&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;checkUpdate&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Give your program at most 5 seconds to check for updates.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;cancel&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithTimeout&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cancel&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Check the latest release on GitHub.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;github&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewClient&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Repositories&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GetLatestRelease&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;repoOwner&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;repoName&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TagName&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Check if the latest release is newer than the current version.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;semver&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Compare&lt;/span&gt;(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TagName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;buildTag&lt;/span&gt;) &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Update available (%s)&amp;#34;&lt;/span&gt;, &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TagName&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Since the API is accessible publicly you do not need to authenticate the GitHub API requests.&lt;/p&gt;</description></item><item><title>Generate SSH Known Hosts in Terraform</title><link>https://hjr265.me/blog/generate-ssh-known-hosts-in-terraform/</link><pubDate>Fri, 24 Mar 2023 10:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/generate-ssh-known-hosts-in-terraform/</guid><description>&lt;p&gt;I manage &lt;a href="https://toph.co"&gt;Toph&amp;rsquo;s&lt;/a&gt; infrastructure with Terraform. The setup is as multi-cloud as it gets. Beyond Terraform, I have several Ansible Playbooks for configuring and upkeeping the infrastructure.&lt;/p&gt;
&lt;p&gt;Something that I wanted to do with Terraform was to generate an SSH known_hosts file ahead of time as any changes are applied to the infrastructure. There isn&amp;rsquo;t a straightforward way to do that in Terraform. But the workaround is simple too.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;locals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ssh_hosts&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; merge(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vultr_instance&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;servers&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;main_ip&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; }],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;linode_instance&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;servers&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ip_address&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; }],&lt;span style="color:#75715e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;null_resource&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;known_hosts&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;provisioner&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;local-exec&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;command&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;EOT&lt;/span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; rm -f known_hosts;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;%{ for i, host in local.ssh_hosts }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ssh-keyscan -p ${host.port} ${host.host} &amp;gt;&amp;gt; known_hosts;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;%{ endfor ~}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;EOT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;interpreter&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;All you need to do is put all your SSH hostnames/addresses and ports in a local variable. And, then use a &lt;code&gt;local-exec&lt;/code&gt; provisioner in a &lt;code&gt;null_resource&lt;/code&gt; to run &lt;code&gt;ssh-keyscan&lt;/code&gt; for each host, appending the output to a &amp;ldquo;known_hosts&amp;rdquo; file.&lt;/p&gt;</description></item><item><title>Randomised Suffix in Terraform Resource Names</title><link>https://hjr265.me/blog/randomized-suffix-in-terraform-resource-names/</link><pubDate>Mon, 13 Mar 2023 20:30:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/randomized-suffix-in-terraform-resource-names/</guid><description>&lt;p&gt;I didn&amp;rsquo;t like how I named cloud resources for &lt;a href="https://toph.co"&gt;Toph&lt;/a&gt;. The resource names had sequential suffixes at the end.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take Toph&amp;rsquo;s storage servers, for example, where MongoDB replicas are running. On Linode, while they were there, I had named the three storage servers as &lt;code&gt;toph-storage-sgp-1&lt;/code&gt;, &lt;code&gt;toph-storage-sgp-2&lt;/code&gt; and &lt;code&gt;toph-storage-sgp-3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But as I moved them over to &lt;a href="https://www.vultr.com/?ref=8025299"&gt;Vultr&lt;/a&gt; (referral) last week, I realised that eliminating these sequential numbers could remove any notion of preference/precedence among these three servers.&lt;/p&gt;</description></item><item><title>Go Tidbit: Filtering Slices In-place</title><link>https://hjr265.me/blog/go-tidbit-filtering-slices-in-place/</link><pubDate>Mon, 06 Mar 2023 20:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-filtering-slices-in-place/</guid><description>&lt;p&gt;At launch, Go did many things that felt out of place next to the other popular programming languages of the era. But now, those same language features feel so natural that I find it hard to think of a time without them.&lt;/p&gt;
&lt;p&gt;&lt;img src="overview.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;One such feature is the slice type. In Go, a slice is a view of an array. And naturally, you can have multiple slices backed by the same array:&lt;/p&gt;</description></item><item><title>Making Ranked Leaderboards with MongoDB Aggregation Framework</title><link>https://hjr265.me/blog/making-ranked-leaderboards-with-mongodb-aggregation-framework/</link><pubDate>Sat, 11 Feb 2023 11:55:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/making-ranked-leaderboards-with-mongodb-aggregation-framework/</guid><description>&lt;p&gt;When I first added the &lt;a href="https://toph.co/leaderboard"&gt;leaderboard on Toph&lt;/a&gt; several years ago, I had to do most of the leaderboard-related things in application code. But then MongoDB introduced the &lt;code&gt;$setWindowFields&lt;/code&gt; aggregation operator in MongoDB 5.0. And it simplified so much when it came to implementing ranked leaderboards.&lt;/p&gt;
&lt;p&gt;One of the things that I had to do in the application code was to calculate the rank of the leaderboard entries.&lt;/p&gt;
&lt;p&gt;&lt;img src="overview.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;In this tutorial, we will see how the same can be done using MongoDB&amp;rsquo;s aggregation pipelines.&lt;/p&gt;</description></item><item><title>Go Tidbit: Handling Signals, Exiting Gracefully</title><link>https://hjr265.me/blog/go-tidbit-handling-signals-exitting-gracefully/</link><pubDate>Thu, 09 Feb 2023 20:25:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-handling-signals-exitting-gracefully/</guid><description>&lt;p&gt;Signals are standardized messages that an operating system can send your programs.&lt;/p&gt;
&lt;p&gt;Take &lt;code&gt;Ctrl+C&lt;/code&gt; for example. When running a program from the terminal and you hit &lt;code&gt;Ctrl+C&lt;/code&gt;, you expect the program to end immediately.&lt;/p&gt;
&lt;p&gt;How does that work, though? &lt;code&gt;Ctrl+C&lt;/code&gt; is a &lt;em&gt;shortcut&lt;/em&gt; for the POSIX signal &lt;code&gt;SIGINT&lt;/code&gt;. By default, this signal causes your program to be terminated.&lt;/p&gt;
&lt;p&gt;But this is one of those signals you can handle: You can intercept it and do whatever you please.&lt;/p&gt;</description></item><item><title>Go Tidbit: Setting Variables in Go During Build</title><link>https://hjr265.me/blog/go-tidbit-setting-variables-in-go-during-build/</link><pubDate>Tue, 07 Feb 2023 22:40:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-setting-variables-in-go-during-build/</guid><description>&lt;p&gt;Last week I was working on a Go program. I wanted it to print the closest Git tag and build time to logs at startup.&lt;/p&gt;
&lt;p&gt;I could write a small script that generates a Go file with these two variables set right before building the Go program.&lt;/p&gt;
&lt;p&gt;But, Go makes it easier.&lt;/p&gt;
&lt;h2 id="ldflags"&gt;
	Ldflags
	
	&lt;a class="hlink" href="#ldflags"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Say you have the following Go code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; println(&lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can build it like so:&lt;/p&gt;</description></item><item><title>Go Tidbit: Ellipsize Strings Without Breaking Unicode</title><link>https://hjr265.me/blog/go-tidbit-ellipsize-strings-without-breaking-unicode/</link><pubDate>Mon, 06 Feb 2023 21:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-ellipsize-strings-without-breaking-unicode/</guid><description>&lt;p&gt;Strings in Go support Unicode. If you come from a programming language like C, you may think of strings as an array of (byte-sized) characters.&lt;/p&gt;
&lt;p&gt;In Go, you can convert a string to a byte slice, and access/manipulate each byte. But if you want to truncate or ellipsize the string to a specific length, you have to think of a string like a slice of runes.&lt;/p&gt;
&lt;p&gt;Take this string for example: &lt;code&gt;&amp;quot;বাংলাদেশ&amp;quot;&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Go Tidbit: Peek a Snippet From io.Reader</title><link>https://hjr265.me/blog/go-tidbit-peek-a-snippet-from-io-reader/</link><pubDate>Sun, 05 Feb 2023 13:40:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-peek-a-snippet-from-io-reader/</guid><description>&lt;p&gt;You have an &lt;code&gt;io.Reader&lt;/code&gt;, and you want to extract a small snippet from the beginning of the &lt;code&gt;io.Reader&lt;/code&gt; and then put it back.&lt;/p&gt;
&lt;p&gt;Ideally, &lt;code&gt;io.ReadSeeker&lt;/code&gt; would let you do that. But not all &lt;code&gt;io.Reader&lt;/code&gt;s can seek.&lt;/p&gt;
&lt;p&gt;And, like toothpaste from its tube, you cannot put something back once you read it from an &lt;code&gt;io.Reader&lt;/code&gt;. But you can do the next best thing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// PeekSnippet takes r and returns the first n bytes from it and another&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// io.Reader with the entire data.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PeekSnippet&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Reader&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;) ([]&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Reader&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;lr&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;LimitReader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ReadAll&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;lr&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// Read first n bytes from r through lr.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MultiReader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewReader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;), &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// Make a new reader combining the bytes just read and the remaining data in r.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;As you can see, you can use &lt;code&gt;io.LimitReader&lt;/code&gt; to read the first &lt;code&gt;n&lt;/code&gt; bytes. Then use &lt;code&gt;io.MultiReader&lt;/code&gt; to combine those bytes with the remainder of data in &lt;code&gt;r&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Go Tidbit: How Much Was Read From io.Reader?</title><link>https://hjr265.me/blog/go-tidbit-how-much-was-read-from-io-reader/</link><pubDate>Sun, 05 Feb 2023 12:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/go-tidbit-how-much-was-read-from-io-reader/</guid><description>&lt;p&gt;You have an &lt;code&gt;io.Reader&lt;/code&gt;, and you are about to pass it to some utility that will read from it. But the utility won&amp;rsquo;t tell you how much it has read from the &lt;code&gt;io.Reader&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;How do you figure out how many bytes of data were in the &lt;code&gt;io.Reader&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Like this snippet of Go+AWS code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uploadToS3&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Reader&lt;/span&gt;) (&lt;span style="color:#66d9ef"&gt;int64&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;s3manager&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewUploader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;awsSess&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Upload&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;s3manager&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;UploadInput&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Bucket&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;some-bucket&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Key&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// How many bytes were in r?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You could implement a type that wraps the &lt;code&gt;io.Reader&lt;/code&gt; and proxies all calls to &lt;code&gt;Read&lt;/code&gt; while keeping track of the number of bytes read.&lt;/p&gt;</description></item><item><title>Using Caddy to Indicate GitLab Repository for Go Module Path with Different Domains</title><link>https://hjr265.me/blog/using-caddy-to-serve-canonical-go-get-import-path/</link><pubDate>Sat, 21 Jan 2023 11:05:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/using-caddy-to-serve-canonical-go-get-import-path/</guid><description>&lt;p&gt;The self-hosted GitLab at my company, &lt;a href="https://furqansoftware.com"&gt;Furqan Software&lt;/a&gt;, is home to all the company Go projects. The domain where Furqan Software&amp;rsquo;s GitLab is accessible is different from the domain in Go module paths. That means &lt;code&gt;go get&lt;/code&gt; doesn&amp;rsquo;t work out of the box for our Go projects.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say you have a Go project with the module path &lt;code&gt;go.example.com/tools/aglet&lt;/code&gt;. And the corresponding GitLab project is at &lt;code&gt;https://gitlab.example.com/tools/aglet&lt;/code&gt;. If you run &lt;code&gt;go get go.example.com/tools/aglet&lt;/code&gt;, you will see an error from &lt;code&gt;go get&lt;/code&gt; about not finding the repository.&lt;/p&gt;</description></item><item><title>Private and Secure Multi-cloud Network with WireGuard</title><link>https://hjr265.me/blog/private-secure-multi-cloud-network-wireguard/</link><pubDate>Tue, 17 Jan 2023 13:05:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/private-secure-multi-cloud-network-wireguard/</guid><description>&lt;p&gt;Can you make a private and secure network between servers on multiple cloud providers?&lt;/p&gt;
&lt;p&gt;Yes, with WireGuard, you can. The solution is fast and secure. It is easy to deploy/automate. And, best of all, it is cloud-agnostic.&lt;/p&gt;
&lt;p&gt;For convenience, I have created a &lt;a href="https://github.com/hjr265/clique"&gt;demo repository&lt;/a&gt; with Terraform and Ansible that you can clone and try. The demo uses &lt;a href="https://www.linode.com/lp/refer/?r=8d4f388136825d3d04a90d3f7b0ce6b29732a835"&gt;Linode&lt;/a&gt; (referral) and &lt;a href="https://www.vultr.com/?ref=8025299"&gt;Vultr&lt;/a&gt; (referral).&lt;/p&gt;
&lt;p&gt;&lt;img src="overview.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;In this article, I will walk you through the inner workings of the demo and this solution.&lt;/p&gt;</description></item><item><title>Links for Social Network Share Dialogs</title><link>https://hjr265.me/blog/links-for-social-network-share-dialogs/</link><pubDate>Sat, 07 Jan 2023 11:20:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/links-for-social-network-share-dialogs/</guid><description>&lt;p&gt;You have a few options to add social network sharing buttons to your website or web app.&lt;/p&gt;
&lt;p&gt;One, you can embed their JavaScript &lt;del&gt;operating systems&lt;/del&gt; SDKs. If your site needs 21st-century Internet bloat, this is what you are looking for.&lt;/p&gt;
&lt;p&gt;Two, you can use third-party share button JavaScript libraries. There are way too many of these out there. And they are just a &amp;ldquo;google&amp;rdquo; away. I will refrain from linking to any.&lt;/p&gt;</description></item><item><title>Embedding a JavaScript Library (KaTeX) in Go</title><link>https://hjr265.me/blog/embedding-a-javascript-library-katex-in-go/</link><pubDate>Sat, 24 Dec 2022 10:35:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/embedding-a-javascript-library-katex-in-go/</guid><description>&lt;p&gt;KaTeX is the fan-favourite way of doing math and equations on the web. The library is simple to use and easy to reason about. But it is JavaScript. How do you build a Go program that renders math and equations like KaTeX?&lt;/p&gt;
&lt;p&gt;You embed KaTeX in your Go program.&lt;/p&gt;
&lt;p&gt;Several Go packages allow you to run JavaScript from within Go. In this post, we will use &lt;a href="https://github.com/lithdew/quickjs"&gt;github.com/lithdew/quickjs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let us first fetch the latest copy of &lt;a href="https://cdnjs.com/libraries/KaTeX"&gt;katex.min.js from CDNJS&lt;/a&gt;. KaTeX uses the MIT license; make sure to include that in your project.&lt;/p&gt;</description></item><item><title>Block All But One Website With Windows Defender Firewall</title><link>https://hjr265.me/blog/block-all-but-one-website-with-windows-defender-firewall/</link><pubDate>Sun, 11 Dec 2022 00:15:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/block-all-but-one-website-with-windows-defender-firewall/</guid><description>&lt;p&gt;How do you block access to all websites except one on a Windows device?&lt;/p&gt;
&lt;p&gt;On-site programming contest organizers need to ensure that the contestant workstations can access &lt;a href="https://toph.co"&gt;Toph&lt;/a&gt; but no other website.&lt;/p&gt;
&lt;p&gt;Toph recommends restricting the Internet through router/firewall configuration; sometimes, it is not a solution that the contest organizer can choose. At one of the recent divisional programming contests in Bangladesh, the only option was to configure each contestant&amp;rsquo;s workstation to restrict the Internet.&lt;/p&gt;</description></item><item><title>Preparing Myself to Learn Programming</title><link>https://hjr265.me/blog/preparing-myself-to-learn-programming/</link><pubDate>Thu, 08 Dec 2022 14:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/preparing-myself-to-learn-programming/</guid><description>&lt;p&gt;My first exposure to any programming involved writing Batch scripts - think shell scripts for Windows.&lt;/p&gt;
&lt;p&gt;The first time I used a computer was an IBM XT clone. It ran DOS.&lt;/p&gt;
&lt;p&gt;I was 11 when I got my second computer, a nifty Windows 95 machine. I kept all of my games organized on that second computer. One day, I thought of making a number-based menu for my games using Batch scripts. That&amp;rsquo;s where it started.&lt;/p&gt;</description></item><item><title>Adding target="_blank" to User-Generated HTML Anchors in Go</title><link>https://hjr265.me/blog/adding-target-blank-to-html-anchors-in-go/</link><pubDate>Tue, 06 Dec 2022 16:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/adding-target-blank-to-html-anchors-in-go/</guid><description>&lt;p&gt;Working with user-generated content is always &lt;del&gt;a nightmare&lt;/del&gt; interesting.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say you are building a blogging platform with Go. Your users write posts in Markdown that the platform then renders as HTML. And, you want to add &lt;code&gt;target=&amp;quot;_blank&amp;quot;&lt;/code&gt; and &lt;code&gt;rel=&amp;quot;noreferrer noopener&amp;quot;&lt;/code&gt; to all the external links. How do you do that?&lt;/p&gt;
&lt;h2 id="annotated-code"&gt;
	Annotated Code
	
	&lt;a class="hlink" href="#annotated-code"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The steps are simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parse the HTML with &lt;code&gt;golang.org/x/net/html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Walk the tree. The annotated code below implements a simple &lt;code&gt;Walk&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;Update the nodes. Do this in the callback of the &lt;code&gt;Walk&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;Render the modified HTML.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;38
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;39
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;40
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;41
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;42
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;43
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;44
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;45
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;46
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;47
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;48
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;49
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;50
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;51
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;52
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;53
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;54
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;55
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;56
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;57
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;58
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;59
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;60
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;61
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;62
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;63
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;64
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;65
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;66
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;67
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;68
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;69
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;70
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;71
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;72
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;73
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;74
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;75
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;76
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;77
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;78
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;79
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;80
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;81
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;82
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;83
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;84
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;85
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;86
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;87
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;bytes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;golang.org/x/net/html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;golang.org/x/net/html/atom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Error handling omitted for brevity.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;`&amp;lt;p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;	&amp;lt;a href=&amp;#34;https://example.com&amp;#34;&amp;gt;Not External&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;	&amp;lt;a href=&amp;#34;https://furqansoftware.com&amp;#34;&amp;gt;External&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;/p&amp;gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Parse the HTML.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;doc&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewReader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Walk the entire HTML tree.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Walk&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;doc&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Node&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Type&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ElementNode&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DataAtom&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;atom&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;A&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IsExternal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;GetAttr&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#75715e"&gt;// Set attributes on all anchors with external links.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;SetAttr&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;target&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;_blank&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;SetAttr&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;rel&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;noreferrer noopener&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Render the modified HTML.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Buffer&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Render&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;doc&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// &amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// 	&amp;lt;a href=&amp;#34;https://example.com&amp;#34;&amp;gt;Not External&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// 	&amp;lt;a href=&amp;#34;https://furqansoftware.com&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer noopener&amp;#34;&amp;gt;External&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// &amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Walk traverses the entire HTML tree and calls fn on each node.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Walk&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Node&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Node&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Each node has a pointer to its first child and next sibling. To traverse&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// all children of a node, we need to start from its first child and then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// traverse the next sibling until nil.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;FirstChild&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NextSibling&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Walk&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// GetAttr returns the attribute on a node by its key.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetAttr&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Node&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attr&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Key&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Val&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// SetAttr sets an attribute on a node.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SetAttr&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Node&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;val&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attr&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attr&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Key&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;a&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Val&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;val&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attr&lt;/span&gt; = append(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attr&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attribute&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Key&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Val&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;val&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// IsExternal returns true if url doesn&amp;#39;t have &amp;#34;https://example.com&amp;#34; as the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// prefix. You can do better than this.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IsExternal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; !&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HasPrefix&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;https://example.com&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="why-is-the-output-an-entire-html-document"&gt;
	Why Is The Output An Entire HTML Document?
	
	&lt;a class="hlink" href="#why-is-the-output-an-entire-html-document"&gt;&lt;img src="https://hjr265.me/link.svg" alt=""&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Any HTML you parse is treated as an entire document. But more often than not, when dealing with user-generated content, you are probably dealing with an HTML fragment.&lt;/p&gt;</description></item><item><title>Bump Calendar Versioning (CalVer) Bash Script for Git</title><link>https://hjr265.me/blog/bump-calver-bash-script-for-git/</link><pubDate>Mon, 05 Dec 2022 09:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/bump-calver-bash-script-for-git/</guid><description>&lt;p&gt;For Toph, SemVer didn&amp;rsquo;t make much sense. &lt;a href="https://calver.org/"&gt;CalVer&lt;/a&gt; did.&lt;/p&gt;
&lt;p&gt;Toph is closed-source and available to users as a SaaS. We are not trying to maintain or communicate backwards-compatible/incompatible changes to Toph with our users. With CalVer (short for Calender Versioning), the versioning would be tied to the release date.&lt;/p&gt;
&lt;p&gt;The pattern we wanted to use: &lt;code&gt;{YYYY}.{0M}.{SEQ}&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{YYYY}&lt;/code&gt;: Four-digit year.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{0M}&lt;/code&gt;: Zero-padded month.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{SEQ}&lt;/code&gt;: Starts from 0. Goes up by 1 for each bump. Resets after the end of the month.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example: &lt;code&gt;2022.12.3&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Single Node MongoDB Replica Set with Docker Compose</title><link>https://hjr265.me/blog/single-node-mongodb-replica-set-docker-compose/</link><pubDate>Sun, 04 Dec 2022 15:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/single-node-mongodb-replica-set-docker-compose/</guid><description>&lt;p&gt;Toph needs a few MongoDB features that only work in replica set mode. Take &lt;a href="https://www.mongodb.com/docs/manual/changeStreams/"&gt;Change Streams&lt;/a&gt;, for example. It is unavailable in standalone mode.&lt;/p&gt;
&lt;p&gt;To ease development, all of Toph&amp;rsquo;s application codebases come with Docker Compose files. With just a single &lt;code&gt;docker-compose up&lt;/code&gt;, I can have any of Toph&amp;rsquo;s applications running in a development environment. But having MongoDB start in replica set mode has been a bit of work.&lt;/p&gt;
&lt;p&gt;You see, simply starting three nodes and then going in to configure them to be in a replica set wasn&amp;rsquo;t something I wanted to do. I wanted only a single node to run, and I wanted it to start in replica mode, even if I was running &lt;code&gt;docker-compose up&lt;/code&gt; on a freshly installed machine.&lt;/p&gt;</description></item><item><title>Grid Chart for Toph's One Million Submissions</title><link>https://hjr265.me/blog/grid-chart-for-toph-s-million-submissions/</link><pubDate>Wed, 30 Nov 2022 09:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/grid-chart-for-toph-s-million-submissions/</guid><description>&lt;p&gt;Toph recently reached a milestone my family and I have been looking forward to. &lt;a href="https://blog.toph.co/spotlight/one-million-submissions/"&gt;One million submissions&lt;/a&gt;. It may be a small win, but it is that sweet reward of working on your favourite project.&lt;/p&gt;
&lt;p&gt;On the relevant Toph Blog post, we have a picture that is 3840×25982 pixels and has one million coloured squares on it. The squares represent the one million submissions and are green for correct (Accepted) submissions and red for any other incorrect (Wrong Answer, CPU Limit Exceeded, etc.) submissions.&lt;/p&gt;</description></item><item><title>Showing GitHub Stars With Static Site Generator Hugo</title><link>https://hjr265.me/blog/showing-github-stars-with-static-site-generator-hugo/</link><pubDate>Sun, 27 Nov 2022 20:13:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/showing-github-stars-with-static-site-generator-hugo/</guid><description>&lt;p&gt;Static site generators are one of my favourite things about the Internet. I remember when almost every website built around me was based on Joomla or WordPress. I dread that time.&lt;/p&gt;
&lt;p&gt;My website, which you are on right now, is built with Hugo. I have a page on this website listing some of &lt;a href="https://hjr265.me/projects/"&gt;my open-source projects&lt;/a&gt;. And I wanted an easy way to show the number of GitHub stars on my Hugo-based website for my open-source projects.&lt;/p&gt;</description></item><item><title>Passing A Vector of RedisModuleString to RedisModule_Call</title><link>https://hjr265.me/blog/passing-a-vector-of-redismodulestring-to-redismodule-call/</link><pubDate>Sat, 26 Nov 2022 08:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/passing-a-vector-of-redismodulestring-to-redismodule-call/</guid><description>&lt;p&gt;While building &lt;a href="https://github.com/hjr265/redis-too"&gt;Redis Too&lt;/a&gt; (a recommendation engine module for Redis), I spent a good bit of time (and strands of hair) figuring out how to pass a variable number of arguments to a Redis command like &amp;ldquo;SUNION&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The format specified of the &lt;a href="https://redis.io/docs/reference/modules/modules-api-ref/#RedisModule_Call"&gt;&lt;code&gt;RedisModule_Call&lt;/code&gt;&lt;/a&gt; function accepts &amp;lsquo;v&amp;rsquo; to denote &amp;ldquo;a vector of RedisModuleString&amp;rdquo;. But to use it, you need to pass two arguments to &lt;code&gt;RedisModule_Call&lt;/code&gt;: the array of &lt;code&gt;RedisModuleString&lt;/code&gt; and the size of the array.&lt;/p&gt;</description></item><item><title>Extracting Text From PDF Using Go</title><link>https://hjr265.me/blog/extracting-text-from-pdf-with-go/</link><pubDate>Mon, 14 Nov 2022 06:09:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/extracting-text-from-pdf-with-go/</guid><description>&lt;p&gt;TL;DR: Read all text values from PDF, ordered by Y-position. Make a state machine. The link to the complete code is at the end of the article.&lt;/p&gt;
&lt;p&gt;When it comes to extracting text from PDF, you will likely face one of these two scenarios:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The PDF you generate or export from some software where all the text is still text.&lt;/li&gt;
&lt;li&gt;The PDF you scan from a physical document, and what you have is an image in a PDF document.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If it is a scanned PDF, this is an Optical Character Recognition (OCR) task. You will need to use a tool like Tesseract to extract the text from the image.&lt;/p&gt;</description></item><item><title>Updating Creality CR-10 Smart Firmware</title><link>https://hjr265.me/blog/updating-creality-cr-10-smart-firmware/</link><pubDate>Thu, 21 Oct 2021 17:25:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/updating-creality-cr-10-smart-firmware/</guid><description>&lt;p&gt;There seems to be a lot of frustration with this 3D printer. And one of those frustration stems from how confusing the firmware update process is. The documentation is lacking in terms of some of the details.&lt;/p&gt;
&lt;p&gt;To keep things simple, here are the important bits:&lt;/p&gt;
&lt;p&gt;Updating the firmware of this 3D printer involves two separate steps. The first one is to update the firmware of the hardware. The second one is to update the firmware of the screen.&lt;/p&gt;</description></item><item><title>Stream Uploading Files to S3 with Object Writer</title><link>https://hjr265.me/blog/stream-uploading-files-s3-object-writer/</link><pubDate>Sun, 06 Jun 2021 09:50:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/stream-uploading-files-s3-object-writer/</guid><description>&lt;p&gt;The official AWS SDK provides an upload manager construct that allows you to upload to S3 from any &lt;code&gt;io.Reader&lt;/code&gt;. Using it is straightforward, that is until you need to create and upload a potentially large ZIP file.&lt;/p&gt;
&lt;p&gt;The solution: use the upload manager with a pipe.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Open a pipe.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;pr&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;pw&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Pipe&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;errch&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Upload from pr in a separate Go routine.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;s3manager&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewUploader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;awssess&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Upload&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;s3manager&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;UploadInput&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Bucket&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;brain-bucket&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Key&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;blobs/very-large.zip&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;pr&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;errch&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Create a ZIP writer around pw.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;zw&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;zip&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewWriter&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pw&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Add stuff to zip.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;zw&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;() &lt;span style="color:#75715e"&gt;// Finishes the ZIP.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;pw&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;() &lt;span style="color:#75715e"&gt;// Closes pw, marks EOF in pr.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;errch&lt;/span&gt; &lt;span style="color:#75715e"&gt;// If err == nil, success.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For convenience, I have wrapped this up in a Go package: &lt;a href="https://pkg.go.dev/github.com/hjr265/s3ow"&gt;github.com/hjr265/s3ow&lt;/a&gt; [ &lt;a href="https://github.com/hjr265/s3ow"&gt;GitHub&lt;/a&gt; ].&lt;/p&gt;</description></item><item><title>Using Language Servers with CodeMirror 6</title><link>https://hjr265.me/blog/codemirror-lsp/</link><pubDate>Sun, 28 Mar 2021 13:36:59 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/codemirror-lsp/</guid><description>&lt;p&gt;CodeMirror 6, a rewrite of the &lt;a href="https://codemirror.net/"&gt;CodeMirror&lt;/a&gt; editor, brings several improvements. &lt;a href="https://toph.co/"&gt;Toph&lt;/a&gt; has been using CodeMirror for its integrated code editor since its introduction.&lt;/p&gt;
&lt;p&gt;As CodeMirror 6 reached a stable interface with the promise of better touchscreen support, it was time for an upgrade! During which I wanted to introduce language server support.&lt;/p&gt;
&lt;p&gt;The goal was to provide code completion, diagnostics, and hover tooltips. And, CodeMirror 6 makes it easy to do all three.&lt;/p&gt;</description></item><item><title>Should I Even Eat That Onion?</title><link>https://hjr265.me/blog/should-i-even-eat-that-onion/</link><pubDate>Sun, 28 May 2017 00:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/should-i-even-eat-that-onion/</guid><description>&lt;p&gt;This is just a long overdue rant.&lt;/p&gt;
&lt;p&gt;A little over a year ago, I ordered a power bank from a very reputable online store. Compared to my previous experiences with other stores, they seemed super professional. I received a phone call from them to get the order confirmed, and a couple of days later they delivered it right to my doorstep.&lt;/p&gt;
&lt;p&gt;Amazing service! Except, what I received was a fake product.&lt;/p&gt;</description></item><item><title>Building a Recommendation Engine</title><link>https://hjr265.me/blog/building-a-recommendation-engine/</link><pubDate>Fri, 06 Feb 2015 00:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/building-a-recommendation-engine/</guid><description>&lt;p&gt;Ever wondered how a recommendation engine works? I did. Turns out, the mechanics involved in generating recommendations is actually pretty neat! You can learn more about it in the &lt;a href="http://www.toptal.com/algorithms/predicting-likes-inside-a-simple-recommendation-engine"&gt;article I wrote for Toptal on the anatomy of a simple recommendation engine&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>tar</title><link>https://hjr265.me/blog/tar/</link><pubDate>Tue, 26 Aug 2014 00:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/tar/</guid><description>&lt;p&gt;&lt;a href="http://xkcd.com/1168/"&gt;&lt;img src="tar.png" alt="xkcd: tar"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tar is a file format that allows you to archive files and directories while preserving flags and other file information. Tar is also the name of the Unix utility that manipulates these files and is also popular for being notoriously enigmatic.&lt;/p&gt;
&lt;p&gt;Did you know? Tar, the name, is derived from &amp;ldquo;tape archive&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But Ridwan, I don&amp;rsquo;t store my files on tapes. Do I need &lt;code&gt;tar&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, if you need to ask that question (and don&amp;rsquo;t care about helping Rob disarm the bomb), then the answer may just be &amp;ldquo;no&amp;rdquo;. But if you spend enough time in Unix-like systems, you are bound to come across a &amp;ldquo;.tar&amp;rdquo; file eventually.&lt;/p&gt;</description></item><item><title>Using an external USB drive for the root file system of a Raspberry Pi</title><link>https://hjr265.me/blog/using-an-external-usb-drive-for-the-root-file-system-of-a-raspberry-pi/</link><pubDate>Tue, 14 Jan 2014 00:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/using-an-external-usb-drive-for-the-root-file-system-of-a-raspberry-pi/</guid><description>&lt;p&gt;By design, a Raspberry Pi always requires an SD card to boot from. But one can still have its root partition located on an external storage device. Be it for reasons involving speed improvement, or avoid challenging the &lt;a href="http://en.wikipedia.org/wiki/Flash_memory#Write_endurance"&gt;write endurance&lt;/a&gt; of an SD card.&lt;/p&gt;
&lt;p&gt;The details in the following steps may vary based on the distribution of Linux being used, but the fundamental idea should be similar anyway:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Assuming a distribution of Linux is already installed on the SD card, use it to boot a Raspberry Pi up.&lt;/p&gt;</description></item><item><title>Hello World</title><link>https://hjr265.me/blog/hello-world/</link><pubDate>Sat, 28 Dec 2013 00:00:00 +0600</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/blog/hello-world/</guid><description>&lt;p&gt;Just a few hours ago I didn&amp;rsquo;t even know that I would suddenly have the urge to write. Heck! I didn&amp;rsquo;t even know that I would be creating a blog for that. But now that my first post is live, I guess I will have to make it a habit.&lt;/p&gt;
&lt;p&gt;The first post is definitely about saying &amp;ldquo;hello&amp;rdquo;. And I thought, what better way is there to say that than to say it in different languages: &lt;a href="https://gist.github.com/hjr265/8157156"&gt;Bash&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157098"&gt;C&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157194"&gt;C#&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157105"&gt;C++&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157113"&gt;Go&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157190"&gt;Java&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157127"&gt;JavaScript&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157164"&gt;Perl&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157146"&gt;PHP&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157172"&gt;Python&lt;/a&gt;, &lt;a href="https://gist.github.com/hjr265/8157179"&gt;VisualBasic&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Experience</title><link>https://hjr265.me/experience/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/experience/</guid><description/></item><item><title>Firefox's Screenshot Command</title><link>https://hjr265.me/notes/firefox-screenshot/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/notes/firefox-screenshot/</guid><description>&lt;p&gt;I needed to capture a HiDPI screenshot in Firefox without switching to a device with a HiDPI display. I was wondering if that was even possible. While searching for a how-to, I came across this &lt;code&gt;:screenshot&lt;/code&gt; web console helper function.&lt;/p&gt;
&lt;p&gt;How is it that I didn&amp;rsquo;t know about it for so many years? 🤯&lt;/p&gt;
&lt;p&gt;This function has been in &lt;a href="https://meyerweb.com/eric/thoughts/2018/08/24/firefoxs-screenshot-command-2018/"&gt;existence for about 7-8 years&lt;/a&gt;. And it has a bunch of handy options.&lt;/p&gt;</description></item><item><title>MTR: Not An Ordinary Network Diagnostic Tool</title><link>https://hjr265.me/notes/mtr/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/notes/mtr/</guid><description>&lt;p&gt;MTR combines the functionality of the traceroute and ping programs in a single network diagnostic tool. And, I am certain internet service providers (ISPs) of third-world countries hate it when customers discover this tool.&lt;/p&gt;
&lt;p&gt;I came across the tool again recently while investigating a WireGuard connection issue between an on-premises Toph server and one in Singapore.&lt;/p&gt;
&lt;p&gt;Was it our router? Was it Linode&amp;rsquo;s infrastructure? Was it our local ISP?&lt;/p&gt;
&lt;p&gt;A quick &lt;code&gt;mtr&lt;/code&gt; command solved the mystery:&lt;/p&gt;</description></item><item><title>Publications</title><link>https://hjr265.me/publications/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/publications/</guid><description/></item><item><title>Timeline</title><link>https://hjr265.me/timeline/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>m@hjr265.me (Mahmud Ridwan)</author><guid>https://hjr265.me/timeline/</guid><description/></item></channel></rss>