<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>orionc.dev</title>
    <link>https://orionc.dev/</link>
    <description>One man with too many hobbies</description>
    <language>en</language>
    <lastBuildDate>Thu, 26 Mar 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://orionc.dev/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>How to self-host rybbit properly</title>
      <link>https://orionc.dev/posts/how-to-setup-rybbit</link>
      <guid>https://orionc.dev/posts/how-to-setup-rybbit</guid>
      <pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<h2>"This website is being recorded for quality assurance purposes"</h2>
<h3><a href="https://rybbit.orionc.dev/1">See the live analytics data from this website</a></h3>
<p><figure>
  <img alt="Screenshot from the live tracker after the first week of data" src="/static/images/rybbit-graph.png" />
  <figcaption>Screenshot from the live tracker after the first week of data</figcaption>
</figure></p>
<p><a href="https://rybbit.com/">Rybbit.io</a> is a privacy respecting website analysis platform that is free and open source to anyone willing to run it on their platform.
At the moment, self hosting it gives you almost everything that the <code>$13 a month</code> tier does with the exception of the email related items (price list can be found <a href="https://rybbit.com/pricing">here</a>).</p>
<p>It's exceptionally easy to get up and running, with a very intuitive and insightful display of information regarding how real users are interacting with your website.
Since implementing rybbit on this site last week, I've seen traffic from 53 unique users from 11 different countries.</p>
<p><figure>
  <img alt="A week's worth of traffic" src="/static/images/rybbit-stats.png" />
  <figcaption>A week's worth of traffic</figcaption>
</figure></p>
<h2>Details you don't want to miss</h2>
<p>As I said above, it is <code>exceptionally easy</code> to get it up and running on your own machine by following the docs on the <a href="https://rybbit.com/docs/self-hosting">rybbit site</a>.
This however, is a bit of a problem.
There are blatant security issues present in the <code>./setup.sh</code> code that <strong>need to be addressed before making your Rybbit instance public</strong>.</p>
<p>When running the <code>./setup.sh</code>, it automatically builds a minimal <code>.env</code> for you based on your user input.
There are various arguments for the script which can help getting set up faster, but they don't solve the many core issues in the script itself.</p>
<p>When peeking at the <code>.env</code> and <code>docker-compose.yml</code> files that get built as part of the script, a few things should immediately jump out at you as an issue.</p>
<pre><code>orion@intrepid:~/rybbit$ cat .env
# Required variables configured by setup.sh
DOMAIN_NAME=my-website.local
BASE_URL=https://my-website.local
BETTER_AUTH_SECRET=$(my_auto_generated_auth_secret)
DISABLE_SIGNUP=false
</code></pre>
<pre><code>orion@intrepid:~/projects/rybbit$ cat docker-compose.yml

# this file has been truncated for brevity
# only the entries relevant to my point remain

  clickhouse:
    environment:
      - CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
      - CLICKHOUSE_USER=${CLICKHOUSE_USER:-default}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
    ports:
      - &quot;8123:8123&quot;

  postgres:
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-frog}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
      - POSTGRES_DB=${POSTGRES_DB:-analytics}
    ports:
      - &quot;5432:5432&quot;
