<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><id>https://qali.net/</id><title>Qasim Ali's Blog</title><updated>2023-08-24T18:23:51.072475+00:00</updated><author><name>Qasim Ali</name><email>me AT qali.net</email></author><link href="https://qali.net" rel="self"/><generator uri="https://lkiesow.github.io/python-feedgen" version="0.9.0">python-feedgen</generator><subtitle>Qasim Ali's Blog</subtitle><entry><id>https://qali.net/2023/08/24/toronto-niagara-route</id><title>Biking from Toronto to Niagara</title><updated>2023-08-24T18:23:51.083819+00:00</updated><content type="html">&lt;p&gt;Earlier in July I rode a 145km bike route from Toronto to Niagara. I made it to the end in one piece, though since it was pretty hot (25c+ with high humidity and very little cloud cover) I ended the ride feeling tired and dehydrated. That said, the ride was well worth the effort.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s an itinerary for anyone planning a similar trip.&lt;/p&gt;
&lt;div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0.5em;"&gt;
    &lt;img src="collage-port-credit.jpg" alt="" style="max-width: 45%"/&gt;
    &lt;img src="collage-burlington.jpg" alt="" style="max-width: 45%"/&gt;
    &lt;img src="collage-hamilton.jpg" alt="" style="max-width: 45%"/&gt;
    &lt;img src="collage-grimsby.jpg" alt="" style="max-width: 45%"/&gt;
    &lt;img src="collage-niagara.jpg" alt="" style="max-width: 45%"/&gt;
