<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>FoggyMtnDrifter</title><description>Michael Kinder&apos;s personal site.</description><link>https://www.foggymtndrifter.com</link><item><title>Rocky Linux: A User&apos;s Guide to Contributing</title><link>https://www.foggymtndrifter.com/posts/rocky-linux-contributing-guide</link><guid isPermaLink="true">https://www.foggymtndrifter.com/posts/rocky-linux-contributing-guide</guid><description>Learn how to go from open source enthusiast to active contributor within the welcoming Rocky Linux community.</description><pubDate>Sun, 14 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve always been passionate about open source software. The idea of collaborative communities building powerful tools freely available to everyone resonated deeply with me. Yet, for a long time, I was just a user, a beneficiary. I&apos;d never taken the plunge into actually &lt;em&gt;contributing&lt;/em&gt; to an open source project.&lt;/p&gt;
&lt;p&gt;That all changed when the CentOS landscape shifted. Like many, I was caught off guard by the news around CentOS 8. Reading Greg Kurtzer&apos;s announcement of Rocky Linux on the CentOS blog, something clicked. This was my chance to put my belief in open source into action.&lt;/p&gt;
&lt;p&gt;With a mix of excitement and apprehension, I joined the Rocky Linux Slack (which later transitioned to Mattermost). I had zero open source contribution experience, yet I found a welcoming community eager to bring enthusiastic people on board. I started by talking to the then Web Team Lead, and soon I was making small contributions to the Rocky Linux website.&lt;/p&gt;
&lt;p&gt;My involvement grew quickly. As my contributions increased, so did my sense of ownership in the project. Before I knew it, I was leading the Web Team, overseeing the development and maintenance of the main Rocky Linux site. It was incredibly rewarding!&lt;/p&gt;
&lt;p&gt;I&apos;ve since scaled back my involvement in that specific role, but remain integral in shaping the project&apos;s visual identity as the Design Team Lead. The thrill of helping a crucial open source project remains the same.&lt;/p&gt;
&lt;h2&gt;How You Can Make a Difference&lt;/h2&gt;
&lt;p&gt;My story isn&apos;t about being a technical genius. It&apos;s about the power of the Rocky Linux community, a community that throws its doors wide open and says, &quot;Come build with us!&quot; If you&apos;re intrigued by this project, here&apos;s your roadmap for getting involved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Join the Mattermost Chat:&lt;/strong&gt; The heart of the Rocky Linux community beats on &lt;a href=&quot;https://chat.rockylinux.org&quot;&gt;Mattermost&lt;/a&gt;. Dive right in!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explore Your Interests:&lt;/strong&gt; Find channels that align with your skills or areas you want to learn about. Maybe you like to write; documentation is always needed. Love to design? Join me on the Design Team. If you&apos;re a developer, well, the possibilities are endless.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Let Your Voice Be Heard:&lt;/strong&gt; Don&apos;t be shy! Introduce yourself within the channels, let people know you&apos;re interested in helping out. You&apos;ll be amazed at how quickly you&apos;ll be guided towards ways to contribute.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Open Doors, Open Community&lt;/h2&gt;
&lt;p&gt;The core of Rocky Linux is its inclusivity. Whether you&apos;re a seasoned expert, a student wanting to learn, or someone who just wants to give back to the open source world, there&apos;s a place for you.&lt;/p&gt;
&lt;p&gt;Don&apos;t think you have to become a team lead like I did to make a difference. Every pull request reviewed, every documentation page written, every design asset created; it all matters. We&apos;re not just building an operating system; we&apos;re building a vibrant community.&lt;/p&gt;
&lt;p&gt;What are you waiting for? Join us! You might be surprised by how much you can achieve and who you might become.&lt;/p&gt;
</content:encoded><author>Michael Kinder</author></item><item><title>Installing Virtual Machine Manager on Void Linux</title><link>https://www.foggymtndrifter.com/posts/virtual-machine-manager-void-linux</link><guid isPermaLink="true">https://www.foggymtndrifter.com/posts/virtual-machine-manager-void-linux</guid><description>This step-by-step tutorial shows you how to install and use Virtual Machine Manager on Void Linux.</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;re a fellow Void Linux enthusiast like me, you know the thrill of a lean, customizable system. But sometimes, you might want to run other operating systems within your streamlined Void environment. That&apos;s where virtual machines (VMs) come to the rescue, and Virtual Machine Manager makes it super easy to set them up.&lt;/p&gt;
&lt;p&gt;In this guide, I&apos;ll walk you through the steps I took to get this working.&lt;/p&gt;
&lt;h2&gt;Step 1: Installing the Essentials&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo xbps-install libvirt virt-manager qemu polkit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gets us all the pieces we need; &lt;code&gt;libvirt&lt;/code&gt; for virtualization magic, &lt;code&gt;virt-manager&lt;/code&gt; for a friendly interface, &lt;code&gt;qemu&lt;/code&gt; as our trusty emulator, and &lt;code&gt;polkit&lt;/code&gt; for handling permissions.&lt;/p&gt;
&lt;h2&gt;Step 2: Getting the Right Permissions&lt;/h2&gt;
&lt;p&gt;We need to make sure our regular user account can play with virtual machines. Let&apos;s add ourselves to the libvirt and kvm groups:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo usermod -a -G libvirt,kvm your_username
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Remember to replace &lt;code&gt;your_username&lt;/code&gt; with your actual username.)&lt;/p&gt;
&lt;h2&gt;Step 3: A Quick Log Out and Back In&lt;/h2&gt;
&lt;p&gt;Just to be sure the group changes stick, log out of your account and log back in.&lt;/p&gt;
&lt;h2&gt;Step 4: A Tiny Bit of Configuration&lt;/h2&gt;
&lt;p&gt;Let&apos;s setup a config file for &lt;code&gt;libvirt&lt;/code&gt; so it knows what&apos;s up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir ~/.config/libvirt &amp;amp;&amp;amp; sudo cp -rv /etc/libvirt/libvirt.conf ~/.config/libvirt/ &amp;amp;&amp;amp; sudo chown your_username:your_user_group ~/.config/libvirt/libvirt.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 5: Tweaking libvirt Settings&lt;/h2&gt;
&lt;p&gt;Open &lt;code&gt;~/.config/libvirt/libvirt.conf&lt;/code&gt; in your favorite text editor and find the line that says &lt;code&gt;uri_default&lt;/code&gt;. Change it to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uri_default = &quot;qemu:///system&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 6: QEMU Permissions&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/libvirt/qemu.conf&lt;/code&gt;, setting the user and group to match your username and libvirt respectively. This lets you manage the VMs you create.&lt;/p&gt;
&lt;h2&gt;Step 7: Starting the Services&lt;/h2&gt;
&lt;p&gt;Void Linux uses &lt;code&gt;runit&lt;/code&gt; for services. Let&apos;s enable the ones we need:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ln -s /etc/sv/dbus /var/service/
sudo ln -s /etc/sv/polkitd /var/service/
sudo ln -s /etc/sv/libvirtd /var/service/
sudo ln -s /etc/sv/virtlockd /var/service/
sudo ln -s /etc/sv/virtlogd /var/service/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 8: Launch Time!&lt;/h2&gt;
&lt;p&gt;That&apos;s it! Go ahead, launch Virtual Machine Manager, and get ready to spin up new virtual machines!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Bonus Tip: Pump Up the Graphics&lt;/h2&gt;
&lt;p&gt;Want smoother graphics in your VMs? Edit a VM&apos;s settings, go to &quot;Video&quot;, select &quot;Virtio&quot;, and check the &quot;3D Acceleration&quot; box.&lt;/p&gt;
</content:encoded><author>Michael Kinder</author></item><item><title>Integrating Gitea &amp; Vercel</title><link>https://www.foggymtndrifter.com/posts/integrating-gitea-and-vercel</link><guid isPermaLink="true">https://www.foggymtndrifter.com/posts/integrating-gitea-and-vercel</guid><description>I share my experience integrating the open source tool Gitea and the hosting platform Vercel for streamlined code management and deployment.</description><pubDate>Tue, 18 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m a strong supporter of open source software and find that the tools I choose strongly influence my development workflow. For managing my code, I use &lt;a href=&quot;https://gitea.io/&quot;&gt;Gitea&lt;/a&gt;. It&apos;s a fantastic, open source version control platform that&apos;s lightweight and offers the features and security I need. One of the aspects I love is &lt;a href=&quot;https://docs.gitea.io/en-us/actions/&quot;&gt;Gitea Actions&lt;/a&gt;, which makes it easy to streamline my deployment process to &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why Gitea?&lt;/h2&gt;
&lt;p&gt;Gitea excels as a self-hosted, open source version control platform. If you like having flexibility and control over your development setup, it&apos;s a compelling alternative to larger, corporate-owned code hosting solutions. With Gitea Actions (which are compatible with GitHub Actions), I can tap into the rich ecosystem of Actions from the broader development community without sacrificing the benefits of an independent, open source platform.&lt;/p&gt;
&lt;h2&gt;Streamlining Deployment with Vercel&lt;/h2&gt;
&lt;p&gt;Let&apos;s talk about how I use Gitea Actions for deployment. Here&apos;s my &lt;code&gt;preview.yaml&lt;/code&gt; workflow that enables streamlined feedback cycles. Whenever I open a pull request to the main branch, this workflow triggers a preview deployment on Vercel. This lets me test and gather feedback before merging changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Vercel Preview Deployment
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