</code></pre>
<p>Right off the bat, you should see two problems.
First, there are no <code>CLICKHOUSE_PASSWORD</code> or <code>POSTGRES_PASSWORD</code> fields in the <code>.env</code>, which means they are using the default values <code>default:frog</code> and <code>frog:frog</code> respectively.
Additionally, in case you didn't know, <a href="https://docs.docker.com/engine/network/packet-filtering-firewalls/#docker-and-ufw">docker bypasses ufw firewall rules</a> and the ports in the default configuration aren't bound to localhost.
The combination of these two things essentially means that <strong>without any additional configuration, your databases are exposed to the internet using the default credentials found in the <a href="https://github.com/rybbit-io/rybbit/blob/master/docker-compose.yml">github repo</a>.</strong></p>
<p>To prove this hunch, I spun up a default <code>rybbit</code> instance on another machine on my home network only using the commands found in the <a href="https://rybbit.com/docs/self-hosting">self-hosting guide on the rybbit docs</a>.
After it was up, I ran a quick <code>nmap</code> command from another machine to verify that those ports were indeed open for external use:</p>
<pre><code>orion@theseus:~/projects/rybbit$ nmap -p 8123,5432 intrepid.lan
Starting Nmap 7.95 ( https://nmap.org ) at 2026-03-05 07:35 PST
Nmap scan report for intrepid.lan (192.168.1.156)
Host is up (0.0047s latency).

PORT     STATE SERVICE
5432/tcp open  postgresql
8123/tcp open  polipo

Nmap done: 1 IP address (1 host up) scanned in 0.10 seconds
</code></pre>
<p>Using the default credentials stated earlier, I was easily able to get <strong>unrestricted access</strong> to both databases.</p>
<pre><code>orion@theseus:~/projects/rybbit$ psql -h intrepid.lan -U frog -d analytics
Password for user frog:
psql (17.8 (Debian 17.8-0+deb13u1), server 17.4 (Debian 17.4-1.pgdg120+2))
Type &quot;help&quot; for help.

analytics=# \echo &quot;I've been pwned!&quot;
&quot;I've been pwned!&quot;
</code></pre>
<pre><code>orion@theseus:~/projects/rybbit$ curl -u default:frog 'http://intrepid.lan:8123/?query=SHOW+TABLES+FROM+analytics'
events
monitor_events
session_replay_events
session_replay_metadata
</code></pre>
<h2>Don't worry, this is an easy fix.</h2>
<p>Rather than using <code>./setup</code>, update your <code>.env</code> to include the following:</p>
<pre><code># ClickHouse Database Configuration
CLICKHOUSE_USER=CHANGE_ME
CLICKHOUSE_PASSWORD=CHANGE_ME

# PostgreSQL Database Configuration
POSTGRES_USER=CHANGE_ME
POSTGRES_PASSWORD=CHANGE_ME
</code></pre>
<p>Then update the <code>ports</code> entries in <code>docker-compose.yml</code> such that they are bound to localhost (127.0.0.1):</p>
<pre><code>  clickhouse:
    ports:
      - &quot;127.0.0.1:8123:8123&quot;

  postgres:
    ports:
      - &quot;127.0.0.1:5432:5432&quot;
</code></pre>
<p>Start up your docker instance via <code>docker compose up -d</code>, and verify that the ports specified aren't accessible externally:</p>
<pre><code>orion@theseus:~/projects/rybbit$ nmap -p 8123,5432 intrepid.lan
Starting Nmap 7.95 ( https://nmap.org ) at 2026-03-05 08:12 PST
Nmap scan report for intrepid.lan (192.168.1.156)
Host is up (0.0082s latency).

PORT     STATE    SERVICE
5432/tcp filtered postgresql
8123/tcp filtered polipo

Nmap done: 1 IP address (1 host up) scanned in 1.27 seconds
</code></pre>
<p>Updating the username and password alongside the port binding can probably be seen as a belt-and-suspenders approach to solving this issue, but there really is no harm in updating an <code>.env</code> file on your machine.</p>
<h2>Additional nginx setup</h2>
<p>If you're like me and using nginx, you'll need some additional configuration to get rybbit working properly and securely on your site.
There is a rybbit doc that <a href="https://rybbit.com/docs/self-hosting-guides/self-hosting-manual">covers this</a>, but I've included my own config below which adds additional security.</p>
<p>First thing, you'll need to get an SSL certificate for the domain.
If you're running rybbit as a subdomain like I am, you can use the following command:</p>
<pre><code>sudo certbot --nginx -d rybbit.$(your_domain)
</code></pre>
<p>Once your SSL cert is complete, add the following to your existing website entry in <code>/etc/nginx/sites-enabled/$(your_domain)</code>:</p>
<pre><code>server {
    listen 80;
    listen [::]:80;
    server_name rybbit.$(your_domain);
    return 301 https://$host$request_uri; # This forces https connections
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name rybbit.$(your_domain);

    # SSL configuration (using Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/rybbit.$(your_domain)/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/rybbit.$(your_domain)/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Security headers
    add_header X-Frame-Options &quot;SAMEORIGIN&quot; always;
    add_header X-Content-Type-Options &quot;nosniff&quot; always;
    add_header X-XSS-Protection &quot;1; mode=block&quot; always;

    # API requests
    location /api/ {
        proxy_pass http://localhost:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $server_name;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Client app
    location / {
        proxy_pass http://localhost:3002;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $server_name;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}
</code></pre>
<h2>Login for the first time</h2>
<p>Once everything is up, go to the signup page found at <code>rybbit.$(your_domain)/signup</code> and make an admin account.
Once this step is complete, I'd highly recommend that you disable signups.
This can be done by simply changing a value in the <code>.env</code>:</p>
<pre><code>DISABLE_SIGNUP=true
</code></pre>
<p>Restart your container via <code>docker compose down &amp;&amp; docker compose up -d</code>, verify that signups are indeed blocked, then you should be good to go!</p>
<p><figure>
  <img alt="Always double check your work" src="/static/images/signup-disabled.png" />
  <figcaption>Always double check your work</figcaption>
</figure></p>
<h2>Write a privacy policy for your site</h2>
<p>Now that you have a rybbit instance up and running on your website, I'd highly encourage you to write a <strong>transparent</strong> privacy policy such that your website's users are aware of rybbit's presence and can <strong>easily opt out</strong>.
For an example of such a policy, you can find the one <a href="/privacy-policy">for this website here</a>.</p>]]></description>
    </item>
    <item>
      <title>How I used Claude Code to refactor this website</title>
      <link>https://orionc.dev/posts/refactoring-with-claude-code</link>
      <guid>https://orionc.dev/posts/refactoring-with-claude-code</guid>
      <pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p><figure>
  <img alt="Seriously, why is the logo a butthole?" src="/static/images/claude_butthole.png" />
  <figcaption>Seriously, why is the logo a butthole?</figcaption>
</figure></p>
<p>This website has always been a means of exploring technology that I wasn't already familiar with.
When it first came into being I was a college student who knew a lot about software theory, but knew next to nothing about how the internet functioned or how websites worked.
Building a website from scratch provided an opportunity to get my hands dirty and learn a thing or two by doing rather than by reading.</p>
<p>As a gainfully employed software engineer in 2026 I've been witness to a lot of hype around these tools.
In just this month, <a href="https://www.digitaljournal.com/business/tech-layoffs-big-tech-is-now-leading-the-way/article">big tech is cutting jobs (again)</a>, Anthropic is now considered a <a href="https://reason.com/2026/02/28/anthropic-labeled-a-supply-chain-risk-banned-from-federal-government-contracts/">threat to national security by the current government</a>, and almost everyone I've talked to is using these tools in some capacity on a daily basis.</p>
<p>I've personally found that since LLMs have been released to the public, most of my own and my coworkers' workdays haven't changed very much.
Only in the last few months have companies like mine decided to start their initial investigation to determine if these tools are viable enough to use on production code.</p>
<p>This refactor represents my own exploration of the current state of LLMs and to help determine if they are worth all of the hype, or if they are still vaporware.</p>
<p>I have been using Claude and ChatGPT on and off over the last year in a limited capacity.
In my experience using these tools in a browser context window I concluded that they were good, but the reality of being a glorified autocomplete became very apparent after any extended interaction with them.
As a programmer I've found that using LLMs as a <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">rubber duck</a> can be very helpful, but you still need to have an idea of how the thing you're trying to fix fundamentally works before asking it for help.</p>
<p>Just as I did during the <a href="/posts/static-site-refactor">last refactor</a>, I wanted to have a few goals in mind for Claude to help me with.</p>
<ol>
<li>Improved auto SEO (search engine optimization)</li>
<li>Add a configurable tag system</li>
<li>Include excerpts such that the main page isn't just post titles</li>
<li>Maintain the look / feel / spirit of the original site</li>
</ol>
<p>Getting Claude installed on my machine was easy, and setting up a plan and expectations with it was very straightforward.
I appreciated that, as of 2026, it has a plan feature where it asks follow up questions to get more specific details about the project, just as a software engineer would with a customer.</p>
<p>It took a bit of time to narrow down a good approach, but after some back and forth discussion we agreed that Python + Jinja made more sense for a simple and extensible html templating architecture than the current pure bash + html approach.
This way we are still able to keep all of the business logic localized to one script while taking advantage of Python's insane number of libraries for other uses.
The result is on GitHub for your viewing pleasure: <a href="https://github.com/orioncrocker/dsssg">dsssg (dead simple static site generator)</a>.</p>
<p>Once the plan was done, we got to work.</p>
<p><figure>
  <img alt="from an IBM training manual, circa 1979" src="/static/images/ibm-training-manual.webp" />
  <figcaption>from an IBM training manual, circa 1979</figcaption>
</figure></p>
<h2>A good partner, not a programmer replacement</h2>
<pre><code> Do you want to make this edit to config.yaml?
 ❯ 1. Yes
   2. Yes, allow all edits during this session (shift+tab)
   3. No
</code></pre>
<p>I'm very grateful I had the good sense not to hit option 2 even once during the refactor.
There were many occasions where I had to keep it on track, redirect it back towards the main goal, or stop it from writing its own implementation of something rather than simply using an existing library.
In the end I felt like I had to treat it like a software undergraduate and dole out very specific tasks that should be done in a very specific way, and berate it when it got some lofty ideas about its own way of doing things.</p>
<p>Despite all of the coralling, and "wait why are you doing it that way" questions, the end result was good.
Better than good really.
Claude and I wrote something in a few hours what probably would have taken me a whole day to wrap up.
After our slop sandwich was "complete," I asked it to go back and reread the code, find any issues, and provide suggestions on how to fix them.
I think that it did <strong>very</strong> well at this task, and could easily see how this might be the best use case for such a tool.</p>
<pre><code>❯ Please take a hard look at the code and find anything that dead / currently not being used

● Here's what I found:

1. post_template assigned twice — line 517 loads it for nav/root pages, then line 549 loads the exact same template again for posts. Second assignment is dead.
2. tag_slug recomputed on line 586 — tag_name.lower().replace(' ', '-') is already stored as tag['slug'] from process_tag(). Duplicate logic.
3. Redundant import re inside functions — re is imported at the top of the file but also re-imported locally inside process_image_captions, regex_search, regex_replace, and safe_html_truncate.
4. from html.parser import HTMLParser buried inside safe_html_truncate — should be a top-level import.
5. featured field in tag objects — stored in every tag object via process_tag() but never referenced in any template.
6. images_optimized potential NameError — if images_dir doesn't exist, the variable is never defined but referenced unconditionally later.
7. Misleading docstring on get_post_date
</code></pre>
<p>Within a few iterations of this "look back over everything again" instruction, we had trimmed almost one hundred lines from <code>build.py.</code>
It trimmed the dead code we made together during mid-session redesigns, found more efficient ways of doing things, and fixed comments along the way.</p>
<h2>Still needs training wheels</h2>
<p>I'm uncertain how I feel about the fact that this tool will soon be forced to train on "vibe coded" software.
Almost every meme or YouTube video I've come across of someone hyping this tool up also loudly professes that they have no idea how to write software (and therefore no idea what they are doing) but are very happy with the results they are getting.
Maybe I'm wrong here, but this seems like a case of <code>"ignorance is bliss."</code>
The term <code>nocoders</code> to be used as a slur cannot become popularized fast enough.</p>
<p>If I were to completely ignore software fundamentals and best practices during this refactor Claude would have most likely written a hot and sloppy mess.
One could probably argue that even when keeping it on track it still produced slop, but I'll defend that it is at the very least maintainable slop.</p>
<p>In keeping with the undergraduate analogy, here's how I think Claude performed:</p>
<h3>Software Planning and Design: A</h3>
<p>Claude's Planner tool that auto pops up when discussing plans is great.
It asked intelligent follow up questions during the design process and queried the web for up to date information instead of relying on its potentially old and outdated training data.</p>
<h3>Implementation: B</h3>
<p>Even though it was pretty good at implementing our refactor plan, I maintain that you will need to have some background in basic software fundamentals to get decent results.
Now that this technology is open to anyone with a credit card, I'm a bit concerned that we are going to see far more people egregiously and joyously producing absolute slop which will be used to train the next model.
I sincerely hope there is a means of mitigating this self defeating alzheimers-like mechanism that will surely cripple the tool in the long run.</p>
<h3>Cleanup: A-</h3>
<p>Even though it did do very well in cleaning up its own sloppy mess, I still had to ask it about 5 different times to go over the very small repo to find all issues. Even then, I was able to find a few more on my own after taking a pass myself.</p>
<p><figure>
  <img alt="Claude, circa 2030" src="/static/images/im-sorry-dave.webp" />
  <figcaption>Claude, circa 2030</figcaption>
</figure></p>
<h2>To vibe or not to vibe?</h2>
<p>This tool is indisputably disruptive, and will continue to be disruptive until some equilibrium is achieved (probably not anytime soon).
I haven't looked into agentic coding yet, but it seems to be the next logical step after this.
<a href="https://www.youtube.com/watch?v=kSno1-xOjwI">OpenClaw</a> has taken the "AI community" by storm, but at the moment it seems that most people who have gotten on board are either active participants of AI development themselves, or <code>nocoders</code> who remind me an awful lot of the <a href="https://www.urbandictionary.com/define.php?term=NFT+bros">NFT bros</a>.</p>
<p>In conclusion, if you are going to use vibe code tools, I would <code>highly recommend</code> that you learn software and Git fundamentals first, <code>especially Git.</code>
Having the ability to back up and revert the incremental changes that you make alongside your LLM is <code>critical</code> to success.</p>
<p>These tools are honestly incredible, but they can be <code>easily abused</code>.
If you have an interest in writing or understanding software, this tool can be used as a cheat sheet to streamline knowledge, or simply produce slop.
Garbage in, garbage out.</p>
<p><img alt="" src="/static/images/puckering.gif" /></p>
<pre><code>❯ Why does your logo look like an asshole?

● Ha — I didn't design it, that's on Anthropic. The logo is meant to be an abstract representation of a neural network or nodes connecting, but I completely understand why it reads differently to a lot of people. You're not the first to notice.
</code></pre>]]></description>
    </item>
    <item>
      <title>JS8 station is finally up</title>
      <link>https://orionc.dev/posts/js8-station</link>
      <guid>https://orionc.dev/posts/js8-station</guid>
      <pubDate>Wed, 09 Jul 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>This has been a long time coming. I've been planning on creating a relatively permanent station running JS8Call on 40 meters for many months now, but like many of my other projects it kept getting pushed off in lieu of higher priority projects. Now that I have a second desk in my office I was able to justify the horizontal real estate to dedicate to such a station.</p>
<p><figure>
  <img alt="Comfy permanent 40M JS8 station" src="/static/images/js8-station.jpg" />
  <figcaption>Comfy permanent 40M JS8 station</figcaption>
</figure></p>
<p>After suspending up a 40M End Fed Halfwave antenna between my office window and an extendable fly fishing rod attached to my fence, I fired up the radio to check out the background noise. Right, that's why I didn't want to try to set up a station in suburban hell. My radio is picking up anywhere between S8 and S+ background noise at all hours of the day.</p>
<p><figure>
  <img alt="The pain of suburban hell as a ham" src="/static/images/suburban-hell.jpg" />
  <figcaption>The pain of suburban hell as a ham</figcaption>
</figure></p>
<p>Despite these limitations, I'm still able to make a great deal of contacts due to JS8Call's amazing decoding software. Most nights I'm able to see stations as far as the eastern sea board, and once I saw an Australian station's signal come through in the <em>very</em> early hours of the morning.
Next time a power outage comes through my neighborhood (which will likely kill all the background noise) I'll be very interested in seeing how my station performs. Hopefully such an event will occur at night.</p>
<p>Side note: <a href="https://github.com/js8call/js8call/pull/13">The bug-fix I submitted to the JS8Call team</a> was accepted and made it into the latest JS8Call software (<a href="https://github.com/js8call/js8call/releases/tag/v2.3.1">version 2.3.1</a>). Although this was a small bug that I'll bet no one but a handful of people noticed, it feels good to contribute to a project that I know sees a lot of use (plus this was my first ever contribution to an open source project). I'm hoping to contribute more to the as time moves on, provided that the rest of my project list is cleaned up first (it probably will never be).</p>
<p>Regardless, feel free to reach out via 40M if you wish to send me a message (propagation is always better at night).</p>]]></description>
    </item>
    <item>
      <title>Bucket List Peaks</title>
      <link>https://orionc.dev/posts/bucket-list-peaks</link>
      <guid>https://orionc.dev/posts/bucket-list-peaks</guid>
      <pubDate>Wed, 21 May 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>A list of peaks I'd like to climb before I expire along with personal notes on how I plan to tackle them.</p>
