summaryrefslogtreecommitdiff
path: root/posts
diff options
context:
space:
mode:
authorBrandon C. Irizarry <brandon.irizarry@gmail.com>2026-03-08 19:16:04 -0400
committerBrandon C. Irizarry <brandon.irizarry@gmail.com>2026-03-08 19:16:04 -0400
commitcdecb8191a791b8db66effe61de16cb3e3904238 (patch)
tree815c42e49e87ab89b103cf876437cc985f078675 /posts
parentc4af1098ffebffa02d3a055d7570193acd77985d (diff)
Reorder posts according to new publishing algorithm
Diffstat (limited to 'posts')
-rw-r--r--posts/adding-cgit-subdomain-to-personal-site.md195
-rw-r--r--posts/apple-time.md21
-rw-r--r--posts/making-cgit-go-gettable.md101
-rw-r--r--posts/smoothing-over-more-markdown-pain-points.md73
-rw-r--r--posts/understanding-pratt-parsing.md155
-rw-r--r--posts/writing-my-blog-with-eleventy.md90
6 files changed, 635 insertions, 0 deletions
diff --git a/posts/adding-cgit-subdomain-to-personal-site.md b/posts/adding-cgit-subdomain-to-personal-site.md
new file mode 100644
index 0000000..c62fde8
--- /dev/null
+++ b/posts/adding-cgit-subdomain-to-personal-site.md
@@ -0,0 +1,195 @@
++++
+title = "Adding a CGit Subdomain To My Site"
+tags = ["linux", "nginx", "certbot", "cgit"]
+summary = "Setting up CGit on my VPS."
+date = 2026-03-06
++++
+
+# Motivation
+
+To update the content of my blog, I have to do something of a
+dance. My blog's content started out life as a subdirectory of my SSG
+project, but now lives in its own separate Git repo. This is super
+convenient since I now can host my blog wherever I want on my VPS's
+filesystem, for example in a folder called `brandons_blog`. So right
+now what I'm doing is this:
+
+1. Commit all changes.
+2. Push the changes to GitHub, where it's currently hosted in a
+ somewhat centralized manner.
+3. Log in via SSH into my VPS.
+4. Perform a `cd` into the `brandons_blog` directory, and run `git
+ pull`.
+
+However, I thought, "wouldn't it be nice if I could push **directly**
+to the VPS repo?" And so I started working on that idea in the obvious
+manner: add the VPS repo as a remote, such that I would be pushing to
+`vps/main` alongside of `origin/main` (where `origin` points to
+GitHub).
+
+It turns out that this alone is a shade more complicated that it would
+seem: I had to first add a new `git` user (see [this tutorial](https://landchad.net/git/)),
+and then adjust my SSH configuration appropriately to allow for this
+pseudo-user to log in via SSH (since I would be pushing into a
+directory now owned by it.)
+
+I quickly learned, to my dismay, that I couldn't push to the VPS
+remote if it's not a bare repo. A bare repo is one initialized with
+`git init --bare`, so that it doesn't have a working directory
+populated with files. However, the buildablog server expects to see a
+working directory with blog files (not just blobs, for example), so
+this doesn't solve my problem.
+
+# Cgit
+
+What I ended up doing in the end didn't solve this problem, but it
+ended up becoming an interesting rabbit hole in its own right.
+
+I ended up adding the Cgit web interface to my site, available via
+<https://git.brandonirizarry.xyz>. I checked out a [tutorial](https://landchad.net/cgit/) on
+how to do it, but their suggested Nginx setup was off in some
+parts.
+
+After a ton of false starts, I ended up doing slightly different. Note
+that this assumes that you've already followed the aforementioned
+tutorial on setting up your `git` user and its home directory. As a
+quick addon to that, I suggest adjusting the permissions for the
+`/var/git` directory to 775 (I had initially found they were set to
+770.) This allows the Cgit web interface to actually read and display
+your hosted repos, which is after all the point.
+
+Here's what I did. I'm phrasing these in the imperative mood since it
+reads better than beginning everything with "I" + past-tense, and the
+steps themselves are also suitable as a potential HOWTO for my future
+self:
+
+1. Add two new *external records* for `git.brandonirizarry.xyz` to my
+ site's DNS configuration over on Epik: the A (IPv4) and AAAA (IPv6)
+ records, per usual if you're already somewhat familiar with this
+ thing.
+
+2. Start out by adding the Nginx configuration of the `http` version
+ of the site. Not only does this make adding the TLS certificate
+ later on painless, you can immediately verify that your new
+ subdomain is, in fact, being hosted. Add the following server block
+ to your published Nginx configuration, and then reload Nginx
+ (e.g. `sudo systemctl restart nginx.service`):
+
+```nginx
+server {
+ listen 80;
+ listen [::]:80;
+
+ # Replace this with your actual site.
+ server_name git.example.org;
+
+ root /usr/share/cgit ;
+ try_files $uri @cgit ;
+
+ location ~ /.+/(info/refs|git-upload-pack) {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param GIT_HTTP_EXPORT_ALL 1;
+
+ # This part assumes your git user's home directory is /var/git.
+ fastcgi_param GIT_PROJECT_ROOT /var/git;
+ fastcgi_param HOME /var/git;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+
+ location @cgit {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_param HTTP_HOST $server_name;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+}
+```
+
+3. Add a TLS certificate for your subdomain. I admit that I took a
+ somewhat nonlinear path in achieving my setup, but this should be
+ as simple as running `sudo certbot --nginx`, and then selecting
+ your subdomain from the menu options. Here I'm assuming you've
+ already gotten a certificate for your main site, hence you need a
+ certificate only for your new subdomain.
+
+From there you shouldn't even have to restart Nginx: you should see
+that your subdomain is available over `https`.
+
+# Five Strikes and You're Out
+
+I learned, through banging my head against various misconfigurations
+(both from the DNS and Nginx sides), that Let's Encrypt (what Certbot
+uses to issue the certificate) [imposes a rate limit](https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-identifier-per-account) on
+certificate issues per identifier (five per hour), which doesn't
+forgive botched attempts at certificate registration. The solution
+here is to run Certbot with the `--test-cert` flag, which uses Let's
+Encrypt's staging area, which has a much more forgiving rate limit.
+
+In digging a bit through the Let's Encrypt [forums](https://community.letsencrypt.org/), I learned
+about two super helpful sites for debugging DNS and certificate
+issues:
+
+1. <https://letsdebug.net>
+
+ Super helpful for figuring out issues with bad certificates.
+
+2. <https://dnsviz.net>
+
+ For debugging a site's DNS config, which I was messing up since I
+ wasn't sure in the beginning how to properly add a subdomain to my
+ DNS records (somewhat confusingly, Epik places the word
+ "subdomain" alongside the CNAME section, making me think CNAME had
+ something to do with it, which it doesn't.)
+
+# A Ghost in the Machine?
+
+I finally managed to [host](https://git.brandonirizarry.xyz) my Cgit dashboard on my site, which
+currently contains only my blog repo. I even managed to share the link
+with a friend of mine, who was successfully able to view it from their
+end.
+
+However, when going through some exercises in *The Go Programming
+Language* (a story for another time), I happened to cavalierly make a
+GET request to that subdomain, which then reported a TLS error. In my
+mind this seemed somewhat bonkers, since, after all, everything was
+already up and running, no? So late that evening I had to jump back
+onto the VPS and do some bespoke troubleshooting.
+
+It looked like there were some redundant server blocks in my Nginx
+config file that were added while I was throwing everything and the
+kitchen sink at getting a valid TLS certificate. So what I did in the
+end was remove everything Certbot had added concerning my `git`
+subdomain, essentially reverting back to just the server block shown
+just above, and repeating those exact steps — including first
+verifying service over `http`. This part of the process for me was the
+most satisfying, since it proves that the mere act of publishing a
+website on the Web is, at its core, not all that difficult. One thing
+different this time though was that, per the options Cerbot presents
+you, it sufficed to reinstall the existing certificate, as opposed to
+applying for a new one.)
+
+After that, everything was in order! I even checked the site the next
+morning just to make sure it had stayed that way. To date, everything
+looks good.
+
+# In the End...
+
+In the end, I didn't actually solve my initial problem, but still went
+down an interesting rabbit hole, and now have a convenient tool at my
+disposal — my own poor-man's GitHub — for personal use. For now, I may
+well only use it for throwaway Go packages, in case I don't feel like
+using workspaces.
+
+
+
+
+
+
+
+
+
+
diff --git a/posts/apple-time.md b/posts/apple-time.md
new file mode 100644
index 0000000..e012fc5
--- /dev/null
+++ b/posts/apple-time.md
@@ -0,0 +1,21 @@
++++
+title = "My first post"
+summary = "Stub post for buildablog"
++++
+
+# Roses are red
+
+Violets are blue, etc.
+<br>
+```go
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello world!")
+}
+```
+<br>
+
+[This is a link](https://example.com)
diff --git a/posts/making-cgit-go-gettable.md b/posts/making-cgit-go-gettable.md
new file mode 100644
index 0000000..0c197ca
--- /dev/null
+++ b/posts/making-cgit-go-gettable.md
@@ -0,0 +1,101 @@
++++
+title = "Making Cgit Repos Installable as Go Packages "
+summary = "Steps I took to configure my private Cgit repo hub to host Go packages."
+tags = ["nginx", "cgit", "go"]
+date = 2026-03-07
++++
+
+# Putting my private Git server to good use
+
+In my quest to learn the real ins and outs of Go by going through *The
+Go Programming Language*, I decided to use my newly minted private
+repo [hub](https://git.brandonirizarry.xyz) to store exercises from the book as packages I can reuse
+for later exercises. For example, I decided to make the lissajous
+example from Chapter 1 into a separate installable [package](https://git.brandonirizarry.xyz/lissajous). The
+lissajous package is used to create a GIF of a [Lissajous curve](https://en.wikipedia.org/wiki/Lissajous_curve),
+which was a staple visual effect in old sci-fi movies.
+
+However, simply "go-getting" from the repo's URL, as you would in the
+case of GitHub, isn't that simple. There were multiple hiccups along
+the way which I had to overcome. What follows is my best attempt to
+piece together what I did to finally enable Go package installation
+from my private Git server. Hopefully this account will serve two
+purposes:
+
+1. Set people straight who are looking for answers to this same
+ question.
+
+2. Serve as a reference for my future self in case I have to do this
+ again.
+
+# Steps
+
+## Inject the appropriate HTML `meta` tag from Nginx
+
+This [excellent](https://anirudh.fi/go-get-cgit ) blog post by Anirudh
+Oppiliappan set me on the right path for fixing an error I encountered
+early on involving a missing `go-import` something-or-other:
+
+
+I ended up putting the `sub_filter` stuff inside the `location @cgit`
+block:
+
+```nginx
+ location @cgit {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_param HTTP_HOST $server_name;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+
+ # Make our repos go-gettable.
+ sub_filter '</head>'
+ '<meta name="go-import" content="$host$uri git https://$host$uri"></head>';
+
+ sub_filter_once on ;
+ }
+```
+
+Remembering the semicolons here is important. You can always use `sudo
+nginx -t` to test whether your current config is valid.
+
+
+## Configure Git to use SSH for the Git server
+
+Add this block to your home directory's `.gitconfig` file, making the
+appropriate substitution for "example.com":
+
+```ini
+[url "git@example.com:"]
+ insteadOf = https://git.example.com/
+```
+
+Alternatively, you can issue the equivalent command line invocation:
+
+```
+git config --global url."git@example.com".insteadOf "https://git.example.com/"
+```
+
+Now, whenever you invoke `go get`, you'll be prompted for your SSH
+password.
+
+Note that, in this case, the `git` in `git@example.com` refers to the
+Linux *user* on your remote server that owns the directory containing
+all your Git repos. This syntax mirrors the one used when logging into
+the same server via SSH, viz. `ssh git@example.com`.
+
+## Set GOPRIVATE (optional?)
+
+I'm not sure I need to set this in my case, since AFAICT this has more
+to do with close-sourcing code, which isn't my intention here. But I
+threw it in just in case.
+
+Since I want **all** my repos to be potentially installable as Go
+packages for now, so I use a glob to indicate that:
+
+`go env -w GOPRIVATE=git.brandonirizarry.xyz/*`
+
+Initially, I had set `GOPRIVATE` to point to the `lissajous` repo
+only, though this glob technique should work also (and be way easier
+to maintain.)
diff --git a/posts/smoothing-over-more-markdown-pain-points.md b/posts/smoothing-over-more-markdown-pain-points.md
new file mode 100644
index 0000000..d9432ac
--- /dev/null
+++ b/posts/smoothing-over-more-markdown-pain-points.md
@@ -0,0 +1,73 @@
++++
+title = "Smoothing Over More Markdown Pain Points"
+tags = ["blogging", "emacs"]
+date = 2025-12-05
+
+summary = """
+
+A post from my old blog about a small Elisp helper library I wrote for \
+generating a table of contents for a Markdown file.
+
+"""
+
++++
+
+# I Couldn't Keep It Together
+
+As I go about editing these blogs as Markdown buffers inside Emacs,
+I've been running into a snag of sorts. Previously, I had been
+exporting Org to Markdown one way or another. I observed how the
+Markdown output inserts an anchor tag above a given section as a way
+to link to it from the table of contents. I decided to continue this
+practice in my now hand-wrought Markdown. However, manually keeping
+the table of contents in sync with changes in the outlining of the
+content itself—adding and removing sections, renaming sections, and so
+on—is a pain. And so I came up with a way to sync the two, using
+Emacs Lisp. Emacs Lisp, or Elisp for short, is the Emacs editor's
+extension language: the language you use to write Emacs plugins.
+
+# Elisp For The Win
+[Having written](2025-12-03) about my zany Elisp-based Java build system made
+me recall those times: I could once again rise to the challenge, and
+solve this new problem with Elisp. That's exactly what I did. I wrote
+two functions, `bcimd-generate-toc` and `bcimd-remove-toc`. The first
+one regenerates the table of contents based on the current set of
+level-1 headings. The second one erases the existing table of
+contents, along with the connected anchor tags. It's used by the first
+function to start out with a clean slate before defining the new table
+of contents.
+
+I decided to collect these functions into an installable package. It's
+currently available through Emacs' version-control installation
+mechanisms (for example, `package-vc-install`.) See the [project
+README](https://github.com/BrandonIrizarry/bcimd) for more details.
+
+I find Emacs' VC-based package installation facilities extremely
+convenient for writing my own bespoke stuff which I otherwise have to
+manage locally. I store it remotely, and install it as an *official*
+package, much like how Go packages work. In this way, I can even share
+my work with the community.
+
+# Yet Another Yasnippet Testimonial
+
+I also decided to go the extra mile and use a [Yasnippet](Yasnippet) snippet
+that generates some stock front matter. In particular, the title of a
+given blog post is ripped directly from the name of the file itself,
+which first undergoes some on-the-fly formatting. I got this idea from
+[another blog](https://weblog.masukomi.org/2024/07/19/using-org-mode-with-hugo/) where the author runs with the whole Yasnippet idea
+to set up her `ox-hugo` front matter. In fact, this is what turned me
+on to the idea of Yasnippet as a useful tool in general; that is, it
+isn't just a lazy man's way of inserting a for-loop into source code.
+
+# Now I Can Keep It Together!
+
+I now use table-of-contents regeneration frequently: writing the
+package was a worthwhile investment of time.The only minor hiccup is
+that I have to remember to leave two spaces in between headers, so
+that the anchor tag doesn't eliminate all whitespace between sections,
+an effect which looks aesthetically jarring. I may address this in the
+future, but I first need to see how this package interacts with, for
+example, level-2 headers. Other ideas include running
+table-of-contents generation as an `after-save-hook`, and eventually
+writing a full-blown minor-mode. But for now, I'm taking it easy on
+this project: I still have to work on other things.
diff --git a/posts/understanding-pratt-parsing.md b/posts/understanding-pratt-parsing.md
new file mode 100644
index 0000000..d88ba88
--- /dev/null
+++ b/posts/understanding-pratt-parsing.md
@@ -0,0 +1,155 @@
++++
+title = "Understanding Pratt Parsing"
+tags = ["programming languages"]
+date = 2025-12-02
+
+summary = """
+
+A post I had written about Pratt parsing, in the context of a \
+programming language I was designing at the time.
+
+"""
+
++++
+
+# Introduction
+
+I've forgotten how I came across Pratt parsing specifically. I had
+been working on an interpreter for a programming language based on the
+one loosely described in Greg Michaelson's *An Introduction to
+Functional Programming Through Lambda Calculus*. I had managed to
+implement simple arithmetic, and even extended the basic lambda
+calculus spec with assignment expressions (a feat which I was very
+proud of.)
+
+However, some parts of my implementation felt a bit hacky (for
+example, how I had implemented `letrec`), and my implementation of
+lazy evaluation, while mostly complete, ultimately turned out to be
+buggy.
+
+At first, I decided to rewrite the project from scratch. One major
+guiding factor was to narrow the scope of the project, borrowing some
+advice from [Zed Shaw](https://learncodethehardway.com/blog/32-very-deep-not-boring-beginner-projects/). I got around to writing a new tokenizer,
+and then I was on to writing the parser. My initial parser used the
+recursive descent technique, taking advantage of the simplified lambda
+calculus grammar used in Michaelson's text (for example, parentheses
+are always used for application terms there.)
+
+This time though, I wanted to try something different. And so,
+rummaging through the internets, I stumbled across Pratt parsing.
+
+# "It's like a burrito"
+
+Understanding Pratt parsing ended up being much harder than I
+expected. I ended up searching through a bunch of examples online:
+
+1. [Alex Kladov's](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) Rust-based tutorial.
+2. [Eli Bendersky's](https://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing) Python-based tutorial.
+3. Bob Nystrom's [intro](https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/) to the subject, as well as the relevant
+ chapter in his [Crafting Interpreters](https://craftinginterpreters.com/compiling-expressions.html).
+4. Vaughan Pratt's [original paper](https://tdop.github.io/). Shout-out to the legend who
+ put this up as a GitHub Pages site!
+5. Douglas Crockford's celebrated [article](https://crockford.com/javascript/tdop/tdop.html) on the subject deserves
+ honorable mention, though my JavaScript is currently rusty and so I
+ didn't look into it in any depth.
+
+Alex Kladov in his post calls Pratt parsing the "monad tutorial of
+syntactic analysis". As I had recently become familiar with the
+concept of "monad" in a [philosophical](https://en.wikipedia.org/wiki/Monad_(philosophy)) sense, I aksed ChatGPT
+where the reference comes from: it turns out that it's an inside joke
+involving *Haskell* monads. I'm not an expert, but my readings have
+given me enough of an inkling to see the connection: Pratt parsing
+isn't a discrete "thing" with exactly one shape: it's more of a
+technique, if you will—a design pattern—which can assume various
+manifestations.
+
+A good example of this is iteration, because we all recognize it when
+we see it, even when we don't know the language—but no one syntactic
+construction sufficiently defines it. For-loops, while-loops, Python
+generator functions, and [optimized tail-recursive functions](https://mitp-content-server.mit.edu/books/content/sectbyfn/books_pres_0/6515/sicp.zip/full-text/book/book-Z-H-11.html#%_sec_1.2.1 ) all
+count as iteration.
+
+After struggling for some time, I finally managed to distill the
+essence of the algorithm, which I present here as pseudocode:
+
+```
+parse(level):
+ t ← next(stream)
+ acc ← nud_dispatch(t)
+
+ while level < precedence(peek(stream)):
+ t ← next(stream)
+ acc ← led_dispatch(t, acc)
+
+ return acc
+```
+
+Unwrapping what this does exactly is a surprisingly nuanced task,
+precisely because the algorithm is more about technique than
+structure. Because of this, I won't pretend to be up to the task
+here. Nevertheless, a brief synopsis is warranted.
+
+There is a global `stream` of tokens, such that `next(stream)`
+consumes and returns the next token, and `peek(stream)` returns the
+next token without consuming it. The function `nud_dispatch`
+interprets `t` as a *null denotation*, or "nud" for short, and
+initializes `acc`. The function `led_dispatch` interprets `t` as a
+*left denotation*, or "led" for short. It accumulates a value into
+`acc` using the existing value of `acc` (hence the choice of name.)
+Both dispatch functions call `parse` recursively in all but the most
+trivial cases.
+
+When `peek`ing the stream reveals a token with higher precedence than
+the current `level`, the while loop exits and `acc` is returned.
+
+The algorithm is initialized by calling `parse(0)`.
+
+# Down To Brass Tacks
+
+My approach was to take Eli Bendersky's full source code at the bottom
+of his post, and start chiseling away at it. What I ended up with was
+the same simple arithmetic calculator, only with a different
+architecture: I moved away from Eli's object-oriented approach towards
+something closer to the formulation given in the previous section.
+
+In the end, I was amazed at how simple and robust the actual
+implementation turned out to be! I feel that what I came up with (at
+this stage, anyway) is arguably simpler than even many of the examples
+I initially came across: for example, it isn't necessary to add space
+between precedence levels (10, 20, etc.), since you can use an enum to
+take care of any ordering needed. Also, using even and odd precedence
+levels (for handling right associativity) is unnecessary. For example,
+say you have precedence levels `MULTIPLICATION = 2` and
+`EXPONENTIATION = 3`. The algorithm cleverly avoids clashing
+`EXPONENTIATION-1` with `MULTIPLICATION` when enforcing right
+associativity for exponentiation. I found this to be one of the more
+remarkable aspects of the algorithm.
+
+# Wanting More
+
+To be fair, my calculator app technically doesn't parse arithmetic
+expressions: it evaluates them wholesale. This is OK: instead of
+accumulating an AST, I'm accumulating an arithmetic result.
+
+Because of how compelling the calculator app turned out to be, I
+decided to stop work on the lambda calculus project, and instead work
+on expanding the calculator into a full-blown programming language,
+albeit a simple one. I've already made progress in this direction: in
+addition to arithmetic (including trig functions!), the application
+currently supports variable assignment.
+
+Ideally, I'd like something with
+<br></br>
+
++ Booleans
++ Conditionals
++ Loops
++ Functions
++ Proper lexical scoping, even for conditional and loop blocks
+<br></br>
+I'll see how many of these I manage.
+
+
+
+
+
diff --git a/posts/writing-my-blog-with-eleventy.md b/posts/writing-my-blog-with-eleventy.md
new file mode 100644
index 0000000..dc78e51
--- /dev/null
+++ b/posts/writing-my-blog-with-eleventy.md
@@ -0,0 +1,90 @@
++++
+title = "Writing My Blog With Eleventy"
+tags = ["blogging"]
+date = 2025-12-03
+
+summary = """
+
+This is a reproduction of a post I had on my old blog, which I've \
+since migrated to a custom engine.
+
+"""
+
++++
+
+# Introduction
+
+This is *at least* my third time trying to start a blog.
+
+First, I experimented with using Org Mode's HTML exporting feature to
+create posts; unfortunately, that didn't get me far, though there are
+some [interesting attempts](https://one.tonyaldon.com/) by others to this end. I might've
+published this material at some point, but at any rate it didn't stay
+up long. An early topic from this time include a post about a [Java
+build system](https://github.com/BrandonIrizarry/Hydraulic-Make) I once wrote that scanned a `.java` file for its
+dependencies (defined by things like package imports and code syntax),
+so that those would get passed into `javac` along with the target
+file.
+
+# Hugo
+I then started writing a blog using Hugo. Hugo was my first encounter
+with an SSG. Because of this, I was a bit impatient with Hugo, and hit
+a wall every time I came across any sort of complexity. I also got
+frustrated with how themes never follow a consistent template; each
+does something different, with different elements, and so each one
+effectively has different rules. In the end, I published a blog post
+or two on GitHub pages using this setup. It was passable, but in the
+end configuring it still felt wonky and cargo-culted.
+
+Another reason for why I didn't have success with Hugo was my use of
+`ox-hugo`. It's a fun package, and you can tell the author put a *lot*
+of love into it. However, using Org Mode as a middleman between you
+and Hugo obfuscates the nature of Hugo, something I'm realizing now as
+I go deeper into using Eleventy.
+
+
+# Eleventy: The Soup Actually Tastes Good
+
+I went ahead and did a little bit of "shopping" for SSGs. I ran into
+[Eleventy](https://www.11ty.dev). I watched the author's [intro video](https://www.youtube.com/watch?v=kzf9A9tkkl4), and
+immediately took a liking to it. After a few false starts, I cloned
+their [official starter project](https://github.com/11ty/eleventy-base-blog), tweaked it here and there, and
+the rest is what you're currently looking at.
+
+A huge shift in my thinking which made the leap from Hugo to Eleventy
+possible occurred when I learned to stop worrying and love the
+Markdown.
+
+I used to think of Markdown as an icky, second-rate version of Org
+Mode. Then, I eventually got the hang of writing Markdown using Emacs'
+`markdown-mode` package, which is a [masterpiece](https://jblevins.org/projects/markdown-mode/) of a plugin: it
+makes the experience of writing Markdown rival that of using Org, and
+smoothes out a lot of Markdown's pain points (significant whitespace,
+noisy links, etc.) And so I slowly let go of the attachment of using
+Org Mode in all the things, and embraced the idea of writing blog
+posts directly in Markdown; this also alleviated the complexity of
+sundry issues arising from exporting from Org to Markdown.
+
+At first, Eleventy looks like a deceptively complex pile of language
+soup: JS, Markdown, templating languages, HTML, and CSS—at times all
+occurring within the same file—all somehow live under one
+roof. However, tweaking the starter project ended up being a
+relatively easy, even pleasant experience.
+
+# Painless Deployment
+
+Even deployment is simple. This site's content is version-controlled
+locally. I then build the site, then simply `scp` the `_site`
+directory to the appropriate directory in my VPS, where this blog is
+hosted. The previous remote `_site` directory is simply overwritten
+with the new files. I don't need a GitHub workflow, as I did when
+using Hugo with GitHub pages; I don't even need to push to a remote
+repo. Copying the files suffices.
+
+# Conclusion
+
+On the one hand, I'm nowhere near able to make something like the
+starter project from scratch. On the other hand, neither am I
+daunted. Eleventy in a sense reminds me of Emacs, in that there's a
+certain joy to be found in its eclectic complexity. I look forward to
+continue using Eleventy as I grow this blog.