&lt;/div&gt;
&lt;h2&gt;Route Overview&lt;/h2&gt;
&lt;p&gt;&lt;img src="route-overview.png" alt="A map of the bike route running from western Etobicoke to Niagara falls" /&gt;&lt;/p&gt;
&lt;p&gt;The route starts on Lakeshore Road West near Etobicoke and follows the &lt;a href="https://waterfronttrail.org/places/communities/toronto/"&gt;Great Lakes Waterfront Trail&lt;/a&gt; west to Port Credit, Oakville, and Burlington. The route continues east along the trail to Grimsby, Dalhousie, and Niagara-on-the Lake. The route then follows the trail south to Niagara Falls.&lt;/p&gt;
&lt;p&gt;My ride was 145km long, and lasted almost 9h including breaks (moving time was 7h).&lt;/p&gt;
&lt;h2&gt;Gear&lt;/h2&gt;
&lt;p&gt;Wearing a backpack is doable but will be uncomfortable towards the end. Save your back and shoulders by using a saddlebag, pannier, or some other bike storage.&lt;/p&gt;
&lt;p&gt;Plan like you would for any 8-hour-long outdoor activity. An extra spare tire could come in handy, even if you have a tube patch kit.&lt;/p&gt;
&lt;p&gt;There will be one or two 50km long sections without stores or gas stations, so make sure you can carry enough food and water to last that long (and adjust your water requirements according to the temperature).&lt;/p&gt;
&lt;h2&gt;Part 1: Toronto to Burlington (40km, 40/145 km)&lt;/h2&gt;
&lt;p&gt;&lt;img src="route-1.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;The first section of the ride is mostly urban/suburban areas. Traffic can be dense. Food and water frequent.&lt;/p&gt;
&lt;p&gt;Follow Lakeshore West all the way until Burlington. This area is usually clear of traffic on weekends before 8am. Remember that you probably have over 5 hours of riding left once you reach Burlington, so ride at a slow pace.&lt;/p&gt;
&lt;h2&gt;Part 2: Burlington to Grimsby (30km, 70/145 km)&lt;/h2&gt;
&lt;p&gt;&lt;img src="route-2.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Suburban residential areas through to Grimsby, with increasingly long and frequent patches of farmland between QEW ramps. Stops for food and water 5-10km apart.&lt;/p&gt;
&lt;p&gt;Turn left at the Brant Street Pier and follow the mixed-use path south. Ride along the Waterfront Trail and follow the signs for the Canal Lift Bridge. The trail goes past the bridge then turns under it and rises to meet the road. After crossing the canal, get off the bridge and head north to the Breezeway Trail, and follow it east. The trail is a mixed-use path that travels through parks along the shore, so keep an eye out for pedestrians. The trail ends in a residential area north of the QEW.&lt;/p&gt;
&lt;p&gt;Follow the Waterfront Trail/bike route signs. These signs will take you east on the North Service Road along the QEW, occasionally turning off the road into residential areas to avoid highway onramps/offramps. North Service Road has a speed limit limit of 80kph and shoulders are nonexistent in some areas.&lt;/p&gt;
&lt;h2&gt;Part 3: Grimsby to Dalhousie (25km, 95/145km)&lt;/h2&gt;
&lt;p&gt;&lt;img src="route-3.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;A very long rural stretch. Mostly farmland and vineyards. Stops for food and water 10-15km apart, with stops in urban Dalhousie.&lt;/p&gt;
&lt;p&gt;Continue following the N Service Road until the 90 km mark, then turn left onto Lakeshore Rd W and follow it straight to downtown Port Dalhousie.&lt;/p&gt;
&lt;p&gt;This is where staying hydrated and fed becomes especially important. If you were not eating something and drinking a bottle of water every hour, it&amp;#8217;s likely that you will start to hit the wall at this point. Make sure you&amp;#8217;re also having electrolytes otherwise you will probably start to suffer during this section.&lt;/p&gt;
&lt;p&gt;It will be hard to find food and water here, as most of the route is farmland. There is very little shade and you will spend most of your time surrounded by concrete, so staying cool is important.&lt;/p&gt;
&lt;p&gt;Port Dalhousie is a good spot to refuel and grab extra food/water for the next section.&lt;/p&gt;
&lt;h2&gt;Part 4: Dalhousie to Niagara-on-the Lake (NOTL) (20km, 115/145km)&lt;/h2&gt;
&lt;p&gt;&lt;img src="route-4.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Suburban/industrial until the Welland Canal, then farmland again until downtown Niagara-on-the-Lake (NOTL). No food and water between Dalhouse and NOTL.&lt;/p&gt;
&lt;p&gt;Turn south out of Dalhousie on Lakeport until you get to Lakeshore Rd then follow it west along the shoreline, ending in NOTL.&lt;/p&gt;
&lt;p&gt;Food and water are also important here. There is very little shade until the end. NOTL is a good spot to refuel.&lt;/p&gt;
&lt;p&gt;Note that the directions for the past 55km are almost identical. These parts of the route are monotonous and flat. I started to overheat and think about stopping at this point. Reminding yourself why you&amp;#8217;re doing this ride (and having a good reason) might help you continue.&lt;/p&gt;
&lt;p&gt;As you approach NOTL, the route turns onto smaller rural roads. Most drivers gave me room when passing, though there is often no shoulder (unlike the service road). Road conditions ranged from great (brand new asphalt) to poor (2km of gravel-like &lt;a href="https://en.wikipedia.org/wiki/Pavement_milling"&gt;milled road&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Part 5: Niagara-on-the-Lake to Niagara Falls (30km, 145/145km)&lt;/h2&gt;
&lt;p&gt;&lt;img src="route-5.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Rural immediately after exiting NOTL. No food and water for 20km between NOTL and Niagara Falls.&lt;/p&gt;
&lt;p&gt;Exit NOTL and turn onto Niagara Parkway Trail. Follow the trail south until you get to the falls. The route passes by Niagara GO at km 137, continues south for 4km until the falls, then turns back around and ends at the GO station.&lt;/p&gt;
&lt;p&gt;After 115km of flat land, the final stretch to the falls is mostly uphill. The first 10km are a gentle uphill slope, followed by a 5% climb for 1km near Brock&amp;#8217;s Monument. The trail will be busy, with lots of cyclists and pedestrians. I had to bail out early and go directly to the GO station to catch the train back.&lt;/p&gt;
&lt;p&gt;Grab some food, rehydrate, and enjoy the 2h drive/train ride back to Toronto. If it&amp;#8217;s especially hot out you may have mild heat exhaustion, and feel tired tomorrow. Spend the next day celebrating the fact that you travelled all the way to Niagara on your own two legs.&lt;/p&gt;
&lt;p&gt;&lt;img src="efficiency.jpg" alt="A chart displaying the transportation energy efficiency of various animals and human modes of transport. Bicycling is, by a large margin, the most efficient method of transportation in the chart." /&gt;&lt;/p&gt;
</content><link href="https://qali.net/2023/08/24/toronto-niagara-route" rel="alternate"/><summary>&lt;p&gt;A relatively straightforward route.&lt;/p&gt;
</summary><published>2023-08-24T00:00:00-04:00</published></entry><entry><id>https://qali.net/2020/08/21/300-wands</id><title>D&amp;D Oneshot: 300 Wands</title><updated>2023-08-24T18:23:51.083790+00:00</updated><content type="html">&lt;p&gt;As a GM, preparing for a session at the last minute sucks, especially if you&amp;#8217;re not sure how many people will be playing. Plus, if you&amp;#8217;re going to play with people who are new to D&amp;amp;D or TTRPGs, that eats away from your precious one-shot session time. So, I stole (as all GMs do) a setting called the &lt;a href="https://old.reddit.com/r/DnDGreentext/comments/56owc3/the_commoner_curse_of_300_wands/"&gt;Commoner Curse of 300 Wands&lt;/a&gt;. This session can be played with any number of players, and with minimal onboarding and teaching required. It can be roleplay-heavy, or combat-heavy, depending on how your players roll. It&amp;#8217;s goofy, casual, and a great story to keep in your back pocket.&lt;/p&gt;
&lt;p&gt;The players are paid by a wizard&amp;#8217;s assistant to perform some magical experimentation. The assistant shows the party a barrel of 300 unlabeled magical wands, and asks them to identify their effects by casting them at stuff (go outside the town first, though!). That&amp;#8217;s it.&lt;/p&gt;
&lt;p&gt;Everything else can be (and will have to be) improvised on the spot. I start off by drawing a very basic compass map at the start of the session, so that players can have places to go and test their wands. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                       mountains
                  (frost dragon here)

                           N
                           ^
                           |
   waterfalls       W &amp;lt;- town -&amp;gt; E         forest
(sunken treasure)          |          (bandit hideout here)
                           v
                           S

                     silver mines
                    (ongoing theft)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each of these directions has a &amp;#8216;hook&amp;#8217; that players can sink their teeth into. You don&amp;#8217;t necessarily need to give them the hooks (some of them can be a fun surprise), but I give a few of them to the party via an in-town job board. The players can use a job from there to test their wands, and kill two birds with one stone.&lt;/p&gt;
&lt;p&gt;In terms of gameplay, every time a player casts a wand at a target the GM rolls 1d10,000 to pull a random magical effect from the &lt;a href="https://www.reddit.com/r/Roll20/comments/a1d1le/net_libram_of_random_magical_effects_v2_as_a/"&gt;Net Libram of Random Magical Effects&lt;/a&gt; (a list of spells from a Pathfinder module). If the effect is boring or not applicable, you can roll again. Some of these effects take a while to become visible or known. For example, a player may be confronted by a religious cult who claims that the player is their messiah in the future. I tell the players that they either don&amp;#8217;t notice any effects, or give them a subtle hint (&lt;em&gt;you feel somewhat divine&lt;/em&gt;). This gets fun when players, thinking that most wands are safe, spam multiple effects at the same time.&lt;/p&gt;
&lt;p&gt;Practically speaking, rolling and tracking effects can get tedious. I wrote a &lt;a href="https://qali.net/300wands"&gt;web app&lt;/a&gt; to make this go smoother. You add your players to the app, and click a button to add a random effect to their character. Any rolls described in the spell effect (&lt;em&gt;player grows 1d4 additional fingers on their hand&lt;/em&gt;) are automatically rolled for you.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So, that&amp;#8217;s the setup for the story. What happens next?&lt;/p&gt;
&lt;p&gt;The beauty of this setup is that since the spells are so chaotic, they end up driving the story! Players head north to the mountains in order to slay an evil ice dragon, and fight bandits and beasts along the way. During the journey, they use a wand which summons a giant demon in the nearest town. They also use a wand which causes a death cult, devoted to one of the players, to form across the continent. The players don&amp;#8217;t see this just yet. As they crest the mountain peak, they see their hometown engulfed in flames as a 20-story monster stomps on it, and see tens of thousands of pilgrims marching up the mountain towards them, eager to meet their prophet.&lt;/p&gt;
&lt;p&gt;What do the players do? What do the cultists want? What happens to the town? &lt;em&gt;What does the demon want?&lt;/em&gt; You, as the GM, serve to take the chaos created by the wands and funnel it into an enjoyable story. Take a look at what your players are like, what they enjoy, and try to understand how they&amp;#8217;re reading the events in the story so far. Then use that knowledge to tailor the story for maximum fun.&lt;/p&gt;
</content><link href="https://qali.net/2020/08/21/300-wands" rel="alternate"/><summary>&lt;p&gt;A silly, low-effort, low-prep campaign setting for impromptu sessions&lt;/p&gt;
</summary><published>2020-08-21T00:00:00-04:00</published></entry><entry><id>https://qali.net/2020/08/15/spammers-veto</id><title>Spammer&amp;#8217;s Veto</title><updated>2023-08-24T18:23:51.083760+00:00</updated><content type="html">&lt;p&gt;American law has the concept of a &lt;a href="https://en.wikipedia.org/wiki/Heckler%27s_veto"&gt;heckler&amp;#8217;s veto&lt;/a&gt;. It refers to the ability of hecklers (people who respond negatively to someone&amp;#8217;s speech) to shut down the offending speech in the first place. For example, a march may be shut down because the government fears that counterprotesters will disturb the peace.&lt;/p&gt;
&lt;p&gt;This reminded me about spam. I&amp;#8217;d like to host web sites and services for people. I&amp;#8217;d like to do it for free (after all, my computer isn&amp;#8217;t serving anyone 99% of the time anyways). But I always get discouraged when I realize that if people started sharing my site, then it&amp;#8217;s just a matter of time until spammers, malware distributors, and people trying to share other illicit material take it over. Without any sort of content filtering or moderation, links to my site will get quickly blocked by mail servers and social media sites.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;d like to use the knowledge and money I have at my disposal to give people a place to hang out online, but I just can&amp;#8217;t, because spammers have the last word. So for now, it&amp;#8217;s just me posting here.&lt;/p&gt;
</content><link href="https://qali.net/2020/08/15/spammers-veto" rel="alternate"/><published>2020-08-15T00:00:00-04:00</published></entry><entry><id>https://qali.net/2020/06/11/mastodon</id><title>Online Communities, Identity, and Association</title><updated>2023-08-24T18:23:51.083723+00:00</updated><content type="html">&lt;p&gt;&lt;a href="https://joinmastodon.org"&gt;Mastodon&lt;/a&gt; is a social networking software (like Frendica) that allows admins to run individual network instances for users. Think of Twitter, but people&amp;#8217;s accounts exist on a bunch of different servers instead of just one (replicated) datacenter. The individual servers then talk to each other, so that &lt;code&gt;@tabby@cats.space&lt;/code&gt; can follow and chat with &lt;code&gt;@basset@dog.town&lt;/code&gt;.&lt;/p&gt;
&lt;aside&gt;mastodon.social isn&amp;#8217;t even the largest instance on the network. It&amp;#8217;s still smaller than Pawoo, &lt;a href="https://medium.com/@EthanZ/mastodon-is-big-in-japan-the-reason-why-is-uncomfortable-684c036498e5"&gt;a network which is practically isolated by blocklists throughout the fediverse&lt;/a&gt;.&lt;/aside&gt;
&lt;p&gt;In many ways, Mastodon is literally just Twitter. The most-popular instance, &lt;a href="mastodon.social"&gt;mastodon.social&lt;/a&gt;, is a general-purpose instance  with millions of users and no shared interest. But there are a lot of smaller instances on the network as well. There are instances for hobbies like bicycling, photography, and (as expected) a million tech-related niches. There are instances for specific political movements or economic systems. There are instances for marginalized groups, for hate, or for specific parts of the world. These servers make up the &lt;a href="https://en.wikipedia.org/wiki/Long_tail"&gt;long tail&lt;/a&gt; users of the network, with some instances hosting less than a dozen people.&lt;/p&gt;
&lt;p&gt;When a user signs up at an instance, they get the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A handle&lt;/li&gt;
&lt;li&gt;A view of the instance&amp;#8217;s local timeline&lt;/li&gt;
&lt;li&gt;The ability to interact with people on the instance, and (in most cases) the ability to interact with people on other instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But there&amp;#8217;s something else that instance members get: association. When you sign up as &lt;code&gt;@fixie@cycling.club&lt;/code&gt; and interact with other people on the network, you implicitly communicate the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You like bicycling&lt;/li&gt;
&lt;li&gt;You like the people on cycling.club&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;are like&lt;/strong&gt; the people on cycling.club&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Joining an instance strongly ties your online identity to that instance. Though it may be unfair, you will be judged by the second half of your handle if your instance has a reputation in the network. If the people on your instance are hateful enough, your instance may end up on one of many popular instance blocklists, and you lose the ability to federate with large chunks of the network. Users who don&amp;#8217;t want this to happen need to moderate their own communities and protect them from bad actors.&lt;/p&gt;
&lt;p&gt;This is one aspect of Mastodon that I think is not communicated well to newcomers. If you want to stick to having one account on one instance, you need to help moderate the community and keep awful stuff out of it, because &lt;strong&gt;you will be judged by how your worst community members act&lt;/strong&gt;.&lt;/p&gt;
</content><link href="https://qali.net/2020/06/11/mastodon" rel="alternate"/><summary>&lt;p&gt;Some notes on Mastodon&lt;/p&gt;
</summary><published>2020-06-11T00:00:00-04:00</published></entry><entry><id>https://qali.net/2020/04/23/html</id><title>Back to basics</title><updated>2023-08-24T18:23:51.083652+00:00</updated><content type="html">&lt;p&gt;I switched away from hugo and threw together my own blog generator thing. It&amp;#8217;s brittle and assumes a lot of things about where files will be, but I&amp;#8217;ll deal with fixing it if it breaks.&lt;/p&gt;
&lt;p&gt;I realized that writing was such a pain because I&amp;#8217;d need to download a specific version of hugo (yay breaking changes), re-remember what each directory and directive and markup function does, &lt;em&gt;etc etc&lt;/em&gt;. It sucks and it&amp;#8217;s awful on mobile. The script just loops through a bunch of markdown files, renders them, and inserts the HTML into a mustache template (&lt;a href="html.md"&gt;take a look at the markdown for this post&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;One new thing I added is a &lt;a href="/micro"&gt;micro blog&lt;/a&gt; which is basically just a markdown file rendered in a minimal template. It&amp;#8217;s low-effort, low-quality, and kind of reminds me of sites on the &lt;a href="https://tilde.town/"&gt;tildeverse&lt;/a&gt;. I think I&amp;#8217;m end up putting more stuff there since writing a &lt;strong&gt;blog post&lt;/strong&gt; is a lot more work than typing out random nonsense. Maybe I&amp;#8217;ll just post photos here.&lt;/p&gt;
</content><link href="https://qali.net/2020/04/23/html" rel="alternate"/><summary>&lt;p&gt;Wtf is an html&lt;/p&gt;
</summary><published>2020-04-23T00:00:00-04:00</published></entry><entry><id>https://qali.net/2020/01/18/rant-monospace</id><title>Why&amp;#8217;s it gotta be a grid</title><updated>2023-08-24T18:23:51.083613+00:00</updated><content type="html">&lt;p&gt;As someone who voluntarily (!) uses Linux on the desktop and voluntarily (!!) browses Linux-related message boards, I see people arguing that an 80x24 monochrome character grid and an ANSI keyboard are the pinnacle of human-computer interaction, and that anyone who wants more is a normie who should go back to Facebook. I could rant about a lot, but let’s focus on letters.&lt;/p&gt;
&lt;p&gt;I really don’t get it. Printed text has been variable-width for hundreds of years. We grow up reading variable-width lettering. Newspapers, books, magazines, almost every form of written text has formatting. When I open a man page and see an unending, unbroken grid of characters, my eyes glaze over. I’m just trying to figure out why grep doesn’t find the thing I’m grepping for, why is this 5000-page infodump so hard to skim? Why can’t I find a nicely-formatted document with this info, with properly-sized headers and margins that is easier to read?&lt;/p&gt;
&lt;p&gt;Of course, I’m a casual who isn’t worthy of the genius that is The Unix Design. &lt;a href="https://practicaltypography.com/monospaced-fonts.html"&gt;Maybe everyone who complains about this sort of stuff is just wrong&lt;/a&gt;. But I would ask developers to consider why computers exist. Computers were created as a tool to make humans’ lives easier. We use tools to do things that we can’t (or don’t want to) do. When a tool becomes faster or easier to use, we all win.&lt;/p&gt;
&lt;p&gt;So if a tool is designed in a way that &lt;a href="https://journals.sagepub.com/doi/pdf/10.1177/001872088302500303"&gt;the majority of people have difficulty using&lt;/a&gt;, and said annoyances have no discernable benefit for existing besides inertia, can’t we make the tool easier to use?&lt;/p&gt;
</content><link href="https://qali.net/2020/01/18/rant-monospace" rel="alternate"/><summary>&lt;p&gt;I’d just like to interject for a moment.&lt;/p&gt;
</summary><published>2020-01-18T00:00:00-05:00</published></entry><entry><id>https://qali.net/2020/01/16/failing-with-abandon</id><title>Failing with Abandon</title><updated>2023-08-24T18:23:51.083571+00:00</updated><content type="html">&lt;p&gt;&lt;a href="http://mindingourway.com/failing-with-abandon/"&gt;Failing with Abandon - Nate Soares&lt;/a&gt; (&lt;a href="https://web.archive.org/web/20190428081915/http://mindingourway.com/failing-with-abandon/"&gt;archive&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a short public service announcement: &lt;em&gt;you don&amp;#8217;t have to fail with abandon.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Say you&amp;#8217;re playing Civilization, and your target is to get to sleep before midnight, and you check the clock, and it&amp;#8217;s already 12:15. If that happens, you don&amp;#8217;t have to say &amp;quot;too late now, I already missed my target&amp;quot; and then keep playing until 4 in the morning.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;Over and over, I see people set themselves a target, miss it by a little, and then throw all restraint to the wind. &amp;quot;Well,&amp;quot; they seem to think, &amp;quot;willpower has failed me; I might as well over-indulge.&amp;quot; I call this pattern &amp;quot;failing with abandon.&amp;quot;&lt;/p&gt;
&lt;p&gt;But you don&amp;#8217;t have to fail with abandon. When you miss your targets, you&amp;#8217;re allowed to say &amp;quot;dang!&amp;quot; and then &lt;em&gt;continue trying to get as close to your target as you can.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content><link href="https://qali.net/2020/01/16/failing-with-abandon" rel="alternate"/><summary>&lt;p&gt;“This is a short public service announcement: you don’t have to fail with abandon.”&lt;/p&gt;
</summary><published>2020-01-16T00:00:00-05:00</published></entry><entry><id>https://qali.net/2019/12/12/ghost-stories</id><title>Reviews: Collected Ghost Stories (M.R. James), Kwaidan (Lafcadio Hearn)</title><updated>2023-08-24T18:23:51.083532+00:00</updated><content type="html">&lt;div style="float:left; margin-right: 1em"&gt;
	&lt;img src="ghost-stories.jpg" alt="Collected Ghost Stories book cover"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.goodreads.com/book/show/650775.Collected_Ghost_Stories"&gt;Collected Ghost Stories&lt;/a&gt;&lt;/em&gt; (1931) is an anthology of Victorian-era supernatural stories, written by &lt;a href="https://en.wikipedia.org/wiki/M._R._James"&gt;M.R. James&lt;/a&gt;. I picked up this book because of its reviews, and I was not disappointed. For a book written almost 90 years ago, it&amp;#8217;s surprisingly accessible. You might need a dictionary for a few words, and the &lt;em&gt;&amp;#8216;British archaeologist visiting scary foreign lands&amp;#8217;&lt;/em&gt; theme has since been played to death, but the important bits, the scary bits, still work well.&lt;/p&gt;
&lt;p&gt;The stories are slow burns, and leave a lot to the reader&amp;#8217;s imagination. This is what makes these stories so effective. The narrator visits an old European town, searching for ancient Catholic Church manuscripts, and they discover a horrific secret in the process. They find their host&amp;#8217;s disfigured body in the hallway the next morning. Or the protagonist unearths an ancient artifact with the help of their friend, who disappears the next day, leaving only a chilling note behind.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s up to you to fill in the blanks. Maybe some things don&amp;#8217;t have an explanation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It was a high, thin voice that they heard, and it seemed dry, as if from long disuse. Of words or tune there was no question. It went sailing up to a surprising height, and was carried down with a despairing moan as of a winter wind in a hollow chimney, or an organ whose wind fails suddenly. It was a really horrible sound, and Anderson felt that if he had been alone he must have fled for refuge and society to some neighbour bag-man&amp;#8217;s room.&lt;/p&gt;
&lt;p&gt;The landlord sat open-mouthed.&lt;/p&gt;
&lt;p&gt;Just then came an impatient knock at the door, and the knocker entered, without waiting to be asked. It was the lawyer, in deshabille and very rough-haired; and very angry he looked.&lt;/p&gt;
&lt;p&gt;&amp;#8216;I beg pardon, sir,&amp;#8217; he said, &amp;#8216;but I should be much obliged if you would kindly desist &amp;#8212;&amp;#8217;&lt;/p&gt;
&lt;p&gt;Here he stopped, for it was evident that neither of the persons before him was responsible for the disturbance; and after a moment&amp;#8217;s lull it swelled forth again more wildly than before.&lt;/p&gt;
&lt;p&gt;&amp;#8216;But what in the name of Heaven does it mean?&amp;#8217; broke out the lawyer. &amp;#8216;Where is it? Who is it? Am I going out of my mind?&amp;#8217;&lt;/p&gt;
&lt;p&gt;&amp;#8216;Surely, Herr Jensen, it comes from your room next door? Isn&amp;#8217;t there a cat or something stuck in the chimney?&amp;#8217;&lt;/p&gt;
&lt;p&gt;This was the best that occurred to Anderson to say, and he realised its futility as he spoke; but anything was better than to stand and listen to that horrible voice, and look at the broad, white face of the landlord, all perspiring and quivering as he clutched the arms of his chair.&lt;/p&gt;
&lt;p&gt;&amp;#8216;Impossible,&amp;#8217; said the lawyer, &amp;#8216;impossible. There is no chimney. I came here because I was convinced the noise was going on here. It was certainly in the next room to mine.&amp;#8217;&lt;/p&gt;
&lt;p&gt;&amp;#8216;Was there no door between yours and mine?&amp;#8217; said Anderson eagerly.&lt;/p&gt;
&lt;p&gt;&amp;#8216;No, sir,&amp;#8217; said Herr Jensen, rather sharply. &amp;#8216;At least, not this morning.&amp;#8217;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Favourite stories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Mezzotint&lt;/li&gt;
&lt;li&gt;A School Story&lt;/li&gt;
&lt;li&gt;Number 13&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.gutenberg.org/ebooks/1210"&gt;Kwaidan: Stories and Studies of Strange Things&lt;/a&gt;&lt;/em&gt; (1904) is another 18th-century supernatural story collection, except &lt;a href="https://en.wikipedia.org/wiki/Lafcadio_Hearn"&gt;Lafcadio Hearn&lt;/a&gt; focuses on traditional Japanese stories. Some stories probably require you to understand Japanese traditions, and others aren&amp;#8217;t scary or unsettling, just strange. But there are some gems in there. There&amp;#8217;s something captivating about reading a ghost story from the Edo period. Hearn also thankfully includes translations for many Japanese words in the book, along with explanatory footnotes for each story.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s no Stephen King novel, but this book will creep you out if you let it. Give it a shot, since it&amp;#8217;s free!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After dark the priest and the acolyte went away; and Hoichi seated himself on the verandah, according to the instructions given him. He laid his biwa on the planking beside him, and, assuming the attitude of meditation, remained quite still,&amp;#8212;taking care not to cough, or to breathe audibly. For hours he stayed thus.&lt;/p&gt;
&lt;p&gt;Then, from the roadway, he heard the steps coming. They passed the gate, crossed the garden, approached the verandah, stopped&amp;#8212;directly in front of him.&lt;/p&gt;
&lt;p&gt;&amp;quot;Hoichi!&amp;quot; the deep voice called. But the blind man held his breath, and sat motionless.&lt;/p&gt;
&lt;p&gt;&amp;quot;Hoichi!&amp;quot; grimly called the voice a second time. Then a third time&amp;#8212;savagely:&amp;#8212;&lt;/p&gt;
&lt;p&gt;&amp;quot;Hoichi!&amp;quot;&lt;/p&gt;
&lt;p&gt;Hoichi remained as still as a stone,&amp;#8212;and the voice grumbled:&amp;#8212;&lt;/p&gt;
&lt;p&gt;&amp;quot;No answer!&amp;#8212;that won&amp;#8217;t do!&amp;#8230; Must see where the fellow is.&amp;quot;&amp;#8230;&lt;/p&gt;
&lt;p&gt;There was a noise of heavy feet mounting upon the verandah. The feet approached deliberately,&amp;#8212;halted beside him. Then, for long minutes,&amp;#8212;during which Hoichi felt his whole body shake to the beating of his heart,&amp;#8212;there was dead silence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Favourite stories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gutenberg.org/files/1210/1210-h/1210-h.htm#oshidori"&gt;Oshidori&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gutenberg.org/files/1210/1210-h/1210-h.htm#mujina"&gt;Mujina&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gutenberg.org/files/1210/1210-h/1210-h.htm#mujina"&gt;A Dead Secret&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><link href="https://qali.net/2019/12/12/ghost-stories" rel="alternate"/><summary>&lt;p&gt;Spooky scary books send shivers down their spines&lt;/p&gt;
</summary><published>2019-12-12T00:00:00-05:00</published></entry><entry><id>https://qali.net/2019/10/23/gardencast</id><title>dumb funny stuff</title><updated>2023-08-24T18:23:51.083490+00:00</updated><content type="html">&lt;div class="yt-container"&gt;&lt;iframe src="//www.youtube.com/embed/MASjtPaOdgg" frameborder="0" allowfullscreen class="yt"&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p&gt;I think there might be something wrong with my sense of humour. There&amp;#8217;s just something funny about people watching the same 30-second Olive Garden ad on loop for half an hour. My current podcast rotation includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.worstideaofalltime.com/"&gt;The worst idea of all time&lt;/a&gt;: a podcast where 2 kiwis watch the same movie every week for a year&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wgirny.libsyn.com/"&gt;We&amp;#8217;ll Get It Right Next Year: An Adventure in Cinema&lt;/a&gt;: a podcast where the hosts pick a movie they&amp;#8217;ve never seen before and guess the plot based only on the title. they repeat this for an entire year.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.tilldeathdousblart.com/"&gt;Till Death Do Us Blart&lt;/a&gt;: a podcast where the worst idea guys meet up every Thanksgiving with the &lt;a href="https://www.themcelroy.family/"&gt;McElroy guys&lt;/a&gt; and watch &lt;em&gt;Paul Blart: Mall Cop 2&lt;/em&gt;. This will be repeated until the end of time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#8217;s not like this sort of dumb joke hasn&amp;#8217;t been done before:&lt;/p&gt;
&lt;div class="yt-container"&gt;&lt;iframe src="//www.youtube.com/embed/gHdDxKy2QW0" frameborder="0" allowfullscreen class="yt"&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p&gt;But no studio exec would have financed &lt;em&gt;The Holy Grail&lt;/em&gt; if it was 75 minutes of Lancelot running at the camera. But thanks to the internet, someone can make that movie and share it with the rest of the world without paying a cent. You could probably even get paid for it via Patreon.&lt;/p&gt;
&lt;p&gt;This post has gotten away from me, but I guess it&amp;#8217;s just cool to think about how the internet has allowed people to express their creativity without limits. I love the fact that &lt;a href="https://www.youtube.com/watch?v=jE4RoVwaQMY"&gt;someone&amp;#8217;s frame-by-frame analyis of the Emoji Movie trailer&lt;/a&gt; has over 4 million views.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Side note: &lt;a href="https://www.youtube.com/user/wolfout77/"&gt;WolfoutTV&lt;/a&gt;, one of my favourite channels on Youtube, produces a full-length sketch comedy show every week. &lt;em&gt;The Last Show On Earth&lt;/em&gt; has geniuine creativity and effort behind it, and the canned laughter and corny jokes only make it better. Go check it out.&lt;/p&gt;
</content><link href="https://qali.net/2019/10/23/gardencast" rel="alternate"/><summary>&lt;p&gt;&amp;quot;so olive garden&amp;#8217;s keeping the peace in this family&amp;quot;&lt;/p&gt;
</summary><published>2019-10-23T00:00:00-04:00</published></entry><entry><id>https://qali.net/2019/09/11/gandi-livedns</id><title>Dynamic DNS with Gandi</title><updated>2023-08-24T18:23:51.083450+00:00</updated><content type="html">&lt;p&gt;I use &lt;a href="https://gandi.net"&gt;Gandi&lt;/a&gt; for my domain names. For a while, I used a VPS to host all my sites, but I ran into technical issues (it was OpenVZ-based so the kernel was ancient) and after I upgraded my home internet, I decided to put my bandwidth to use and host everything at home.&lt;/p&gt;
&lt;p&gt;Like many other North Americans, my ISP doesn&amp;#8217;t give me a static IP, so this is an issue. You can use dynamic DNS providers like &lt;a href="https://noip.com"&gt;noip&lt;/a&gt; or &lt;a href="https://afraid.org"&gt;afraid.org&lt;/a&gt;, but then you&amp;#8217;re stuck with a subdomain on one of their owned domain names. Fortunately, Gandi has something called &lt;a href="https://doc.livedns.gandi.net/"&gt;LiveDNS&lt;/a&gt;, which provides an HTTP REST API for modifying zonefiles. Here&amp;#8217;s how I&amp;#8217;m using their API to point my DNS records at my dynamic IP.&lt;/p&gt;
&lt;p&gt;First, follow the instructions and visit the &lt;a href="https://account.gandi.net/"&gt;&amp;#8216;Security&amp;#8217;&lt;/a&gt; page to get an API key. This key will need to be provided in the &lt;code&gt;X-Api-Key&lt;/code&gt; header of all your requests. Next, figure out the URL for your site&amp;#8217;s A (or AAAA) record.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Fetch all zones, and select the zone for your domain. Copy the &lt;code&gt;zone_records_href&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ curl -H &amp;quot;X-Api-Key: YOUR_API_KEY_HERE&amp;quot; https://dns.api.gandi.net/api/v5/zones
 [
   {
     &amp;quot;zone_records_href&amp;quot;: &amp;quot;https://dns.api.gandi.net/api/v5/zones/......../records&amp;quot;,
     ...
     &amp;quot;name&amp;quot;: &amp;quot;qali.net&amp;quot;
   }
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List all records for your domain&amp;#8217;s zonefile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ curl -H &amp;quot;X-Api-Key: YOUR_API_KEY_HERE&amp;quot; https://dns.api.gandi.net/api/v5/zones/......../records 
 [
   {
     &amp;quot;rrset_type&amp;quot;: &amp;quot;A&amp;quot;,
     &amp;quot;rrset_ttl&amp;quot;: 10800,
     &amp;quot;rrset_name&amp;quot;: &amp;quot;@&amp;quot;,
     &amp;quot;rrset_href&amp;quot;: &amp;quot;https://dns.api.gandi.net/api/v5/zones/......../records/%40/A&amp;quot;,
     &amp;quot;rrset_values&amp;quot;: [
       &amp;quot;1.2.3.4&amp;quot;
     ]
   },
   {
     &amp;quot;rrset_type&amp;quot;: &amp;quot;AAAA&amp;quot;,
     &amp;quot;rrset_ttl&amp;quot;: 1800,
     &amp;quot;rrset_name&amp;quot;: &amp;quot;@&amp;quot;,
     &amp;quot;rrset_href&amp;quot;: &amp;quot;https://dns.api.gandi.net/api/v5/zones/......../records/%40/AAAA&amp;quot;,
     &amp;quot;rrset_values&amp;quot;: [
       &amp;quot;i:wish:i:had:an:ipv6:address&amp;quot;
     ]
   }
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that you have a URL for your &lt;code&gt;A&lt;/code&gt;/&lt;code&gt;AAAA&lt;/code&gt; records, you just need to &lt;code&gt;PUT&lt;/code&gt; to their URLs. The payload for a given name/record type pair is a TTL and value (make sure you choose a low TTL, as this determines the maximum possible downtime for your site once your IP changes):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ curl -X PUT -d '{&amp;quot;rrset_ttl&amp;quot;: 600, &amp;quot;rrset_values&amp;quot;: [&amp;quot;1.1.1.1&amp;quot;]}' -H 'X-Api-Key: YOUR_API_KEY_HERE' https://dns.api.gandi.net/api/v5/zones/......../records/%40/A
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we know the URL of the record to update, we can write a script that, when run, &lt;a href="https://ifconfig.me"&gt;fetches our current IP&lt;/a&gt; and updates the record accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash

api_key_header=&amp;quot;X-Api-Key: UPDATEME&amp;quot;
record_url=&amp;quot;https://dns.api.gandi.net/api/v5/zones/......../records/%40/A&amp;quot;
curr_ip=$(curl ifconfig.me)
payload=&amp;quot;{\&amp;quot;rrset_ttl\&amp;quot;: 600, \&amp;quot;rrset_values\&amp;quot;: [\&amp;quot;$curr_ip\&amp;quot;]}&amp;quot;

echo payload: $payload

curl -X PUT -H &amp;quot;Content-Type: application/json&amp;quot; -H &amp;quot;$api_key_header&amp;quot; -d &amp;quot;$payload&amp;quot; $record_url
if [ $? -ne 0 ]; then
    echo POST failed
    exit 1
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this on a schedule can be done with cron, or with &lt;a href="https://wiki.archlinux.org/index.php/Systemd/Timers"&gt;systemd timers&lt;/a&gt;. Make sure that the timer frequency matches the TTL used for your DNS record. Also, keep your API key safe. &lt;a href="https://github.com/search?utf8=%E2%9C%93&amp;amp;q=%22BEGIN+RSA+PRIVATE+KEY%22+path%3A.ssh%2F&amp;amp;type=Code&amp;amp;ref=advsearch&amp;amp;l=&amp;amp;l="&gt;Don&amp;#8217;t add it to a VCS&lt;/a&gt;, and make sure that the updater script can only be read by the user scheduled to run it (i.e. &lt;code&gt;chmod go-rwx update-dns.sh&lt;/code&gt;).&lt;/p&gt;
</content><link href="https://qali.net/2019/09/11/gandi-livedns" rel="alternate"/><summary>&lt;p&gt;I use Gandi for my domain names. For a while, I used a VPS to host all my sites, but I ran into technical issues (it was OpenVZ-based so the kernel was ancient) and after I upgraded my home internet, I decided to put my bandwidth to use and host everything at home. Like many other North Americans, my ISP doesn’t give me a static IP, so this is an issue.&lt;/p&gt;
</summary><published>2019-09-11T00:00:00-04:00</published></entry><entry><id>https://qali.net/2019/08/31/hiking-in-colorado</id><title>Hiking in CO</title><updated>2023-08-24T18:23:51.083407+00:00</updated><content type="html">&lt;figure&gt;
	&lt;img src="hiking-in-co-01.jpg" alt=""&gt;
&lt;/figure&gt;
&lt;p&gt;I can’t think of anything to write about, so here’s a post about a hiking trip I went on a couple of months ago. Things didn’t go perfectly, but it was still fun!&lt;/p&gt;
&lt;h2&gt;1. DO plan ahead&lt;/h2&gt;
&lt;p&gt;About a month before the the trip, I decided that I’d put my credit card’s points to use by flying out on a weekend trip to somewhere. I like taking photos, I like nature, and Colorado was (relatively) close, so I asked a buddy if he wanted to tag along, and bought a ticket to Denver. I then forgot about this trip for a few weeks. A couple of days before the flight we realized we needed to actually do something this weekend, so we sat down and skimmed through trail sites, state park maps, and travel guides.&lt;/p&gt;
&lt;p&gt;I originally wanted to do a &lt;a href="https://en.wikipedia.org/wiki/Fourteener"&gt;14er&lt;/a&gt;, but it turned out that most 14er peaks around this time of year are either completely frozen or too far away from roads for us. We tried looking for easy 13ers. As it turns out, the sort of people who spend their free time climbing up really high mountains tend to use the word easy in a very unique way. For example (not a literal quote but it might as well be):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hike starts out with an easy 10 mile hike at 5-10% grade, before steepening to a moderately difficult 20 mile 45% ascent. You may need crampons and ice picks to make it to the summit, but the climb should only be a few hours long. 5⁄5, highly recommended for any novice!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The climb to the summit ends with a very &lt;em&gt;easy&lt;/em&gt; section of climbing ice underhangs, where it is very &lt;em&gt;easy&lt;/em&gt; for you to fall and break all the important bones in your body.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eventually, we settled on a trail labelled &lt;a href="https://www.hikingproject.com/trail/7009397/cdt-berthoud-pass-to-stanley-mountain"&gt;CDT - Berthoud Pass to Stanley Mountain&lt;/a&gt;, which rises 1000 feet from ~11,500 up to 12,500 feet (part of the &lt;a href="https://continentaldividetrail.org/"&gt;Continential Divide Trail&lt;/a&gt;, a 3,000 mile trail starting in New Mexico and ending in Montana). The ascent didn’t seem that bad, and it was only a 8 mile out-and-back hike! Plus it was close to the road, with direct access from a parking lot. Trail reports were mixed, with some people reporting snow, but the reports weren’t very descriptive, and seeing how we were Canadians, a little snow wouldn’t hurt us.&lt;/p&gt;
&lt;p&gt;This was mistake #1. If we had more time, we could’ve spent a little more time looking into weather around the area, and we probably would’ve found a nicer trail. That guy who posted a trail report saying that there was ‘alot of snow’ was right. We ran into 3 foot high packing snow drifts less than a mile out.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src="hiking-in-co-02.jpg" alt="View looking down on a valley. The ground is covered in thick snow drifts."&gt;
	&lt;figcaption&gt;&lt;p&gt;Yup, looks like snow&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Hiking up these was not fun, since we were wearing regular hiking boots. Hiking down was much worse, for reasons I’ll cover later on. Know the weather in the area for the time of year, and rely on trail reports if you can.&lt;/p&gt;
&lt;h2&gt;2. DO respect altitude&lt;/h2&gt;
&lt;p&gt;To help acclimate to the altitude, we spent our first day in downtown Denver and the general Denver/Boulder area. Denver seems like a chill place. Check out the MoMA there.&lt;/p&gt;
&lt;p&gt;The next day, we drove to Stanley Mountain, put on our hiking gear, walked up a slight incline, and gasped for air.&lt;/p&gt;
&lt;p&gt;Mistake #2. The effects of altitude are real. As someone who’s biked multiple 100km rides, and spends about 5 hours on the saddle every week, I’d like to believe that I’m OK in terms of cardiac output. But even for me, every little motion saps away strength, and even something as basic as reaching down to tie your shoes can bring on dizziness and tunnel vision. Steep sections are done one step at a time. Turns out that 24 hours isn’t enough to adjust to physical activity at 11,000 feet. We had to move incredibly slowly during the ascent, and I was exhausted by the time we got to the top. By the time we were at the hotel, I had developed &lt;a href="https://en.wikipedia.org/wiki/Altitude_sickness"&gt;altitude sickness&lt;/a&gt;, which feels like the worst fever ever combined with the worst cold ever. Don’t get altitude sickness.&lt;/p&gt;
&lt;h2&gt;3. DO respect the weather&lt;/h2&gt;
&lt;p&gt;The night before our hike, weather forecasts for the trail area called for clear skies. The morning of the hike, I didn’t bother checking the weather forecast. Whoops. Mistake #3. The bulk of the ascent was uneventful, but after a few miles we started seeing clouds rolling in in the distance. It was hard for me to tell if they were moving towards us or away from us, but whatever, clouds won’t do anything. We slowly made our way to the summit, and took a short break. I somehow still had 4G service at the top. Shitposting at 12,000 feet: S tier.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src="hiking-in-co-03.jpg" alt="A distant mountain range, viewed from the top of a mountain. Dark clouds gather above the mountain range and the sky is grey."&gt;
	&lt;figcaption&gt;&lt;p&gt;A view from the summit. Those clouds in the distance don’t look too friendly.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
	&lt;img src="hiking-in-co-04.jpg" alt="Grasslands and valleys, viewed from the top of a mountain. The sky is blue and cloud cover is minimal."&gt;
	&lt;figcaption&gt;&lt;p&gt;Turning around 180 degrees, the view is a lot greener and a lot less stormy.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The clouds are starting to roll in and we hear thunder in the distance. This is actually very bad news.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src="hiking-in-co-05.jpg" alt="A dirt trail running down a mountainside. As the trail dips below the horizon, the sky above grows dark with clouds."&gt;
	&lt;figcaption&gt;&lt;p&gt;Unfortunately, this is where we came from. The path back down leads to that scary looking dark cloudy section.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We start high-tailing it back down. The slight downwards gradient gives us a little boost, but it’s not enough to outrun the storm. The wind picks up and we’re suddenly in the middle of a flash hailstorm. The hail stings, but we quickly forget about it as we see lightning hit the ground multiple times within a mile of us. We decide to no longer be the tallest objects in our immediate surroundings, and haul ass back to the bottom of the mountain.&lt;/p&gt;
&lt;p&gt;Those snow drifts are now starting to melt and obscure the trail, and navigating them is a lot trickier now. Since they’re partially packed snow, it’s hard to tell if the snow is going to cave in when you step on it, which makes sprained ankles a real risk.&lt;/p&gt;
&lt;p&gt;We slide down some snow drifts and make it back to the parking lot, completely soaked. On the drive back, I enjoy my altitude-induce migraine.&lt;/p&gt;
&lt;h2&gt;4. DO don’t be dumb and have fun&lt;/h2&gt;
&lt;p&gt;I know I’m making this sound like a terrible experience, but it was fun! I enjoyed the challenge, and the views were amazing. Colorado’s mountains are a great place to hike. Just don’t be dumb, and stay safe. If you’re going to hike in a tough environment for the first time, don’t yolo it, and do it with someone else. 12,000 feet up, on a mountain peak, completely alone, is not where you want to be when you discover that the stable rock you’re standing on isn’t so stable, or that &lt;a href="https://en.wikipedia.org/wiki/High-altitude_pulmonary_edema"&gt;you’ve developed a rare life-threatening form of altitude sickness&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Links:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.hikingproject.com/trail/7009397/cdt-berthoud-pass-to-stanley-mountain"&gt;CDT - Berthoud Pass to Stanley Mountain - Hiking Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.strava.com/activities/2493832792"&gt;Strava for this hike&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><link href="https://qali.net/2019/08/31/hiking-in-colorado" rel="alternate"/><summary>&lt;p&gt;I can’t think of anything to write about, so here’s a post about a hiking trip I went on a month ago. Things didn’t go perfectly, but it was still fun!&lt;/p&gt;
</summary><published>2019-08-31T00:00:00-04:00</published></entry><entry><id>https://qali.net/2019/04/28/icann-pir</id><title>The Spurious Justifications for Eliminating Price Caps on .org and Other Legacy Domains</title><updated>2023-08-24T18:23:51.083362+00:00</updated><content type="html">&lt;p&gt;&lt;a href="http://www.circleid.com/posts/20190423_spurious_justifications_for_eliminating_caps_on_legacy_domains/"&gt;This post&lt;/a&gt; by Nat Cohen covers &lt;a href="https://www.icann.org/public-comments/org-renewal-2019-03-18-en"&gt;ICANN’s proposal&lt;/a&gt; to lift price protections on &lt;code&gt;.org&lt;/code&gt; domains, allowing the Public Interest Registry (the owner of &lt;code&gt;.org&lt;/code&gt;) to charge whatever price they wish to registrars. ICANN’s justification is that extortion by registrars will cause domain owners to leave, driving the price down. This analogy falls apart when we consider that organizations are strongly coupled to their domain names. Nat explains by analogy:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Let us say that you live in a long-established city of millions of people. One day some state officials announce that they are going to remove all price controls from the electric company for your city. The electric company will soon be able to charge whatever it wishes to provide you electricity. The state officials say that it is only fair for your electric company to be able to set fees at any level that it chooses since the state recently allowed the electric companies in some new developments to set fees at whatever level they chose (and these companies paid handsomely for the privilege). Those electric companies are not happy because so few people want to move to their developments (in part, you think to yourself, because the residents have no protection against being overcharged on their electric bill), so to make things even, all citizens in the state will lose their protections from being overcharged on their electric bills as well. When you express concern that your electric company might jack up your rates, the state officials offer the reassurance that the electric company would not be able to increase your rates that much because if you did not like the higher rates you could sell your house and move to a different city. The rationale that “don’t worry, you can move” that is clearly absurd in the example from the physical world is being touted as reasonable when applied to the virtual world of domain names. Indeed, the disruption may be even greater in the virtual world of the Internet where one’s domain name is indistinguishable from one’s online brand.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is no justifiable reason for this change except to allow price-gouging, and it’s disappointing to see site owners and NPOs being treated as revenue streams.&lt;/p&gt;
</content><link href="https://qali.net/2019/04/28/icann-pir" rel="alternate"/><published>2019-04-28T00:00:00-04:00</published></entry><entry><id>https://qali.net/2019/04/23/image-hoster</id><title>DIY image hosting</title><updated>2023-08-24T18:23:51.083301+00:00</updated><content type="html">&lt;aside&gt;&lt;p&gt;Please don’t use this for a real site. This script doesn’t do any sort of file validation. It’s possible for someone to rename a .exe to .jpg, upload it, and then use the site for malware distribution.&lt;/p&gt;&lt;/aside&gt;
&lt;p&gt;I spent a couple of hours throwing together a quick Python HTTP server that I can use to run image hosting on this machine. It’s super ugly and not at all responsive to errors, so I have systemd just restart the script on failure. On the plus side, deployment is just a &lt;code&gt;git clone&lt;/code&gt;. The script is running behind Nginx so I don’t have to worry about SSL here. I’m using basic auth to prevent everyone else from uploading images. Any non-image requests get proxied to the script, while image requests get a cache header added and are served normally:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
	listen 443 ssl http2;
	listen [::]:443 http2;
	server_name i.qasima.li;
	root /var/www/i.qasima.li;

	ssl_certificate     /etc/letsencrypt/live/i.qasima.li/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/i.qasima.li/privkey.pem;

	client_max_body_size 50m;

	location / {
		auth_basic &amp;quot;Image upload&amp;quot;;
		auth_basic_user_file /etc/nginx/i.qasima.li.htpasswd;

		proxy_pass http://127.0.0.1:8000;
	}

	location ~* .*\.(jpe?g|png|gif|tiff?|webp)$ {
		access_log off;
		add_header Cache-Control &amp;quot;public, max-age=86400&amp;quot;;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since nginx is the one serving files, the script responds to all &lt;code&gt;GET&lt;/code&gt;s with an upload page. On &lt;code&gt;POST&lt;/code&gt;, we read out the uploaded image’s mime type so we know what to save it as. Then the script just copies the image to the resulting directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
from io import BytesIO


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    homepage = b&amp;quot;&amp;quot;&amp;quot;
	&amp;lt;!DOCTYPE html&amp;gt;
	&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&amp;lt;/head&amp;gt;
        &amp;lt;body style=&amp;quot;padding: 10px;&amp;quot;&amp;gt;
            &amp;lt;form action=&amp;quot;/&amp;quot; enctype=&amp;quot;multipart/form-data&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
                &amp;lt;div style=&amp;quot;margin:10px auto&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;file&amp;quot; accept=&amp;quot;image/*&amp;quot; name=&amp;quot;i&amp;quot; style=&amp;quot;font-size: 16px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Upload&amp;quot; style=&amp;quot;width: 100%;height: 60px;font-size: 23px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/form&amp;gt;
        
    &amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;&amp;quot;&amp;quot;

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(self.homepage)

    def do_POST(self):
        content_type = self.headers[&amp;quot;Content-Type&amp;quot;]
        if not content_type.startswith(&amp;quot;multipart/form-data&amp;quot;):
            self.send_response(400)
            self.end_headers()
            response = BytesIO()

            response.write(
                bytes(&amp;quot;Wrong content type &amp;quot; + content_type, encoding=&amp;quot;utf-8&amp;quot;)
            )
            self.wfile.write(response.getvalue())
            return

        content_length = int(self.headers[&amp;quot;Content-Length&amp;quot;])
        body = self.rfile.read(content_length)
        lines = body.split(b&amp;quot;\r\n&amp;quot;)

        boundary = lines[0]
        file_content_type = lines[2]
        lead_up = &amp;quot;Content-Type: image/&amp;quot;
        ftype = str(file_content_type[len(lead_up) :], encoding=&amp;quot;utf-8&amp;quot;)

        import random, string

        target_filename = (
            &amp;quot;&amp;quot;.join(
                random.choice(string.ascii_lowercase + string.digits) for _ in range(5)
            )
            + &amp;quot;.&amp;quot;
            + ftype
        )

        end_boundary_start = body.rfind(boundary) - len(b&amp;quot;\r\n&amp;quot;)
        file_start = len(b&amp;quot;\r\n&amp;quot;.join(lines[0:3])) + 2 * len(b&amp;quot;\r\n&amp;quot;)

        with open(&amp;quot;/var/www/i.qasima.li/&amp;quot; + target_filename, &amp;quot;wb&amp;quot;) as output:
            output.write(body[file_start:end_boundary_start])

        self.send_response(301)
        self.send_header('Location', '/' + target_filename)

        self.end_headers()
        self.wfile.write(b'')


if __name__ == &amp;quot;__main__&amp;quot;:
    httpd = HTTPServer((&amp;quot;localhost&amp;quot;, 8000), SimpleHTTPRequestHandler)
    httpd.serve_forever()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The systemd unit file is as barebones as can be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Runs /var/www/img/main.py for i.qasima.li
After=network.target

[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/python3 /var/www/img/main.py
Restart=on-failure

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s super janky, but I think it’s all I’ll need for now. Cleanup can be done in shell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env sh

# delete &amp;gt;= 30d old files
find /var/www/i.qasima.li/ -mtime +30 -exec rm {} \;
&lt;/code&gt;&lt;/pre&gt;
</content><link href="https://qali.net/2019/04/23/image-hoster" rel="alternate"/><summary>&lt;p&gt;I spent a couple of hours throwing together a quick Python HTTP server that I can use to run image hosting on this machine.&lt;/p&gt;
</summary><published>2019-04-23T00:00:00-04:00</published></entry><entry><id>https://qali.net/2019/04/19/hello-world</id><title>Hello, World</title><updated>2023-08-24T18:23:51.072507+00:00</updated><content type="html">&lt;p&gt;I read about the &lt;a href="https://indieweb.org/"&gt;IndieWeb project&lt;/a&gt; last week. I’ve followed them at a distance in the past, but I ended up watching &lt;a href="https://youtube.com/watch?v=HNmKO7Gr4TE"&gt;this video&lt;/a&gt;, and it really struck a chord with me:&lt;/p&gt;
&lt;div class="yt-container"&gt;&lt;iframe src="//www.youtube.com/embed/HNmKO7Gr4TE" frameborder="0" allowfullscreen title="Tantek Celik, &amp;quot;Why We Need the IndieWeb&amp;quot;, #PDF14" class="yt"&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p&gt;Having my own website means that I have a ‘real’ online identity. Businesses will come and go, &lt;a href="https://ourincrediblejourney.tumblr.com/"&gt;incredible journeys will come to an end&lt;/a&gt;, and servers get neglected, hacked, or shut down. But even as I churn through social media accounts, my personal domain will always represent me.&lt;/p&gt;
&lt;p&gt;There’s also the appeal of ownership. The web is one of the most influential networks in the history of the world, and by putting this site online, I’m staking a claim to some part of it. I’m not renting space from Facebook or Medium or Reddit, I &lt;em&gt;own&lt;/em&gt; this tiny slice of our web.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Writing on your own website associates your thoughts and ideas with you as a person…Writing for another publication you get a little circular avatar at the beginning of the post and a brief bio at the end of the post, and that’s about it. People will remember the publication, but probably not your name. (&lt;a href="http://bradfrost.com/blog/post/write-on-your-own-website/"&gt;write on your own website&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some of the most useful and meaningful things I’ve read have been written by people on their own sites. Maybe I can try to repay that favour.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Technical details: This site is built using &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;. The theme is a customized version of &lt;a href="https://themes.gohugo.io/hugo-theme-one/"&gt;one&lt;/a&gt;. Since this is a static site without comments or pingback support, I don’t think I’ll be using Microformats and instead will just include some &lt;code&gt;rel=me&lt;/code&gt; tags so that I get nice checkmarks on other websites.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Update: I gave up on Hugo because it seems to introduce breaking changes too rapidly, and instead wrote a markdown-based generator in Python. It&amp;#8217;s easier to use for me since it picks up publication dates and slugs based on file paths, which makes posting on mobile easy.&lt;/p&gt;
</content><link href="https://qali.net/2019/04/19/hello-world" rel="alternate"/><published>2019-04-19T00:00:00-04:00</published></entry></feed>