<p><a href="https://sotl.as/summits/W6/SS-127">Mt Crocker</a>:
Currently no one has activated this remote 10+3 point peak in the Eastern Sierras. I'd like to be the first (if possible), as the summit and I technically family. My father and I hiked to the base of the summit in the summer of 2023 with the intention of reaching the peak, but at the snowline we realized how woefully unprepared we were to to traverse the ice sheets and decided it would be best to just enjoy the alpine lakes instead. Next time I'll make sure to take an ice axe as a precaution.</p>
<p><figure>
  <img alt="My father and I with Mt Crocker in the background" src="/static/images/mt-crocker.jpg" />
  <figcaption>My father and I with Mt Crocker in the background</figcaption>
</figure></p>
<p>Resources:
- <a href="https://www.mountainproject.com/route/111025481/west-ridge">MountainProject page</a>
- <a href="https://peakery.com/mount-crocker-california/">Peakery page</a></p>
<p>Intended route:
Hike to and make camp at Big McGee Lake from McGee Creek Trailhead (near Tom's Place, CA). On day 2, hike up to Hopkin's Pass and follow ridge line to summit from the west. Descend down same path, resting at camp before descending on day 3.</p>
<p><a href="https://sotl.as/summits/W7I/NP-004">North Twin Peak</a>:
This is an interesting peak to me for a few reasons</p>
<ul>
<li>it currently has 0 activations (try to find things you can be the first to do nowadays, I dare you)</li>
<li>it is in one of the most remote and beautiful places on earth</li>
<li>relatively close by to home</li>
<li>there is no trail to the summit, mountaineering will be required</li>
<li>Beehive Lake is nearby, making this the perfect opportunity for a weekend backpacking trip</li>
</ul>
<p>Resources:
<a href="https://www.mountainproject.com/route/111025481/west-ridge">MountainProject page</a>
<a href="https://peakery.com/twin-peaks-idaho-7599ft/">Peakery page</a>
<a href="https://www.youtube.com/@SnowyMountainMan">Reach out to @SnowyMountainMan for route planning advice</a></p>
<p>Intended route:
Hike to and make camp at Beehive Lake, attempt to plan route for day 2. If/when a route is discovered, attempt to make summit in the safest way possibleOn day 2. After activation, descend down same path and camp before descending to car on day 3.</p>
<p>Note: this summit has no logs, so ample time to discover a safe route must be taken into consideration. There is no shame if multiple attempts are required.</p>]]></description>
    </item>
    <item>
      <title>Green Monarch Ridge SOTA Activation (W7I/NP-098)</title>
      <link>https://orionc.dev/posts/W7I-NP-098</link>
      <guid>https://orionc.dev/posts/W7I-NP-098</guid>
      <pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p><img alt="" src="/static/images/monarch-ridge-trailhead.jpg" /></p>
