Moving to GitHub Actions (and adding .txt posts) ================================================================================ This year I moved the blog from GitHub Pages’ built-in Jekyll to a GitHub Actions workflow, then added .txt URL support for posts. Inspired by Terence Eden’s blog post (https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites/) about text-only websites. Why move to GitHub Actions? GitHub Pages runs Jekyll in safe: true mode, which disables custom plugins. It limited me to their whitelist of approved plugins, such as jekyll-sitemap (https://github.com/jekyll/jekyll-sitemap) and jekyll-feed (https://github.com/jekyll/jekyll-feed). That was fine for years, but it meant I couldn’t build anything more interesting without resorting to workarounds. Moving to GitHub Actions removed that restriction entirely. With Actions, I control the build environment and can run any Jekyll plugin. One thing broke in the migration: GitHub Pages automatically included the jekyll-github-metadata (https://github.com/jekyll/github-metadata) plugin, which populated site.github.* variables like repository URLs and build revisions. My templates relied on these, so I wrote a replacement plugin (https://github.com/omgmog/omgmog.github.com/blob/main/_plugins/github_metadata.rb) that extracts the same info from Git and GitHub Actions environment variables. The workflow itself is nothing fancy - it checks out the code, sets up Ruby, builds Jekyll, and deploys: - name: Build with Jekyll run: bundle exec jekyll build --baseurl "$" env: JEKYLL_ENV: production This workflow runs on every push to main then deploys the build via GitHub’s standard Pages actions, so we have the same simple workflow as with Jekyll on GitHub Pages. The .txt format idea With custom plugins enabled, I could finally build that .txt feature. I soon realised it would require an ungodly amount of hoop jumping to make it work without plugins: * Creating stub files for each .txt version would mean maintaining duplicates or setting up some fragile build script and committing generated content to the blog source repo * Duplicating post content entirely would become a maintenance nightmare * Post-processing the built site would be fragile and wouldn’t integrate with Jekyll’s metadata system Another custom plugin (https://github.com/omgmog/omgmog.github.com/blob/main/_plugins/txt_format_generator.rb) sorted this out. It generates the .txt files during Jekyll’s build process, keeps them in sync automatically, and adds the right metadata for linking between formats. The first part was creating the .txt URLs themselves. I used Jekyll’s PageWithoutAFile (https://github.com/jekyll/jekyll/blob/master/lib/jekyll/page_without_a_file.rb) class to create .txt versions without actual files on disk. Each post gets a .txt URL at /post/slug.txt. Here’s how the virtual page creation works: class TxtFormatPage < PageWithoutAFile def initialize(site, post) @site = site @base = site.source collection_path = post.collection.label == "posts" ? "post" : post.collection.label slug = post.data['slug'] # Creates /post/slug.txt @dir = collection_path @name = "#{slug}.txt" self.process(@name) self.data = { 'layout' => 'plain', 'permalink' => "/#{@dir}/#{@name}", 'post' => post } # Generate Liquid content that references the original post self.content = <<~LIQUID {% include txt-format.html %} LIQUID end end Those virtual pages needed content, which meant converting HTML posts to plain text. I used the html_to_plain_text (https://github.com/soundasleep/html_to_plain_text) gem for the main conversion, but it needed post-processing to get the output looking how I wanted. The template (https://github.com/omgmog/omgmog.github.com/blob/main/_includes/txt-format.html) pipes the content through custom Liquid filters: {{ target.content | images_to_urls | unwrap_links | html_to_plain_text_convert | wrap_lines | collapse_blank_lines }} Here’s what the filters do: images_to_urls - Converts image tags to [IMAGE: url] references: def images_to_urls(input) site_url = @context.registers[:site].config['url'] input.gsub(/]+src=["']([^"']+)["'][^>]*>/i) do url = $1 url = "#{site_url}#{url}" if url.start_with?('/') "\n[IMAGE: #{url}]\n" end end unwrap_links - Converts links to “text (url)” format, only showing the URL if it differs from the link text: def unwrap_links(input) site_url = @context.registers[:site].config['url'] input.gsub(/]+href=["']([^"']+)["'][^>]*>(.*?)<\/a>/im) do url = $1 text = $2.strip url = "#{site_url}#{url}" if url.start_with?('/') # Only show URL if different from text if text.downcase == url.downcase || text == url text else "#{text} (#{url})" end end end wrap_lines - Wraps text to 80 characters while preserving code blocks, lists, blockquotes, and long URLs. collapse_blank_lines - Removes excessive whitespace by collapsing three or more consecutive newlines down to two. The last piece was hooking up the metadata. The generator adds an alternates array to each post’s data, making it available to templates. def add_alternate(doc, txt_page) doc.data["alternates"] ||= [] doc.data["alternates"].reject! { |a| a["type"] == "text/plain" } doc.data["alternates"] << { "type" => "text/plain", "href" => txt_page.url, "title" => "plain text" } end The txt-format.html (https://github.com/omgmog/omgmog.github.com/blob/main/_includes/txt-format.html) template wraps the converted content with a header, footer, and a link back to the original HTML post. Results It works. This post is available as HTML (https://blog.omgmog.net/post/moving-to-github-actions-and-adding-txt-posts/) and .txt (https://blog.omgmog.net/post/moving-to-github-actions-and-adding-txt-posts.txt). The text version strips away HTML and wraps everything to 80 characters while keeping code blocks intact. The real win was moving to GitHub Actions. I’ve got a proper plugin system now - can write custom generators, filters, and tags without workarounds. The infrastructure’s there if I want to extend Jekyll further down the line. As for the .txt URLs, I can’t imagine many people will use them. The site’s already pretty light. But the option’s there. ================================================================================ Published January 09, 2026 Generated from the original post: https://blog.omgmog.net/post/moving-to-github-actions-and-adding-txt-posts/ Post syndicated to: - https://social.omgmog.net/2026/postmoving-to-github-actions-and-adding-txt-postsas-a-result-of-reading-blog202512a-small-collection-of-text-only-websites-by - https://indieweb.social/@omgmog/115865892816110097 Max Glenister is an interface designer and senior full-stack developer from Oxfordshire. He writes mostly about front-end development and technology. - Mastodon: https://indieweb.social/@omgmog - Github: https://github.com/omgmog - Reddit: https://reddit.com/u/omgmog - Discord: https://discordapp.com/users/omgmog#6206