In my last post (which also happened to be My first post) I provided a little bit of context as to why I made this website. Now that I’ve gotten that over with, I’m going to continue on my merry way and pontificate on random and mostly technical things that I have been occupying my time. I figured a good place to start would be to capture how I made this website.

Markdown, please

As I’ve gotten older, I’ve begun to care more about retaining full control of my data. I’ve also developed more of a propensity towards simplicity, and that involves searching out things that I’m able to understand and easily navigate. Tools that just get out of my way. At this point, writing in Markdown has become second nature. I find myself using it even I don’t mean to.

Obsidian

I think most developers have heard of Obsidian by now - an application for writing and managing content in Markdown. Anyone who has written a lot of Markdown-based content will know that it can become unwieldy to manage over time, especially when linking between documents and using some more of its advanced features like tables. Obsidian makes these things easy, with real-time previews and the ability to automatically update links when you move things around.

Quartz

While Obsidian has its own (paid) option to publish to a website hosted on their servers, it is after all just a directory full of files. While there are several static website generators out there that support Markdown, none are quite as polished as Quartz- which also happens to be Obsidian compatible out of the box. If you haven’t seen Quartz before, I could not recommend it enough - it’s fast, has an extremely powerful search feature, live reloading, and is pretty much infinitely flexible if you’re willing to spend a little time learning TypeScript.

Running Quartz locally

As much as I love Quartz, I did not love how their documentation currently instructs users to clone the repository locally, install NodeJS, and store content within a fork of the Quartz project.

Custom tooling

To prevent installing Node on my computer, I put together a custom Containerfile that installs the necessary dependencies and clones the Quartz repo:

FROM registry.fedoraproject.org/fedora
 
ARG QUARTZ_REF=eccad3da5d7b84b0f78a85b357efedef8c0127fc
 
USER root
 
RUN dnf install -y git make nodejs && \
    npm install -g n && \
    n lts && \
    npm install -g npm@latest && \
    dnf remove -y nodejs
 
RUN git config --global --add safe.directory /repo
RUN cd /opt && git clone https://github.com/jackyzha0/quartz.git && \
    cd quartz && git checkout ${QUARTZ_REF} && \
    npm ci
 
COPY quartz.config.ts /opt/quartz/
COPY quartz.layout.ts /opt/quartz/
 
WORKDIR /opt/quartz

Note here how I clone Quartz into the image and only COPY the files that I need to customize.

Why am I currently using a random SHA rather than a tagged version? While I was in the process of building out this website I came across this issue - although they didn’t end up accepting my PR, I’m grateful that the maintainers of Quartz were willing to collaborate and fix the underlying problem.

Make it simple

Some things are just better off not being rewritten in JavaScript. I think Make is one of these things. It might have first came out closer to the Moon landing than when I built this website, but it’s still more powerful than anything that’s been written in recent years and it’s way less likely to be abandoned as the project some dude wrote over the weekend and shared on Hacker News.

See the full version of my Makefile for this website for more information, but the gist of it is here:

.DEFAULT_GOAL := scratchpad
 
.PHONY: scratchpad
scratchpad: check_image_uptodate
	@$(CONTAINER_RUNTIME) run --rm -ti \
		-p $(QUARTZ_SERVER_PORT):$(QUARTZ_SERVER_PORT) \
		-p $(QUARTZ_WEBSOCKET_PORT):$(QUARTZ_WEBSOCKET_PORT) \
		$(COMMON_MOUNTS) \
		$(SCRATCHPAD_IMAGE_NAME) \
 