<p>After getting my feet wet <a href="https://youtu.be/s60JHTvU4f8?feature=shared">activating Horn Mountain</a> as a drive up peak, I decided it was time to shake off the rust and go on an actual hike. This particular SOTA activation required a bit of map cross-referencing as the route to the top wasn't immediately available on the <a href="https://sotl.as/summits/W7I/NP-098">Sotl.as map</a>. After checking <a href="https://maps.app.goo.gl/y5bmB94nSe4AT6jd8">Google Maps</a> I was able to confirm that the trailhead did indeed exist but the satellite image of the trail itself couldn't be followed to the summit. My usual map of choice <a href="https://www.openstreetmap.org/#map=13/48.08118/-116.29569&amp;layers=C">OpenSteetMap</a> had no trail available either. This prompted me to start scouring the web for other map websites, ultimately discovering <a href="https://listsofjohn.com/mapf?lat=48.10557&amp;lon=-116.31402&amp;z=14&amp;t=c&amp;d=y">ListsOfJohn.com</a>. LoJ is an incredible resource that provides many different maps of a single area, with some available maps being from 1885 and 1945 respectively. After selecting the CalTopo map option, lo and behold a trail to the summit appeared.</p>
<p><figure>
  <img alt="Lists Of John to the rescue" src="/static/images/monarch-ridge-map.png" />
  <figcaption>Lists Of John to the rescue</figcaption>