on:
  pull_request:
    branches:
      - main

jobs:
  Deploy-Preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v4
        with:
          node-version: &quot;&amp;gt;=18.14.1&quot;
      - name: Install Vercel CLI
        run: npm install --global vercel@latest
      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build Project Artifacts
        run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy Project Artifacts to Vercel
        id: deploy
        run: |
          echo &quot;::group::Deploying&quot;
          DEPLOY_OUTPUT=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} 2&amp;gt;&amp;amp;1)
          echo &quot;$DEPLOY_OUTPUT&quot;
          echo &quot;::endgroup::&quot;
          PREVIEW_URL=$(echo &quot;$DEPLOY_OUTPUT&quot; | grep -o &apos;Preview: https://[1]*&apos; | awk &apos;{print $2}&apos;)
          echo &quot;PREVIEW_URL=$PREVIEW_URL&quot; &amp;gt;&amp;gt; $GITHUB_ENV
          echo &quot;::set-output name=preview_url::$PREVIEW_URL&quot;
          if [[ -z &quot;$PREVIEW_URL&quot; ]]; then exit 1; fi
        continue-on-error: false
      - name: Comment on PR on Success
        if: ${{ success() &amp;amp;&amp;amp; env.PREVIEW_URL }}
        uses: actions/github-script@v5
        with:
          script: |
            const previewUrl = &apos;${{ env.PREVIEW_URL }}&apos;;
            github.rest.issues.createComment({
              issue_number: context.payload.pull_request.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Preview deployment successful.\n\nView Preview: ${previewUrl}`
            });
      - name: Comment on PR on Error
        if: ${{ failure() }}
        uses: actions/github-script@v5
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.payload.pull_request.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: &apos;Deployment encountered an issue. Please refer to the workflow logs for more information.&apos;
            });
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Production Deployments&lt;/h2&gt;
&lt;p&gt;Once the preview is approved, my &lt;code&gt;production.yaml&lt;/code&gt; workflow deploys changes to my live site. It&apos;s very similar to the preview workflow, but tailored for the production environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Vercel Production Deployment
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
  push:
    branches:
      - main
jobs:
  Deploy-Production:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v4
        with:
          node-version: &quot;&amp;gt;=18.14.1&quot;
      - name: Install Vercel CLI
        run: npm install --global vercel@latest
      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy Project Artifacts to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Open Source in Practice&lt;/h2&gt;
&lt;p&gt;Those workflows show how I use open source tools to maintain a flexible and efficient approach to development. Gitea&apos;s adaptability and the seamless integration with GitHub Actions demonstrates the power of open source in creating custom workflows without sacrificing efficiency.&lt;/p&gt;
&lt;h2&gt;My Thoughts on This Approach&lt;/h2&gt;
&lt;p&gt;Using Gitea Actions has been a great experience for streamlining my deployment process. It highlights how open source development enables customization and the ability to tap into community resources. If you&apos;re seeking a flexible development environment that emphasizes open source principles, Gitea and Vercel make a powerful combination.&lt;/p&gt;
</content:encoded><author>Michael Kinder</author></item><item><title>Introducing ClearProxy</title><link>https://www.foggymtndrifter.com/posts/introducing-clearproxy</link><guid isPermaLink="true">https://www.foggymtndrifter.com/posts/introducing-clearproxy</guid><description>ClearProxy is a modern web-based management interface for Caddy Server that simplifies reverse proxy configuration through an intuitive UI while maintaining Caddy&apos;s core features like automatic HTTPS and security defaults.</description><pubDate>Thu, 22 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a long-time user of &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy Server&lt;/a&gt;, I&apos;ve always appreciated its simplicity, automatic HTTPS capabilities, and robust performance. However, one pain point kept nagging at me: maintaining Caddyfile configurations across multiple servers and projects. That&apos;s what led me to create ClearProxy, a modern web-based management interface for Caddy that focuses on making reverse proxy configuration as straightforward as possible.&lt;/p&gt;
&lt;h2&gt;The Journey&lt;/h2&gt;
&lt;p&gt;The idea for ClearProxy was born out of a simple desire: I wanted the power of Caddy without the hassle of manually editing configuration files. While Caddy&apos;s Caddyfile syntax is clean and intuitive, managing multiple proxy configurations across different environments can become tedious. I wanted a solution that would:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Provide a beautiful, intuitive interface for managing proxy hosts&lt;/li&gt;
&lt;li&gt;Maintain Caddy&apos;s core philosophy of simplicity and security&lt;/li&gt;
&lt;li&gt;Offer advanced features for power users without overwhelming beginners&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Technical Implementation&lt;/h2&gt;
&lt;p&gt;I built ClearProxy using modern web technologies that prioritize performance and developer experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SvelteKit&lt;/strong&gt; for the frontend, offering a responsive and snappy user interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt; for reliable data storage without the complexity of a separate database server&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt; for easy deployment and consistent environments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caddy&apos;s Admin API&lt;/strong&gt; for seamless integration with the Caddy server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The architecture is deliberately simple: two containers working in harmony - one running the ClearProxy application and another running Caddy server. This setup ensures that users get all the benefits of Caddy (automatic HTTPS, modern security defaults) while enjoying a user-friendly management interface.&lt;/p&gt;
&lt;h2&gt;Key Features&lt;/h2&gt;
&lt;p&gt;Some of the features I&apos;m most proud of include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Intuitive Proxy Management:&lt;/strong&gt; Add and configure proxy hosts with just a few clicks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic HTTPS:&lt;/strong&gt; Leveraging Caddy&apos;s built-in ACME client for SSL/TLS certificates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Authentication:&lt;/strong&gt; Easily secure proxied hosts when needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced Configuration:&lt;/strong&gt; Raw Caddyfile syntax support for power users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access Logging:&lt;/strong&gt; Built-in monitoring capabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Rewards&lt;/h2&gt;
&lt;p&gt;Building ClearProxy has been incredibly rewarding for a couple reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Learning Experiences:&lt;/strong&gt; The project pushed me to dive deep into Caddy&apos;s internals, modern web development practices, and container orchestration. Every challenge was an opportunity to learn something new.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Source Collaboration:&lt;/strong&gt; The project is open source, and I&apos;m hoping and looking forward to collaborating with other developers to make it better.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Looking Forward&lt;/h2&gt;
&lt;p&gt;ClearProxy is more than just a personal tool - it&apos;s becoming a project that makes Caddy more accesible to everyone. Future plans include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enhanced monitoring and analytics&lt;/li&gt;
&lt;li&gt;Support for more advanced Caddy features&lt;/li&gt;
&lt;li&gt;Improved documentation and tutorials&lt;/li&gt;
&lt;li&gt;Community-requested features and improvements&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Try It Yourself&lt;/h2&gt;
&lt;p&gt;If you&apos;re using Caddy and want to simplify your proxy management, give ClearProxy a try. The project is &lt;a href=&quot;https://github.com/FoggyMtnDrifter/ClearProxy&quot;&gt;available on GitHub&lt;/a&gt;, and getting started is as simple as running a few Docker commands.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir clearproxy &amp;amp;&amp;amp; cd clearproxy
curl -L https://raw.githubusercontent.com/foggymtndrifter/clearproxy/main/docker-compose.yml -o docker-compose.yml
docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Building ClearProxy has been a journey of solving a personal pain point that turned into something much bigger. It&apos;s a testament to the power of open source and the satisfaction that comes from creating tools that make developers&apos; lives easier. If you&apos;re using Caddy or looking for a more modern reverse proxy solution, I encourage you to give ClearProxy a try and join our community.&lt;/p&gt;
</content:encoded><author>Michael Kinder</author></item><item><title>Building a Custom Comment System with GitHub Discussions</title><link>https://www.foggymtndrifter.com/posts/building-custom-comment-system</link><guid isPermaLink="true">https://www.foggymtndrifter.com/posts/building-custom-comment-system</guid><description>How I replaced Giscus with a custom comment system powered by GitHub Discussions, complete with guest comments, OAuth, and spam protection.</description><pubDate>Thu, 29 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently decided to replace Giscus on my blog with a custom-built comment system. While Giscus is fantastic, I wanted more control over the user experience and the ability to support guest comments without requiring GitHub authentication. Here&apos;s how I built it.&lt;/p&gt;
&lt;h2&gt;The Goal&lt;/h2&gt;
&lt;p&gt;I wanted a comment system that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses GitHub Discussions as the backend (free, reliable, and I already use GitHub)&lt;/li&gt;
&lt;li&gt;Supports both authenticated GitHub users and anonymous guests&lt;/li&gt;
&lt;li&gt;Has a clean UI that matches my site&apos;s aesthetic&lt;/li&gt;
&lt;li&gt;Includes basic spam protection (honeypot + CAPTCHA)&lt;/li&gt;
&lt;li&gt;Sorts comments newest-first&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Architecture&lt;/h2&gt;
&lt;p&gt;The system has three main components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Backend API routes&lt;/strong&gt; (Astro SSR endpoints)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frontend component&lt;/strong&gt; (Svelte for reactivity)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OAuth flow&lt;/strong&gt; (GitHub authentication)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Backend: API Routes&lt;/h3&gt;
&lt;p&gt;I created several API routes in &lt;code&gt;src/pages/api/&lt;/code&gt;:&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;/api/comments/[slug].ts&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This is the workhorse. It handles both fetching and posting comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GET&lt;/strong&gt;: Fetches all comments for a post by searching GitHub Discussions for a discussion matching the post&apos;s pathname.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const searchQuery = `repo:${siteConfig.comments.repo} in:title ${term}`
const result = await octokit.graphql(query, { term: searchQuery })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key insight here: GitHub&apos;s search API is powerful. By searching for discussions with the post&apos;s pathname in the title, I can reliably find the right discussion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;POST&lt;/strong&gt;: Creates a new comment. This is where it gets interesting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the user is authenticated (has a GitHub token cookie), post as them&lt;/li&gt;
&lt;li&gt;If not, post as a bot account and append &lt;code&gt;_(Posted by DisplayName)_&lt;/code&gt; to the comment body&lt;/li&gt;
&lt;li&gt;The GET endpoint strips this attribution and uses it to display the guest&apos;s name&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;OAuth Routes&lt;/h4&gt;
&lt;p&gt;Three simple routes handle GitHub authentication:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/auth/signin&lt;/code&gt; - Redirects to GitHub OAuth&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth/callback&lt;/code&gt; - Exchanges code for token, stores in HTTP-only cookie&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth/signout&lt;/code&gt; - Clears the token cookie&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Frontend: Svelte Component&lt;/h3&gt;
&lt;p&gt;I chose Svelte for the comment UI because it&apos;s lightweight and has excellent reactivity. The component handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Displaying comments with proper nesting (replies)&lt;/li&gt;
&lt;li&gt;A collapsible comment form&lt;/li&gt;
&lt;li&gt;Switching between &quot;Post as Guest&quot; and &quot;Sign in with GitHub&quot; modes&lt;/li&gt;
&lt;li&gt;Client-side CAPTCHA validation for guests&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{#if authMode === &apos;guest&apos; &amp;amp;&amp;amp; !user}
  &amp;lt;div&amp;gt;
    &amp;lt;label for=&quot;captcha&quot;&amp;gt;Human Check: What is {num1} + {num2}?&amp;lt;/label&amp;gt;
    &amp;lt;input id=&quot;captcha&quot; type=&quot;number&quot; bind:value={captchaAnswer} required /&amp;gt;
  &amp;lt;/div&amp;gt;
{/if}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Guest Comments: The Attribution Trick&lt;/h2&gt;
&lt;p&gt;The clever part about guest comments is how they&apos;re stored. Since GitHub Discussions doesn&apos;t natively support &quot;guest&quot; authors, I:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Post the comment using a bot account&lt;/li&gt;
&lt;li&gt;Append the guest&apos;s display name in a specific format: &lt;code&gt;_(Posted by Jane Doe)_&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On fetch, parse this attribution and transform the comment object&lt;/li&gt;
&lt;li&gt;Strip the attribution from the HTML before displaying&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means the comment lives in GitHub Discussions, but appears to be from the guest user in my UI.&lt;/p&gt;
&lt;h2&gt;Spam Protection&lt;/h2&gt;
&lt;p&gt;I implemented two layers of protection:&lt;/p&gt;
&lt;h3&gt;Honeypot&lt;/h3&gt;
&lt;p&gt;A hidden field that bots might fill but humans won&apos;t:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; name=&quot;website_honey&quot; class=&quot;hidden&quot; bind:value={honeyPot} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If this field has a value, the submission is rejected.&lt;/p&gt;
&lt;h3&gt;Math CAPTCHA&lt;/h3&gt;
&lt;p&gt;For guest comments only, a simple math question:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (!userToken) {
  if (parseInt(num1) + parseInt(num2) !== parseInt(answer)) {
    return new Response(&apos;Incorrect math answer&apos;, { status: 400 })
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s basic, but effective against simple bots without annoying users with complex CAPTCHAs.&lt;/p&gt;
&lt;h2&gt;UI Polish&lt;/h2&gt;
&lt;p&gt;I spent time making the UI feel native to my site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generic avatars for guests (using DiceBear&apos;s initials API)&lt;/li&gt;
&lt;li&gt;Changed &quot;OWNER&quot; badge to &quot;ADMIN&quot;&lt;/li&gt;
&lt;li&gt;Newest-first sorting (reversed the GitHub API response)&lt;/li&gt;
&lt;li&gt;Collapsible form (using native &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element)&lt;/li&gt;
&lt;li&gt;Matched the styling to my &quot;Buy me a coffee&quot; modal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The system is configured in &lt;code&gt;site.config.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;comments: {
  repo: &apos;FoggyMtnDrifter/website&apos;,
  repoId: &apos;R_kgDORBPuRw&apos;,
  category: &apos;General&apos;,
  categoryId: &apos;DIC_kwDORBPuR84C1bYd&apos;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;ll also need environment variables:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GITHUB_CLIENT_ID=your_oauth_app_client_id
GITHUB_CLIENT_SECRET=your_oauth_app_secret
GITHUB_PERSONAL_ACCESS_TOKEN=your_bot_token
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitHub&apos;s GraphQL API is powerful&lt;/strong&gt; - The search query approach works better than trying to filter discussions directly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP-only cookies are your friend&lt;/strong&gt; - Storing OAuth tokens securely is critical&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple spam protection works&lt;/strong&gt; - You don&apos;t need reCAPTCHA for a personal blog&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native HTML is underrated&lt;/strong&gt; - Using &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; for the collapsible form meant zero JavaScript for that feature&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;I now have a comment system that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Costs nothing (uses GitHub&apos;s free tier)&lt;/li&gt;
&lt;li&gt;Supports both authenticated and guest users&lt;/li&gt;
&lt;li&gt;Matches my site&apos;s aesthetic perfectly&lt;/li&gt;
&lt;li&gt;Has basic spam protection&lt;/li&gt;
&lt;li&gt;Gives me full control over the UX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best part? All comments are stored in GitHub Discussions, so they&apos;re backed up, searchable, and I can manage them using GitHub&apos;s excellent moderation tools.&lt;/p&gt;
&lt;p&gt;If you&apos;re building an Astro site and want more control than Giscus provides, this approach is definitely worth considering. The code is all on my &lt;a href=&quot;https://github.com/FoggyMtnDrifter/website&quot;&gt;GitHub repo&lt;/a&gt; if you want to dig deeper.&lt;/p&gt;
</content:encoded><author>Michael Kinder</author></item></channel></rss>