.PHONY: check_image_uptodate
check_image_uptodate: image
	@echo "Checking current image SHA..."
	@current_sha=$$($(CONTAINER_RUNTIME) inspect --format='{{.Id}}' $(SCRATCHPAD_IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || echo "none"); \
	stored_sha=$$(cat $(IMAGE_ID_FILE) 2>/dev/null || echo "none"); \
	if [ "$$current_sha" != "$$stored_sha" ] || [ -z "$$current_sha" ]; then \
		echo "Building image... $$current_sha $$stored_sha"; \
		$(MAKE) -B -f $(MAKEFILE_PATH) image; \
	else \
		echo "Image is up to date."; \
	fi
 
.PHONY: image
image: $(IMAGE_ID_FILE)
 
$(IMAGE_ID_FILE): .generated Containerfile quartz.config.ts quartz.layout.ts
	$(CONTAINER_RUNTIME) build -t $(SCRATCHPAD_IMAGE_NAME):$(IMAGE_TAG) .
	$(CONTAINER_RUNTIME) inspect --format='{{.Id}}' $(SCRATCHPAD_IMAGE_NAME):$(IMAGE_TAG) > $(IMAGE_ID_FILE)

The result here is that on a machine with just make and podman (or docker) installed, I can simply clone this repo and run the command:

âžś  shanemcd.github.io git:(main) âś— make
Checking current image SHA...
Image is up to date.
npx quartz build --serve --port=6006 --directory=/repo/content

 Quartz v4.5.0

Cleaned output directory `public` in 2ms
Found 4 input files from `/repo/content` in 5ms
Parsed 4 Markdown files in 112ms
Filtered out 0 files in 24ÎĽs
Emitted 16 files to `public` in 71ms
Done processing 4 files in 191ms
Started a Quartz server listening at http://localhost:6006

This handles building (and rebuilding) the container image, starting serving Quartz, mounting in the site content, and mapping ports for the web server and live-reload websocket.

Deploying via GitHub Pages

Most people are familiar with using GitHub Pages to host a Jekyll website, but it is also possible to serve up a pre-built HTML website that has been pushed to a branch. To automate this process I threw together this GitHub Action:

# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
 
permissions:
  contents: write
 
on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]
 
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false
 
env:
  CONTAINER_RUNTIME: docker
  RUN_CMD: >-
    QUARTZ_BUILD_OPTS='-o /repo/public' make -f /repo/Makefile public
 
jobs:
  # Single deploy job since we're just deploying
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: Build site
        run: make run
 
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: public
 
      - name: Deploy 🚀
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          folder: public
          branch: gh-pages
          clean-exclude: pr-preview
          force: false

Previewing changes

I might be lazy, but I also love finding a good problem to over-engineer. I had originally done this for a project at work, but was able to reuse here without much effort.

Unfortunately GitHub Pages does not natively support previewing changes. It seems like that will come at some point, but it’s been almost 5 years and there’s still ETA. Luckily the Deploy PR Preview action will do just fine for now.

Here is how I’m using it. The only thing I’ve changed is an additional invocation of marocchino/sticky-pull-request-comment, which I found rossjrw/pr-preview-action uses under the hood. I update the comment it posts to add a little reminder that the link will only work once the Action completes.

---
name: preview
 
permissions:
  contents: write
  pull-requests: write
 
concurrency: preview-${{ github.ref }}
 
on:
  pull_request_target:
    types:
      - opened
      - reopened
      - synchronize
      - closed
 
env:
  CONTAINER_RUNTIME: docker
  RUN_CMD: >-
    QUARTZ_BUILD_OPTS='-o /repo/public' make -f /repo/Makefile public
 
jobs:
  preview:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
          fetch-depth: 0
 
      - run: |
          git log
 
      - name: Build site
        if: github.event.action != 'closed'
        run: make run
 
      - name: Deploy preview
        uses: rossjrw/pr-preview-action@v1
        with:
          source-dir: public
 
      - name: Update preview comment with note about being patient
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          header: pr-preview
          append: true
          message: |
            > [!IMPORTANT]
            > The link above will not work until the Pages deployment is complete. You can find this under [Actions -> pages-build-deployment](https://github.com/andyettanotherorg/shanemcd.github.io/actions/workflows/pages/pages-build-deployment).

Which looks like this:

This might be overkill, but it lets me test things out without iterating directly on my main branch and helps for cases where I might not have access to Obsidian or the ability to test a site build locally.

Wrapping up

I mostly captured this for my own posterity, but if for whatever reason you find yourself wanting to learn more please about any of this please check out the source for this website. If you have any questions or just want to say hi, my email is on my GitHub profile.