</figure></p>
<p>The initial section of the trail is steep, reminding me just how out of shape I am. Even in mid May there are large deep patches of snow hiding in the shadows of the pines, just waiting for the opportunity to pack an unprepared hiker's socks with months old ice.
While walking I realized that I could find no other boot prints in the snow or mud, and was likely the first person to have walked the trail since last Fall. There were however plenty of deer prints peppered in the snow. Additionally there were a few prints of what could only be a canine, but as there were no boots to accompany them I had to assume the prints were wolf or coyote.</p>
<p><img alt="" src="/static/images/monarch-ridge-tracks.jpg" /></p>
<p>Reaching the summit after a mere mile I was able to set up my 40M EFHW with the assistance of my telescoping fishing rod and a few local saplings. Trying the local 2 meter band on my handheld first, I was able to successfully contact Drew KG7YSX heading north on the 95 and Josh W7ITZ at his home at a secret undisclosed location somewhere in North Idaho.
At the time we all thought that we didn't have line of sight to each other and that the signals must have been bouncing off of the mountains. After getting home and checking the RF LOS tool, it turned out that I did indeed almost did have perfect LOS to both locations. That being said, every little bit of elevation counts. I was able to confirm from both parties that my transmission clarity was boosted significantly when standing on this elevated tree stump, a mere 2-3 feet above where I previously was.</p>
<p><figure>
  <img alt="+2db magick stump" src="/static/images/monarch-ridge-magick-stump.jpg" />
  <figcaption>+2db magick stump</figcaption>
</figure></p>
<p>Because I had decent cell phone reception at the summit I was able to spot myself on 20 meters and let the chasers come to me. While logging contacts with the help of <a href="https://polo.ham2k.com/">Ham2K's PoLo app</a> I realized that I was also technically within a valid Parks On The Air (POTA) zone. After getting the 5 contacts required to complete the activation, I pursued a few more Summit to Summit (S2S) and Park to Park (P2P) contacts before calling it a day and heading home.</p>
<p>I am very much looking forward to revisiting the area for both hiking and SOTA. The wall of silence that hit the trees after I turned off the radio was eerie, something I hadn't experienced in a long time. I couldn't help but feel like I had somehow violated a sacred space with static and the voices of distant strangers. Thankfully Sasquatch wasn't nearby to express his grievances.</p>
<p>Contacts logged:<br />
14:24   KG7YSX  2m  s55 r55<br />
14:25   W7ITZ   2m  s55 r48<br />
14:44   W7ITZ   20m s59 r59<br />
14:49   AC6M    20m s42 r53, S2S W6/NC-516<br />
15:21   WF7Q    20m s59 r53<br />
15:23   K4RXL   20m s53 r53, S2S W4V/FC-051<br />
15:28   KG7PRD  20m s55 r41<br />
15:30   W5ODS   20m s53 r41<br />
15:32   K6EL    20m s42 r55, S2S W6/NC-298<br />
15:47   N5BO    20m s41 r43<br />
15:53   KG5FLM  20m s41 r52  </p>
<p><figure>
  <img alt="Thank you chasers!" src="/static/images/monarch-ridge-qso-map.jpg" />
  <figcaption>Thank you chasers!</figcaption>
</figure></p>]]></description>
    </item>
    <item>
      <title>Now: September 2023</title>
      <link>https://orionc.dev/posts/09292023</link>
      <guid>https://orionc.dev/posts/09292023</guid>
      <pubDate>Fri, 29 Sep 2023 00:00:00 +0000</pubDate>
      <description><![CDATA[<h1>What I'm doing now</h1>
<p>September 2023:</p>
<p>Currently employed as a Software Engineer at <a href="https://www.trellisware.com/">TrellisWare Technologies</a> in my hometown of San Diego, California.
When I'm not at my desk, you can often find me gardening, biking, backpacking, rock climbing, and working on old engines as much as possible.</p>
<p>Goals I'm currently working towards:</p>
<ol>
<li>
<p>Start a small business in 2024</p>
<p>In November of last year, I purchased a 1971 Honda CL350 somewhat on a whim.
Since then, I have had a fantastic time learning how to refurbish almost every component of the bike.
This journey has included cracking open the engine and performing a top-end rebuild, relying on information from new friends, books, and YouTube.</p>
<p>I've discovered that I genuinely enjoy this work and have even contemplated the idea of starting a small business centered around buying, restoring, and selling vintage motorcycles out of my garage.
Given the current economic situation that most of us are facing, having a side gig that not only helps me acquire valuable skills and connect with a community but also generates some extra income seems increasingly worthwhile - approaching necessary.</p>
</li>
<li>
<p>Own property by 2025</p>
<p>Unfortunately, this goal has been pushed further into the future than I would have preferred.
The original target year was 2021.
In the early months of 2021, we had enough savings for a down payment, but we hesitated to take the plunge due to our perception at the time.
We believed that the housing market was already quite high due to the circumstances surrounding the pandemic and couldn't possibly increase any further.</p>
<p>Fast forward two years, and the price of a single-family home in the San Diego area has risen by nearly 50%, with interest rates climbing from 2.5% to almost 8%.
Given these two factors, the likelihood of me having the purchasing power to afford even a modest home in this area is now highly improbable.
A more likely scenario involves relocating to an area with more affordable housing options and transitioning to a fully remote position.
However, this alternative comes with a significantly more complex set of considerations.</p>
</li>
</ol>
<p>This page was inspired by <a href="https://www.nownownow.com">nownownow.com</a>.</p>]]></description>
    </item>
    <item>
      <title>Migrate Matrix server database to PostgreSQL</title>
      <link>https://orionc.dev/posts/migrate-matrix-server-db-to-postsql</link>
      <guid>https://orionc.dev/posts/migrate-matrix-server-db-to-postsql</guid>
      <pubDate>Sat, 20 Nov 2021 00:00:00 +0000</pubDate>
      <description><![CDATA[<h3>Deprecation warning, read before proceeding!</h3>
<p><strong>Note:</strong> This article was written in <strong>2021</strong>. Some steps — particularly around <code>synapse_port_db</code> and password management — may be outdated for newer versions of Synapse.
<strong>If you're running an older version of Matrix, this guide is for you</strong>, otherwise check the <a href="https://element-hq.github.io/synapse/latest/postgres.html">official Synapse docs</a> for the most current guidance.</p>
<p>While getting a Matrix chat server up and running is a relatively easy and well documented task, managing and maintaining the server is somewhat nebulous as far as existing documentation is concerned.
For example, when my wife asked me to reset her password on our server, I quickly realized that I had no idea how to do such a thing.
After some digging around on the net, it became apparent that other people were having similar problems.</p>
<p>Regardless of which method used to install a Matrix server, a <code>homeserver.db</code> file is always present after installation.
This file represents all data associated with the server such as rooms, users, their passwords, and their attributes.
By default, Matrix uses sqlite3 to access this <code>.db</code> file.
However, <a href="https://matrix-org.github.io/synapse/latest/postgres.html">Matrix’s own documentation</a> recommends migrating the database to use postgres instead.</p>
<p>This guide will walk you through the process of installing postgres, getting it running, migrating the existing Matrix database, and how to change a user’s password.
As always, this guide assumes the reader is running a Debian based linux operating system with access to root privileges via sudo.
Additionally, this guide assumes that you already have a Matrix instance up and running on your machine.
If you do not, you can get on easily using <a href="https://matrix-org.github.io/synapse/latest/setup/installation.html">Matrix’s installation guide</a>.</p>
<p>First things first, we need to install postgres.</p>
<pre><code>sudo apt install postgresql postgresql-contrib
</code></pre>
<p>During the installation process, a postgres user will be created.
Switch to the new postgres user while still maintaining sudo privileges using the following command.</p>
<pre><code>sudo -u postgres bash
</code></pre>
<p>Once switched, create a new postgresql user.</p>
<pre><code>createuser –pwprompt synapse_user
</code></pre>
<p>Enter the postgres shell and create the new database.</p>
<pre><code>psql
postgres=# createdb –encoding=UTF8 –locale=C –template=template0 --owner=synapse_user synapse;
</code></pre>
<p>Next, allow the new postgres user synapse_user connect to the synapse databases.
While still in postgres shell, type the following:</p>
<pre><code>postgres=# show hba_file;
</code></pre>
<p>Something like this should follow: <code>/etc/postgresql/11/main/pg_hba.conf</code></p>
<p>Leave postgres shell using <code>\q</code> and open <code>pg_hba.conf</code> in a text editor.
Add the following line:</p>
<pre><code>host        synapse synapse_user        ::1/128         md5
host        synapse synapse_user        127.0.0.1/32    md5
</code></pre>
<p>Restart the postgresql server for the changes to take effect.</p>
<pre><code>sudo systemctl restart postgresql
</code></pre>
<p>Now we need to configure the <code>homeserver.yaml</code> file to use postgresql instead of sqlite3.
But before making changes it’s important to keep backups of the original <code>.yaml</code> and <code>.db</code> files just in case anything goes wrong.
Additionally important, it’s important to make sure that the <code>.db</code> and <code>.yaml</code> file belongs to the proper user.
In this case the user should be matrix-synapse.</p>
<pre><code>sudo chown matrix-synapse:nogroup /etc/matrix-synapse/homeserver.yaml
sudo chown matrix-synapse:nogroup /var/lib/matrix-synapse/homeserver.db

sudo cp /etc/matrix-synapse/homeserver.yaml /etc/matrix-synapse/homeserver.yaml.backup
sudo cp /var/lib/matrix-synapse/homeserver.db /var/lib/matrix-synapse/homeserver.db.backup
</code></pre>
<p>Open <code>homeserver.yaml</code> in a text editor and change the following text.</p>
<pre><code>#database:
#  name: sqlite3
#  args:
#    database: /path/to/homeserver.db

database:
    name: psycopg2
    txn_limit: 10000
    args:
        user: synapse_user
        password: $your_password
        database: synapse
        host: localhost
        port: 5432
        cp_min: 5
        cp_max: 10
</code></pre>
<p>Once saved, stop the Matrix server.</p>
<pre><code>sudo systemctl stop matrix-synapse
</code></pre>
<p>Next, port the existing database using the built in command <code>synapse_port_db</code>.
For whatever reason I had an issue using relative file paths with this command, so I’ve included the full file path instead.</p>
<pre><code>synapse_port_db --curses --sqlite-database /var/lib/matrix-synapse/homeserver.db \
--postgres-config /etc/matrix-synapse/homeserver.yaml
</code></pre>
<p>Restart your Matrix server and verify that it is running correctly.</p>
<pre><code>sudo systemctl start matrix-synapse
</code></pre>
<h2>Change a Matrix user password using postgresql</h2>
<p>Now that your Matrix server is using a postgres database, we need to be able to access and change data items as we please.
For example, let’s say one of your users wants their password reset.</p>
<p>Open up the synapse database</p>
<pre><code>psql synapse
</code></pre>
<p>If you’d like to see the usernames and password hashes of all users in your database, run the following command</p>
<pre><code>synapse=# select name, password_hash from users;

name              |                        password_hash
------------------+--------------------------------------------------------------
@user1:domain.com | $2b$12$lk.TLplwaOThuenkqzj9k.6RxqtK9eaReS2faBVFofWwEdc4P.wbS
@user2:domain.com | $2b$12$xEAMCZZ9r8ShtEgkiCitUOf603W2IUIKh7oj8Ni3A6InOUY1h8iHC
(2 rows)
</code></pre>
<p>You may have noticed that the passwords are stored as a hashfile.
This means that the new password you create for your user needs to be hashed before updating the database.
Hashes are created using the command hash_password using a technique called <a href="https://en.wikipedia.org/wiki/Bcrypt">bcrypt</a>.
By default, the number <code>bcrypt_rounds</code> in the <code>homeserver.yaml</code> file is 12, but yours might be configured different.
In any case, it’s a good idea to reference the server’s <code>homeserver.yaml</code> file using the <code>-c</code> flag.</p>
<pre><code>hash_password -p $new_password -c /etc/matrix-synapse/homeserver.yaml
</code></pre>
<p>The result should be a hash of the specified password.</p>
<pre><code>$2b$12$y/jlqx9Cc5TPTr3O9itSMuyYnFb2L6XsGseIYD5W.yP0ypJYXtLhy
</code></pre>
<p>Copy the hash and go back into the postgres shell</p>
<pre><code>psql synapse
</code></pre>
<p>Update the desired user’s password</p>
<pre><code>synapse=# update users
set password_hash = ‘$2b$12$y/jlqx9Cc5TPTr3O9itSMuyYnFb2L6XsGseIYD5W.yP0ypJYXtLhy’
where name = ‘@user1:domain.com’;
</code></pre>
<p>If done so correctly, you should see</p>
<pre><code>UPDATE 1
</code></pre>
<p>The user should now be able to log in with their new password.
Once logged in, they should be able to change it through the client.</p>]]></description>
    </item>
    <item>
      <title>Now: November 2021</title>
      <link>https://orionc.dev/posts/11202022</link>
      <guid>https://orionc.dev/posts/11202022</guid>
      <pubDate>Sat, 06 Nov 2021 00:00:00 +0000</pubDate>
      <description><![CDATA[<h2>What I'm doing now</h2>
<p>As of November 2021:</p>
<p>Currently employed at <a href="https://www.cohu.com/">Cohu</a> as a Software Engineer in my hometown of San Diego, California.</p>
<p>Now that I'm employed full time, my spark for software side projects seems to have all but vanished.
Because I'm staring at a set of screens all day for extended periods of time I'm hesitant to do so at home as well.
Instead, I've taken on and rediscovered old hobbies that get me outside and away from the computer like skateboarding, backpacking, and rock climbing.</p>
<p>Current project list in order of importance:</p>
<ol>
<li>Nurture meaningful relationships</li>
<li>Learn to <a href="https://www.youtube.com/watch?v=dODnfBx_eLQ">kickflip</a></li>
<li>Improve and add content to this website</li>
</ol>
<p>This page was inspired by <a href="https://www.nownownow.com">nownownow.com</a>.</p>]]></description>
    </item>
    <item>
      <title>How to secure your SSH server</title>
      <link>https://orionc.dev/posts/secure-your-ssh-server</link>
      <guid>https://orionc.dev/posts/secure-your-ssh-server</guid>
      <pubDate>Wed, 05 May 2021 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>In order for a user to connect to a remote SSH server, the user needs the following three bits of data:</p>
<ol>
<li>A valid username</li>
<li>The correct password</li>
<li>The port SSH is configured to use on the remote server</li>
</ol>
<p>As a server admin, if you are able to obfuscate any of these three items your server becomes infinitely more secure from brute-force style attacks.
While this style of attacks are <a href="https://blog.codinghorror.com/brute-force-key-attacks-are-for-dummies/">generally for dummies</a>, you'd be surprised by how often they are successful.</p>
<p>Side node: most of the time the attacking IP addresses are from the other side of the planet (which is kind of neat).
As an example, here is one of many IP addresses that was constantly attempting connections to the machine running this website.</p>
<p><figure>
  <img alt="hong kong" src="/static/images/hongkong.png" />
  <figcaption>hong kong</figcaption>
</figure></p>
<p>If you're worried that your machine is being targeted, run the following command on your terminal.</p>
<pre><code>grep ‘Invalid’ /var/log/auth.log
</code></pre>
<p>You might see something like this.
This is a small snippet of what my VPS constantly had to deal with before fixing the issue.</p>
<pre><code>Invalid user iroda from 101.32.181.50 port 36710
Invalid user frank from 49.232.174.23 port 39118
Invalid user egon from 45.240.88.142 port 39456
Invalid user squirrel from 59.152.237.118 port 54674
Invalid user dragos from 129.211.16.182 port 34308
Invalid user miroslav from 128.199.216.49 port 44258
Invalid user admin from 144.34.170.120 port 58604
Invalid user visiteur from 59.152.237.118 port 46436
Invalid user aaaaa from 103.212.120.87 port 58578
</code></pre>
<p>Clearly, there are a LOT Of machines attempting to beat down the door and get inside.
As you can see, none of them really have a clue as to which username, password, or port to specify.
Every connection attempt is effectively throwing data at a wall and hoping something will stick.
Eventually, given enough time, something might.
Thankfully, you can have peace of mind by following a few simple steps.</p>
<p>Instead of using a password every time you log into your machine remotely, consider using an SSH key instead.
This removes the possibility (however incredibly unlikely) that one of these machines might guess your password.</p>
<pre><code>ssh-keygen
</code></pre>
<p>After the key is created on your local machine, copy it to your remote machine.</p>
<pre><code>ssh-copy-id username@domain_name
</code></pre>
<p>Once the server has your home machine’s key, you can remove the ability to log in via a password.
Be warned though, if you lose your key on your local machine for whatever reason you won’t be able to get back into your remote server.
Instead, you'll need to give the server another key.
It's good practice to remove old keys that aren't being used, as keeping the orphaned keys around is a <a href="https://www.csoonline.com/article/3196974/unmanaged-orphaned-ssh-keys-remain-a-serious-enterprise-risks.html">security risk</a>.</p>
<p>By default, SSH communicates using port 22.
One of the best ways to secure your connection is to change it.
Pick an alternate one to use, but be aware that a lot of ports are already <a href="https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports">used by other services</a>.</p>
<p>Once you’ve picked one out, edit <code>/etc/ssh/sshd_config</code> on your remote server and modify the following lines:</p>
<pre><code>Port $your_port
PermitRootLogin no
MaxAuthTries 3
PubkeyAuthentication yes
AllowUsers $your_username
PasswordAuthentication no (or yes, if that’s what you want)
PermitEmptyPasswords no
PrintLastLog yes
</code></pre>
<p>Only users specified in the 'AllowUsers' section will be allowed to connect, all others will be automatically denied.</p>
<p>Restart your SSH instance to enact the changes and exit the server.</p>
<pre><code>systemctl restart sshd
exit
</code></pre>
<p>Now connect to your server with the <code>-p</code> flag to specify the port.</p>
<pre><code>ssh -p $your_port username@domain
</code></pre>
<p>Your server is now more secure than when it started, but there are a few more things we can do to improve security.</p>
<h2>Ban the intruders</h2>
<p>Now that all three connection requirements have been secured, it’s important to be able to ban the IP addresses who are attempting brute force attacks.</p>
<pre><code>apt install fail2ban
</code></pre>
<p>Once it's installed, copy the default conf file to a local one.</p>
<pre><code>cp /etc/fail2ban/jail.{conf,local}
</code></pre>
<p>Edit the newly created local file and remove everything except for the <code>[sshd]</code> section.</p>
<pre><code>vim /etc/fail2ban/jail.local

[sshd]

enabled = true
port    = ssh
maxretry = 3
findtime = 10m
bantime = 24h
logpath = %(sshd_log)s
backend = %(sshd_backend)s
</code></pre>
<p>Restart the service and double check that it’s working correctly</p>
<pre><code>systemctl restart fail2ban
systemctl status fail2ban
</code></pre>
<p>After a few hours, run <code>grep 'banned' /var/log/fail2ban.log</code> to see the fruits of it's labor.</p>
<pre><code>WARNING [sshd] 217.93.169.142 already banned
WARNING [sshd] 18.209.187.21 already banned
WARNING [sshd] 116.59.25.196 already banned
WARNING [sshd] 59.152.237.118 already banned
WARNING [sshd] 153.36.233.60 already banned
WARNING [sshd] 114.141.167.190 already banned
</code></pre>
<h2>Set up a firewall</h2>
<p>ufw is a minimal firewall application for linux operating systems.
By blocking all other ports except for the ones you are using, you remove the vector by which attacks can originate from the the machine.</p>
<pre><code>apt install ufw
enable ufw

ufw allow $your_port
</code></pre>
<p>check that it’s running correctly</p>
<pre><code>ufw status
</code></pre>
<p>Additionally, it is imperitive that you allow other ports for the other services on your machine utilizes or hosts.
For example, if you were to host a website you should probably allow 80 (HTTP) and 443 (HTTPS) to allow users to connect to it.
For a list of which ports are related to which services, check out <a href="https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers">this helpful wikipedia page</a>.</p>
<p>And there you have it!
Your server should be a lot more secure now.
Remember to periodically check logs and monitor your server to keep it healthy and happy.</p>]]></description>
    </item>
    <item>
      <title>Now: April 2021</title>
      <link>https://orionc.dev/posts/04222021</link>
      <guid>https://orionc.dev/posts/04222021</guid>
      <pubDate>Mon, 05 Apr 2021 00:00:00 +0000</pubDate>
      <description><![CDATA[<h2>What I'm doing now</h2>
<p>As of April 2021:</p>
<p>Currently seeking employment in the greater San Diego area, but I'm open to work remotely.
If you are looking to hire a software developer or know someone who is, start a conversation with me at <a href="mailto:orion@orionc.dev">orion@orionc.dev</a></p>
<p>Here's my most <a href="/files/orion_crocker_resume.pdf">up-to-date</a> resume if you're interested.</p>
<p>As of April 22nd, I have satisfied all requirements for a Bachelor's in Computer Science at Portland State.
I expect to recieve my incredibly expensive piece of paper in the mail sometime in early June.</p>
<p>Current project list:</p>
<p>Gainful employment</p>
<ol>
<li>Albumartdb.com (coming soon!)</li>
<li>Improve and add content to this website</li>
<li>Maintain my 300 square foot garden</li>
<li>Make 2021 better than 2020</li>
</ol>
<p>This page was inspired by <a href="https://www.nownownow.com">nownownow.com</a></p>]]></description>
    </item>
    <item>
      <title>First static site refactor, circa 2021</title>
      <link>https://orionc.dev/posts/static-site-refactor</link>
      <guid>https://orionc.dev/posts/static-site-refactor</guid>
      <pubDate>Mon, 05 Apr 2021 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>Now that my degree is techinically done (more on that <a href="/now">here</a>), I had the free time to catch up on my project backlog.</p>
<p>After the last site refactor utilizing PHP, I realized that I was in way too deep for the scope of this site.
This is supposed to be a small personal website to write notes to myself and others, not something that needs to be overly complicated.</p>
<p>Similar to the previous rework, I set up some goalposts for myself to work towards:</p>
<ol>
<li>Easy to write and update blog posts</li>
<li>Written from scratch (mostly) </li>
<li>"Just works"</li>
<li>Looks and feels modern</li>
<li>Keep page size and bloat to a minimum</li>
</ol>
<p>Instead of using a language like PHP that dynamically renders a markdown page each time a new page is accessed on the site, I realized that it would be much more efficient to just serve a pre-rendered HTML file instead.
Then it hit me: Linux was more than capable of rendering markdown into HTML with its own native tools.</p>
<h3>Introducing dead simple static site generator</h3>
<p>As the name suggests, my proposed solution to this problem is dead simple.
dsssg uses nothing but the Linux shell and Make to create html files using templates for the header and footer.
If you’d like to check out the script, feel free to do so on my <a href="https://github.com/orioncrocker/dsssg">github page</a>.</p>
<p>Current bugs / known issues:</p>
<ul>
<li>Blog posts are displayed alphabetically rather than by date submitted</li>
<li>No proper 404 page (<a href="/some-page-that-doesnt-exist">fixed!</a> 04/06/2021)</li>
</ul>]]></description>
    </item>
  </channel>
</rss>