<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Hugo on AlipourIm journeys</title>
    <link>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/hugo/</link>
    <description>Recent content in Hugo on AlipourIm journeys</description>
    <generator>Hugo -- 0.146.0</generator>
    <language>en</language>
    <lastBuildDate>Thu, 23 Oct 2025 19:31:12 +0200</lastBuildDate>
    <atom:link href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/hugo/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>New features for my blog! Adding Comments &#43; RSS to Hugo PaperMod (Giscus and Isso FTW)</title>
      <link>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/comments-rss-papermod/</link>
      <pubDate>Thu, 23 Oct 2025 19:31:12 +0200</pubDate>
      <guid>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/comments-rss-papermod/</guid>
      <description>A friendly, copy-pasteable guide to wiring up Giscus comments (GitHub Discussions) and Isso comments &#43; RSS in Hugo PaperMod.</description>
      <content:encoded><![CDATA[<blockquote>
<p>Short story: I wanted comments and a proper RSS feed on my Hugo + PaperMod site.<br>
Longer story: I spent 30 minutes poking around, and now you don’t have to. Here’s exactly what I did.</p></blockquote>
<hr>
<p>I&rsquo;ve been wanting to add comment section to my blog for a while.
There are a few problems.</p>
<ul>
<li>I don&rsquo;t want to get attacked by bots or random people for no reason.</li>
<li>I don&rsquo;t want to allow weird things to happen.</li>
<li>I don&rsquo;t want to have a database for it.</li>
</ul>
<p>So&hellip; I decided to take a path which is not necesserially the best.
It requires a third party to act on your behalf, but it is not as scary as it sounds.
According to Github:</p>
<blockquote>
<p><strong>What the app can do</strong>
The app’s rights are limited to the repo(s) where the site owner installed it and to the permissions it requested (basically: Discussions read/write + metadata). It can’t touch a commenter’s private repos or do things outside those scopes. GitHub’s docs also say a GitHub App can only do what both you and the app have permission to do.</p></blockquote>
<blockquote>
<p><strong>What “Act on your behalf” means</strong>
That scary wording is GitHub’s. The giscus maintainer explains it means “post comments under your username,” and shows that the app requests Discussions read/write + metadata when installed.</p></blockquote>
<blockquote>
<p><strong>Do commenters have alternatives?</strong>
Yes. Anyone can open the linked GitHub Discussion and comment directly on GitHub, bypassing the app’s OAuth popup. The README explicitly mentions this.</p></blockquote>
<blockquote>
<p><strong>Privacy &amp; storage</strong>
No tracking/ads; comments live in GitHub Discussions. giscus is open-source and has a published privacy policy. You can also revoke the app any time in your GitHub settings.</p></blockquote>
<p>With the notice out of the way let&rsquo;s dive right in!</p>
<hr>
<h2 id="why-i-chose-giscus-instead-of-disqus">Why I chose Giscus (instead of Disqus)</h2>
<ul>
<li><strong>No ads or trackers</strong> (my page weight thanks me).</li>
<li><strong>Uses GitHub Discussions</strong> (portable, transparent, easy to search).</li>
<li><strong>Readers log in with GitHub</strong> (no mystery auth popups).</li>
<li>In theory you can directly go to the repo&rsquo;s discussion section and comment there!</li>
</ul>
<hr>
<h2 id="what-you-need-for-your-site-not-per-user">What you need (for your site, not per user)</h2>
<ul>
<li>A Hugo site using <strong>PaperMod</strong> ✅</li>
<li>A GitHub account ✅</li>
<li>A <strong>public</strong> repository for the discussion threads (I called mine <code>blog-comments</code>) with <strong>Discussions</strong> enabled ✅</li>
<li>Giscus app installed on that repo ✅</li>
</ul>
<p>If you like to keep your main site repo clean, use a separate one, e.g. <code>github-username/blog-comments</code>.</p>
<hr>
<h2 id="1-turn-on-comments-with-giscus">1) Turn on comments with Giscus</h2>
<p>PaperMod renders a <code>comments.html</code> partial when <code>params.comments = true</code>.<br>
So we enable it in config and create the partial.</p>
<h3 id="11-enable-comments-globally">1.1 Enable comments globally</h3>
<p><code>hugo.toml</code> (snippet)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">comments</span> = <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>You can still opt out per post with <code>comments = false</code> in front matter.</p>
<h3 id="12-create-the-comments-partial">1.2 Create the comments partial</h3>
<p>Create this file <strong>in your site</strong> (not inside the theme):</p>
<pre tabindex="0"><code>layouts/partials/comments.html
</code></pre><p>Paste the Giscus embed (swap the placeholders with yours from giscus.app):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">section</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;comments&#34;</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;giscus&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">script</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://giscus.app/client.js&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-repo</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;yourname/blog-comments&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-repo-id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;YOUR_REPO_ID&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-category</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Comments&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-category-id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;YOUR_CATEGORY_ID&#34;</span> <span style="color:#960050;background-color:#1e0010">&lt;!</span><span style="color:#a6e22e">--</span> <span style="color:#a6e22e">Get</span> <span style="color:#a6e22e">them</span> <span style="color:#a6e22e">from</span> <span style="color:#a6e22e">Giscus</span><span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#a6e22e">s</span> <span style="color:#a6e22e">page</span><span style="color:#960050;background-color:#1e0010">!</span> <span style="color:#a6e22e">--</span>&gt;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">mapping</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;pathname&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">strict</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">reactions</span><span style="color:#f92672">-</span><span style="color:#a6e22e">enabled</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;1&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">emit</span><span style="color:#f92672">-</span><span style="color:#a6e22e">metadata</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">input</span><span style="color:#f92672">-</span><span style="color:#a6e22e">position</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;bottom&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">theme</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;preferred_color_scheme&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;en&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">loading</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;lazy&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">crossorigin</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;anonymous&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">noscript</span>&gt;Enable JavaScript to view comments powered by Giscus.&lt;/<span style="color:#f92672">noscript</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">section</span>&gt;
</span></span></code></pre></div><p><strong>Notes</strong></p>
<ul>
<li>You’ll get <code>repo-id</code> and <code>category-id</code> from <strong>giscus.app</strong> after picking your repo + category.</li>
<li><code>data-mapping=&quot;pathname&quot;</code> works great for static sites.</li>
<li>Prefer a <strong>public</strong> repo so visitors can read discussions without friction.</li>
<li>Want a different language? Change <code>data-lang</code> (e.g., <code>de</code>, <code>fr</code>).</li>
</ul>
<hr>
<h2 id="2-enable-rss-home--sections">2) Enable RSS (home + sections)</h2>
<p>Hugo already knows how to generate feeds; we just tell it where.</p>
<p><code>hugo.toml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;https://example.com/&#34;</span>  <span style="color:#75715e"># set your production URL</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">outputs</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">home</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>, <span style="color:#e6db74">&#34;JSON&#34;</span>]     <span style="color:#75715e"># JSON is optional (e.g., for on-site search)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">section</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">taxonomy</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">term</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">services</span>.<span style="color:#a6e22e">rss</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">limit</span> = <span style="color:#ae81ff">-1</span>   <span style="color:#75715e"># -1 = no cap; or set e.g. 20</span>
</span></span></code></pre></div><p><strong>Feed URLs</strong></p>
<ul>
<li>Home feed: <code>/index.xml</code> → <code>https://example.com/index.xml</code></li>
<li>Section feeds: <code>/posts/index.xml</code>, <code>/notes/index.xml</code>, etc. (folders are lowercase)</li>
</ul>
<blockquote>
<p>If you’re using a <code>.onion</code> address or staging domain, just set that in <code>baseURL</code>.<br>
Locally, Hugo serves feeds at <code>http://localhost:1313/index.xml</code>.</p></blockquote>
<hr>
<h2 id="3-show-an-rss-button-on-the-home-page">3) Show an RSS button on the home page</h2>
<p>PaperMod shows a nice RSS icon on <strong>section/taxonomy</strong> lists by default (when enabled), but not on the home page. Two options:</p>
<h3 id="option-a--use-the-home-social-icons-row">Option A — Use the home “social icons” row</h3>
<p>This appears if you enable <strong>Home-Info</strong> or <strong>Profile</strong> mode:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">homeInfoParams</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Title</span> = <span style="color:#e6db74">&#34;Hello, internet!&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Content</span> = <span style="color:#e6db74">&#34;Notes, experiments, and occasional rabbit holes.&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">socialIcons</span>]]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">&#34;rss&#34;</span>        <span style="color:#75715e"># lowercase matters</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">url</span>  = <span style="color:#e6db74">&#34;/index.xml&#34;</span>
</span></span></code></pre></div><h3 id="option-b--add-an-icon-in-the-footer-theme-safe">Option B — Add an icon in the footer (theme-safe)</h3>
<p>Create:</p>
<pre tabindex="0"><code>layouts/partials/extend_footer.html
</code></pre><p>Paste:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/index.xml&#34;</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;alternate&#34;</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;application/rss+xml&#34;</span> <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;RSS&#34;</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rss-link&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  {{ partial &#34;svg.html&#34; (dict &#34;name&#34; &#34;rss&#34;) }}
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span>&gt;RSS&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">a</span>&gt;
</span></span></code></pre></div><p>Make it a sensible size:</p>
<p>Create:</p>
<pre tabindex="0"><code>assets/css/extended/rss.css
</code></pre><p>Paste:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* Small &amp; aligned RSS icon in the footer */</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">rss-link</span> { <span style="color:#66d9ef">display</span>: <span style="color:#66d9ef">inline-flex</span>; <span style="color:#66d9ef">align-items</span>: <span style="color:#66d9ef">center</span>; <span style="color:#66d9ef">gap</span>: <span style="color:#ae81ff">.35</span><span style="color:#66d9ef">rem</span>; }
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">rss-link</span> <span style="color:#f92672">svg</span> { <span style="color:#66d9ef">margin-left</span>: <span style="color:#ae81ff">10</span><span style="color:#66d9ef">px</span>; <span style="color:#66d9ef">width</span>: <span style="color:#ae81ff">18</span><span style="color:#66d9ef">px</span>; <span style="color:#66d9ef">height</span>: <span style="color:#ae81ff">18</span><span style="color:#66d9ef">px</span>; } <span style="color:#75715e">/* a bit of left margin for good measure!*/</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">rss-link</span> <span style="color:#f92672">span</span> { <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">.95</span><span style="color:#66d9ef">rem</span>; }
</span></span></code></pre></div><p>PaperMod auto-bundles anything under <code>assets/css/extended/</code>, so no extra imports needed.</p>
<hr>
<h2 id="4-verify-everything">4) Verify everything</h2>
<p>Build or run dev:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>hugo           <span style="color:#75715e"># outputs to ./public</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or</span>
</span></span><span style="display:flex;"><span>hugo server    <span style="color:#75715e"># serves at http://localhost:1313</span>
</span></span></code></pre></div><p>Check feeds:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Home feed:</span>
</span></span><span style="display:flex;"><span>curl -s http://localhost:1313/index.xml | head
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># A section feed (adjust path to your section name):</span>
</span></span><span style="display:flex;"><span>curl -s http://localhost:1313/posts/index.xml | head
</span></span></code></pre></div><p>Check comments:</p>
<ol>
<li>Open any post locally.</li>
<li>Scroll to the bottom — Giscus should appear.</li>
<li>Try a reaction or comment (you’ll be prompted to sign in with GitHub).</li>
</ol>
<hr>
<h2 id="5-per-post-controls-handy-later">5) Per-post controls (handy later)</h2>
<p>Turn comments off for a single post:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#75715e"># in that post’s front matter</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">comments</span> = <span style="color:#66d9ef">false</span>
</span></span></code></pre></div><p>Full content in RSS (global):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ShowFullTextinRSS</span> = <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>Show RSS buttons on section/taxonomy lists:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ShowRssButtonInSectionTermList</span> = <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><hr>
<h2 id="6-troubleshooting-aka-what-i-bumped-into">6) Troubleshooting (aka “what I bumped into”)</h2>
<ul>
<li>
<p><strong>Home page has no RSS icon</strong><br>
That’s expected; either enable Home-Info/Profile mode + <code>socialIcons</code>, or use the footer partial.</p>
</li>
<li>
<p><strong>Section feed 404</strong><br>
Folder names are lowercase (<code>content/posts/</code>, not <code>content/Posts/</code>).<br>
The URL mirrors that: <code>/posts/index.xml</code>.</p>
</li>
<li>
<p><strong>Giscus doesn’t create a discussion</strong><br>
Double-check the four attributes: <code>data-repo</code>, <code>data-repo-id</code>, <code>data-category</code>, <code>data-category-id</code>.<br>
Make sure Discussions is enabled and the Giscus app is installed for the repo.</p>
</li>
<li>
<p><strong>Icon is comically large</strong><br>
Keep the CSS above; 16–20px usually looks right.</p>
</li>
</ul>
<hr>
<h2 id="7-bonus-fast-setup-via-github-cli-optional-i-didnt-use-it-but-i-shouldve-perhaps">7) Bonus: fast setup via GitHub CLI (optional, I didn&rsquo;t use it, but I should&rsquo;ve perhaps)</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Create the public comments repo (adjust names)</span>
</span></span><span style="display:flex;"><span>gh repo create yourname/blog-comments --public -d <span style="color:#e6db74">&#34;Blog comment threads&#34;</span> --confirm
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Enable Discussions for it</span>
</span></span><span style="display:flex;"><span>gh repo edit yourname/blog-comments --enable-discussions
</span></span></code></pre></div><p>Then visit <strong>giscus.app</strong>, select the repo + category (e.g. “Comments”), and copy the generated IDs into <code>layouts/partials/comments.html</code>.</p>
<hr>
<h3 id="final-config-snapshot-condensed">Final config snapshot (condensed)</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;https://example.com/&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">theme</span> = <span style="color:#e6db74">&#34;PaperMod&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">outputs</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">home</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>, <span style="color:#e6db74">&#34;JSON&#34;</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">section</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">taxonomy</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">term</span> = [<span style="color:#e6db74">&#34;HTML&#34;</span>, <span style="color:#e6db74">&#34;RSS&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">services</span>.<span style="color:#a6e22e">rss</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">limit</span> = <span style="color:#ae81ff">-1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">comments</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ShowRssButtonInSectionTermList</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ShowFullTextinRSS</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Optional if using Home-Info/Profile mode:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># [[params.socialIcons]]</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#   name = &#34;rss&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#   url  = &#34;/index.xml&#34;</span>
</span></span></code></pre></div><hr>
<h1 id="appendix--why-i-added-isso-how-i-wired-it-and-what-i-fixed">Appendix — Why I added Isso, how I wired it, and what I fixed</h1>
<h2 id="the-story-why-isso--why-i-still-keep-giscus">The story (why Isso + why I still keep Giscus)</h2>
<p>I wanted comments that:</p>
<ul>
<li>allow <strong>anonymous</strong> replies (no GitHub account required),</li>
<li>are <strong>lightweight</strong> and <strong>self-hosted</strong>, and</li>
<li>can work on my <strong>Tor <code>.onion</code> mirror</strong>.</li>
</ul>
<p><strong>Isso</strong> checks those boxes: it’s a tiny Python app with SQLite storage, no trackers, and a simple embed. At the same time, I like the developer-friendly workflow of <strong>Giscus</strong> (comments are GitHub Discussions), so I kept both and added a little switcher UI: <strong>“Anonymous (Isso)”</strong> or <strong>“GitHub (Giscus)”</strong>.</p>
<p>Result: readers can pick privacy/anon (Isso) or identity/notifications (Giscus).</p>
<h2 id="how-i-run-it-high-level">How I run it (high-level)</h2>
<ul>
<li><strong>Site:</strong> Hugo + PaperMod</li>
<li><strong>Isso:</strong> listening on <code>127.0.0.1:8080</code>, stored locally (SQLite)</li>
<li><strong>Nginx:</strong> reverse-proxies <strong><code>/isso/</code></strong> to Isso on both the clearnet and the <code>.onion</code> vhost</li>
<li><strong>Hugo partial:</strong> renders the two panels (Isso &amp; Giscus) and a switcher; Giscus is <strong>lazy-loaded</strong> and <strong>auto-rethemed</strong> with the site theme</li>
</ul>
<blockquote>
<p>I’m deliberately keeping Isso’s own config tiny in this appendix; the important part for my setup is the <strong>Nginx proxying</strong> and the <strong>Hugo partial</strong>.</p></blockquote>
<h2 id="hugo-partial-switcher--embeds">Hugo partial: switcher + embeds</h2>
<p>Save as <code>layouts/partials/comments.html</code> and include it in your single layout (e.g. <code>layouts/_default/single.html</code>) with <code>{{ partial &quot;comments.html&quot; . }}</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- comments.html --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;comments-switch&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:flex;gap:.5rem;margin:.5rem 0 1rem;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;btn-isso&#34;</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;button&#34;</span> <span style="color:#a6e22e">aria-pressed</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>&gt;Anonymous (Isso)&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;btn-giscus&#34;</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;button&#34;</span> <span style="color:#a6e22e">aria-pressed</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;false&#34;</span>&gt;GitHub (Giscus)&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://github.com/AlipourIm/blog-comments/discussions/categories/comments&#34;</span> <span style="color:#a6e22e">target</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;_blank&#34;</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;noopener&#34;</span>&gt;Open on GitHub ↗&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- Isso panel (default) --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;panel-isso&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">section</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;isso-thread&#34;</span>&gt;&lt;/<span style="color:#f92672">section</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">script</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/isso/js/embed.min.js&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-isso</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/isso&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-isso-css</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-isso-lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;en&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-isso-max-comments-nested</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;5&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data-isso-sorting</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;newest&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">async</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">small</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;isso-powered&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:block;margin:.5rem 0;color:var(--secondary,#888)&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    Comments powered by &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://isso-comments.de&#34;</span> <span style="color:#a6e22e">target</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;_blank&#34;</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;noopener&#34;</span>&gt;Isso&lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">small</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- Giscus panel (lazy-loaded on click) --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;panel-giscus&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:none&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">section</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;giscus-thread&#34;</span>&gt;&lt;/<span style="color:#f92672">section</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>(<span style="color:#66d9ef">function</span> () {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">panelIsso</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;panel-isso&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">panelGisc</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;panel-giscus&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">issoBtn</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;btn-isso&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">giscBtn</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;btn-giscus&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">gLoaded</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">isDark</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 1) explicit PaperMod preference
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">pref</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">localStorage</span>.<span style="color:#a6e22e">getItem</span>(<span style="color:#e6db74">&#39;pref-theme&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pref</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;dark&#39;</span>)  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pref</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;light&#39;</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 2) page state (classes / data-theme)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">H</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">documentElement</span>, <span style="color:#a6e22e">B</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">body</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">hasDark</span> <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">H</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">&#39;dark&#39;</span>) <span style="color:#f92672">||</span> <span style="color:#a6e22e">B</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">&#39;dark&#39;</span>) <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">H</span>.<span style="color:#a6e22e">dataset</span>.<span style="color:#a6e22e">theme</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;dark&#39;</span>    <span style="color:#f92672">||</span> <span style="color:#a6e22e">B</span>.<span style="color:#a6e22e">dataset</span>.<span style="color:#a6e22e">theme</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;dark&#39;</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">H</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">&#39;theme-dark&#39;</span>) <span style="color:#f92672">||</span> <span style="color:#a6e22e">B</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">&#39;theme-dark&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">hasDark</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 3) OS preference
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">return</span> window.<span style="color:#a6e22e">matchMedia</span> <span style="color:#f92672">&amp;&amp;</span> window.<span style="color:#a6e22e">matchMedia</span>(<span style="color:#e6db74">&#39;(prefers-color-scheme: dark)&#39;</span>).<span style="color:#a6e22e">matches</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">gTheme</span> <span style="color:#f92672">=</span> () =&gt; (<span style="color:#a6e22e">isDark</span>() <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;dark_dimmed&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;noborder_light&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">loadGiscus</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">gLoaded</span>) <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#39;script&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">src</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://giscus.app/client.js&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#66d9ef">async</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">crossOrigin</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;anonymous&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-repo&#39;</span>,<span style="color:#e6db74">&#39;AlipourIm/blog-comments&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-repo-id&#39;</span>,<span style="color:#e6db74">&#39;R_kgDOQGARyA&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-category&#39;</span>,<span style="color:#e6db74">&#39;Comments&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-category-id&#39;</span>,<span style="color:#e6db74">&#39;DIC_kwDOQGARyM4Cw3x-&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-mapping&#39;</span>,<span style="color:#e6db74">&#39;pathname&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-strict&#39;</span>,<span style="color:#e6db74">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-reactions-enabled&#39;</span>,<span style="color:#e6db74">&#39;1&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-emit-metadata&#39;</span>,<span style="color:#e6db74">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-input-position&#39;</span>,<span style="color:#e6db74">&#39;bottom&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-lang&#39;</span>,<span style="color:#e6db74">&#39;en&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;data-theme&#39;</span>, <span style="color:#a6e22e">gTheme</span>());  <span style="color:#75715e">// initial theme
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;giscus-thread&#39;</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">s</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Retheme once iframe exists
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">reTheme</span> <span style="color:#f92672">=</span> () =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">iframe</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;iframe.giscus-frame&#39;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">iframe</span>) <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">iframe</span>.<span style="color:#a6e22e">contentWindow</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">postMessage</span>(
</span></span><span style="display:flex;"><span>        { <span style="color:#a6e22e">giscus</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">setConfig</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">theme</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">gTheme</span>() } } },
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;https://giscus.app&#39;</span>
</span></span><span style="display:flex;"><span>      );
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// PaperMod toggle button
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;theme-toggle&#39;</span>)<span style="color:#f92672">?</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;click&#39;</span>, () =&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">reTheme</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Also react to attribute changes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">mo</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MutationObserver</span>(<span style="color:#a6e22e">reTheme</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mo</span>.<span style="color:#a6e22e">observe</span>(document.<span style="color:#a6e22e">documentElement</span>, { <span style="color:#a6e22e">attributes</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">attributeFilter</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;class&#39;</span>,<span style="color:#e6db74">&#39;data-theme&#39;</span>] });
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mo</span>.<span style="color:#a6e22e">observe</span>(document.<span style="color:#a6e22e">body</span>,             { <span style="color:#a6e22e">attributes</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">attributeFilter</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;class&#39;</span>,<span style="color:#e6db74">&#39;data-theme&#39;</span>] });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">gLoaded</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">show</span>(<span style="color:#a6e22e">which</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">showIsso</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">which</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;isso&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">panelIsso</span>.<span style="color:#a6e22e">style</span>.<span style="color:#a6e22e">display</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">showIsso</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;block&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;none&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">panelGisc</span>.<span style="color:#a6e22e">style</span>.<span style="color:#a6e22e">display</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">showIsso</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;none&#39;</span>  <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;block&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">issoBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;aria-pressed&#39;</span>, <span style="color:#a6e22e">showIsso</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">giscBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#39;aria-pressed&#39;</span>, <span style="color:#f92672">!</span><span style="color:#a6e22e">showIsso</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">showIsso</span>) <span style="color:#a6e22e">loadGiscus</span>();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">issoBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;click&#39;</span>, <span style="color:#a6e22e">e</span> =&gt; { <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">preventDefault</span>(); <span style="color:#a6e22e">show</span>(<span style="color:#e6db74">&#39;isso&#39;</span>); });
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">giscBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;click&#39;</span>, <span style="color:#a6e22e">e</span> =&gt; { <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">preventDefault</span>(); <span style="color:#a6e22e">show</span>(<span style="color:#e6db74">&#39;giscus&#39;</span>); });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// default
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">show</span>(<span style="color:#e6db74">&#39;isso&#39;</span>);
</span></span><span style="display:flex;"><span>})();
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">script</span>&gt;
</span></span></code></pre></div><h2 id="nginx-proxy-isso-on-both-sites-and-avoid-the-404-onion-trap">Nginx: proxy Isso on <strong>both</strong> sites (and avoid the 404-onion trap)</h2>
<p>The bug I hit: my <code>.onion</code> server had a regex static block that <strong>won over</strong> the plain <code>/isso/</code> location, so <code>/isso/js/embed.min.js</code> returned <strong>404</strong>.</p>
<p><strong>Fix:</strong> make <code>/isso/</code> a <code>^~</code> prefix location so it beats regex, and place it <strong>above</strong> the static block.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># /etc/nginx/sites-enabled/onion-blog  (same idea for the clearnet vhost)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> 127.0.0.1:<span style="color:#ae81ff">3301</span> <span style="color:#e6db74">default_server</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion</span> 127.0.0.1 <span style="color:#e6db74">localhost</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Referrer-Policy</span> <span style="color:#e6db74">no-referrer</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">add_header</span> <span style="color:#e6db74">X-Content-Type-Options</span> <span style="color:#e6db74">nosniff</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">add_header</span> <span style="color:#e6db74">X-Frame-Options</span> <span style="color:#e6db74">SAMEORIGIN</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">root</span> <span style="color:#e6db74">/srv/hugo/mysite/public-onion</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">index</span> <span style="color:#e6db74">index.html</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> { <span style="color:#f92672">try_files</span> $uri $uri/ <span style="color:#e6db74">/index.html</span>; }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># /isso -&gt; /isso/
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">location</span> = <span style="color:#e6db74">/isso</span> { <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">/isso/</span>; }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># IMPORTANT: this must beat regex locations below
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">location</span> <span style="color:#e6db74">^~</span> <span style="color:#e6db74">/isso/</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8080/</span>;    <span style="color:#75715e"># Isso container/port
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#f92672">proxy_http_version</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">.1</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> $host;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Host</span> $host;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> $scheme;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span> $proxy_add_x_forwarded_for;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_redirect</span> <span style="color:#66d9ef">off</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_read_timeout</span> <span style="color:#e6db74">60s</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># static
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">location</span> ~<span style="color:#e6db74">*</span> <span style="color:#e6db74">\\.(css|js|ico|png|jpg|jpeg|gif|svg|webp|txt|xml)</span>$ {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">access_log</span> <span style="color:#66d9ef">off</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Cache-Control</span> <span style="color:#e6db74">&#34;public,</span> <span style="color:#e6db74">max-age=31536000,</span> <span style="color:#e6db74">immutable&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">try_files</span> $uri =<span style="color:#ae81ff">404</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Reload and smoke test:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo nginx -t <span style="color:#f92672">&amp;&amp;</span> sudo systemctl reload nginx
</span></span><span style="display:flex;"><span>curl -I http://&lt;your-onion-host&gt;/isso/js/embed.min.js   <span style="color:#75715e"># expect 200</span>
</span></span></code></pre></div><blockquote>
<p>If you ever point <code>data-isso</code> to a <strong>different origin</strong>, add CORS headers on that vhost. For same-origin (<code>data-isso=&quot;/isso&quot;</code>), you don’t need CORS.</p></blockquote>
<h2 id="small-css-tweaks-one-color-across-lightdark--spacing">Small CSS tweaks (one color across light/dark + spacing)</h2>
<p>I wanted the same blue for <strong>reply / edit / delete</strong> links and for <strong>Submit / Preview</strong> buttons, in both themes, and a little spacing:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* put this in your site CSS */</span>
</span></span><span style="display:flex;"><span>:<span style="color:#a6e22e">root</span> { --btn-blue: <span style="color:#ae81ff">#2563eb</span>; } <span style="color:#75715e">/* adjust to taste */</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* Isso form buttons */</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-post-action</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span><span style="color:#f92672">],</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-post-action</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;preview&#34;</span><span style="color:#f92672">]</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">background</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>btn<span style="color:#f92672">-</span><span style="color:#66d9ef">blue</span>) <span style="color:#75715e">!important</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">color</span>: <span style="color:#ae81ff">#fff</span> <span style="color:#75715e">!important</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">border</span>: <span style="color:#66d9ef">none</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">padding</span>: <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">.75</span><span style="color:#66d9ef">rem</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">border-radius</span>: <span style="color:#ae81ff">.4</span><span style="color:#66d9ef">rem</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">cursor</span>: <span style="color:#66d9ef">pointer</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* link actions under comments */</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-comment-footer</span> <span style="color:#f92672">a</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">color</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>btn<span style="color:#f92672">-</span><span style="color:#66d9ef">blue</span>) <span style="color:#75715e">!important</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">margin-right</span>: <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span>;       <span style="color:#75715e">/* gives &#34;edit&#34; and &#34;delete&#34; some breathing room */</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">text-decoration</span>: <span style="color:#66d9ef">none</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-comment-footer</span> <span style="color:#f92672">a</span>:<span style="color:#a6e22e">hover</span> { <span style="color:#66d9ef">text-decoration</span>: <span style="color:#66d9ef">underline</span>; }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* keep name/email/website stacked, slightly larger */</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> .<span style="color:#a6e22e">isso-input-wrapper</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">display</span>: <span style="color:#66d9ef">block</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">margin</span>: <span style="color:#ae81ff">.35</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">label</span> { <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">.95</span><span style="color:#66d9ef">rem</span>; }
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">],</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;email&#34;</span><span style="color:#f92672">],</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;url&#34;</span><span style="color:#f92672">]</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">width</span>: <span style="color:#ae81ff">100</span><span style="color:#66d9ef">%</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">1</span><span style="color:#66d9ef">rem</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">padding</span>: <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">.6</span><span style="color:#66d9ef">rem</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="what-changed-the-fixes-i-actually-made">What changed (the fixes I actually made)</h2>
<ol>
<li>
<p><strong>Theme correctness for Giscus.</strong><br>
PaperMod doesn’t add <code>.light</code>—it only toggles <code>.dark</code> and stores <code>pref-theme</code>. I now:</p>
<ul>
<li>read <code>localStorage.pref-theme</code> first,</li>
<li>fall back to class/data-theme, then</li>
<li>fall back to OS preference.<br>
I also re-theme the Giscus iframe on every toggle via <code>postMessage</code>.</li>
</ul>
</li>
<li>
<p><strong>Isso over Tor.</strong><br>
On the <code>.onion</code> vhost, I moved the proxy to a <code>^~ /isso/</code> block so it beats the static regex. That fixed the <code>404</code> on <code>/isso/js/embed.min.js</code>.</p>
</li>
<li>
<p><strong>UI polish.</strong></p>
<ul>
<li>Unified button/link color across themes,</li>
<li>ensured “edit”/“delete” aren’t touching,</li>
<li>kept the author/email/website inputs <strong>stacked</strong>, and</li>
<li>left <strong>Submit</strong>/<strong>Preview</strong> styled and grouped.</li>
</ul>
</li>
</ol>
<h2 id="sanity-check">Sanity check</h2>
<ul>
<li>Toggle the theme: Giscus switches between <code>noborder_light</code> and <code>dark_dimmed</code> without reload.</li>
<li>Open DevTools → Network on onion: <code>/isso/js/embed.min.js</code> loads with <strong>200</strong>.</li>
<li>Switcher buttons swap panels instantly; Giscus only loads when clicked.</li>
</ul>
<h2 id="8-final-notes">8) Final notes!</h2>
<p>I could not fully fix the style of Isso.
CSS is not easy&hellip; drat!</p>
<p>I don&rsquo;t think anyone actually reads the blog, let alone comment on it.
But it felt cool to have a comment section and also RSS for it.
I had a lot of not so fun debugging experience, and will probably work on the styling to make it look right, but for now I&rsquo;m happy with it! ^^</p>
<h1 id="admin-note--delete-all-isso-comments-for-a-single-post">Admin note — Delete all <strong>Isso</strong> comments for a single post</h1>
<p>This note is for future me: how to remove every comment tied to one post in <strong>Isso</strong>.<br>
Tested with SQLite-backed Isso (default). Works whether you serve via clearnet or <code>.onion</code> — because this operates directly on the database.</p>
<hr>
<h2 id="tldr-host-with-sqlite">TL;DR (host with SQLite)</h2>
<blockquote>
<p>Replace the placeholders for <code>DB</code> and <code>POST_URI</code> first.</p></blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 0) Variables — edit these two lines for your setup</span>
</span></span><span style="display:flex;"><span>DB<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/var/lib/isso/comments.db&#34;</span>          <span style="color:#75715e"># path to your Isso SQLite DB (check isso.conf: [general] dbpath)</span>
</span></span><span style="display:flex;"><span>POST_URI<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/posts/skin_routine/&#34;</span>         <span style="color:#75715e"># the post&#39;s URI as stored by Isso (often just the path)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1) Backup (always do this!)</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;.backup &#39;</span><span style="color:#66d9ef">$(</span>dirname <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">/comments.</span><span style="color:#66d9ef">$(</span>date +%F-%H%M%S<span style="color:#66d9ef">)</span><span style="color:#e6db74">.backup.sqlite3&#39;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2) Inspect: find the thread row and preview the comments</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">.headers on
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">.mode column
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">SELECT id, uri FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">SELECT id, tid, created, author, text
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">FROM comments
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">WHERE tid IN (SELECT id FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">ORDER BY created DESC
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">LIMIT 25;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3) Delete all comments for that post (thread)</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">DELETE FROM comments
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">WHERE tid IN (SELECT id FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;);
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 4) (Optional) Remove the now-empty thread row too</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">DELETE FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">VACUUM;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 5) Done. Isso usually picks this up automatically.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#    If you&#39;re containerized and want to be safe:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># docker restart &lt;isso_container_name&gt;</span>
</span></span></code></pre></div><hr>
<h2 id="if-running-isso-in-docker">If running Isso in Docker</h2>
<p>Open a shell in the container, then run the same steps:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -it &lt;isso_container_name&gt; /bin/sh
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Inside the container:</span>
</span></span><span style="display:flex;"><span>DB<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/db/comments.db&#34;</span>    <span style="color:#75715e"># or /data/comments.db, check your image/volume mapping</span>
</span></span><span style="display:flex;"><span>POST_URI<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/posts/skin_routine/&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;.backup &#39;/db/comments.</span><span style="color:#66d9ef">$(</span>date +%F-%H%M%S<span style="color:#66d9ef">)</span><span style="color:#e6db74">.backup.sqlite3&#39;&#34;</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;SELECT id, uri FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;;&#34;</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;DELETE FROM comments WHERE tid IN (SELECT id FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;);&#34;</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;DELETE FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;; VACUUM;&#34;</span>
</span></span><span style="display:flex;"><span>exit
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Optionally restart the container:</span>
</span></span><span style="display:flex;"><span>docker restart &lt;isso_container_name&gt;
</span></span></code></pre></div><hr>
<h2 id="notes--gotchas">Notes &amp; gotchas</h2>
<ul>
<li><strong>Find the real DB path</strong>: In <code>isso.conf</code> (often <code>/etc/isso/isso.conf</code> or within your container at <code>/config/isso.conf</code>), look for:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[general]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dbpath</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/var/lib/isso/comments.db</span>
</span></span></code></pre></div></li>
<li><strong>What goes in <code>POST_URI</code>?</strong> Isso stores a <code>uri</code> string per thread. With the default embed config, it&rsquo;s usually the <strong>path</strong> (<code>/posts/&lt;slug&gt;/</code>). If you’re unsure, list recent threads:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;.headers on&#34;</span> <span style="color:#e6db74">&#34;.mode column&#34;</span> <span style="color:#e6db74">&#34;SELECT id, uri FROM threads ORDER BY id DESC LIMIT 50;&#34;</span>
</span></span></code></pre></div></li>
<li><strong>Clearnet vs <code>.onion</code></strong>: If you load the same post under multiple origins <em>and</em> your embed setup stores full origins, you may have <strong>multiple thread rows</strong>. The <code>LIKE</code> query above deliberately matches any uri containing your <code>POST_URI</code> to catch those.</li>
<li><strong>Rollback</strong>: To undo, stop Isso (optional), replace the DB with your backup (<code>comments.YYYY-MM-DD-HHMMSS.backup.sqlite3</code>), and start Isso again.</li>
<li><strong>HTTP API option</strong>: Isso supports <code>DELETE /&lt;comment_id&gt;</code> if admin endpoints/auth are enabled. For bulk removal it’s simpler and safer to use SQLite directly.</li>
<li><strong>Safety</strong>: Always run a backup first, and <strong>preview with the SELECT</strong> before you run the DELETE.</li>
</ul>
<hr>
<h2 id="one-liners-for-the-impatient">One-liners for the impatient</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>DB<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/var/lib/isso/comments.db&#34;</span>; POST_URI<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/posts/skin_routine/&#34;</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;.backup &#39;</span><span style="color:#66d9ef">$(</span>dirname <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">/comments.</span><span style="color:#66d9ef">$(</span>date +%F-%H%M%S<span style="color:#66d9ef">)</span><span style="color:#e6db74">.backup.sqlite3&#39;&#34;</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;DELETE FROM comments WHERE tid IN (SELECT id FROM threads WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;);&#34;</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$DB<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;DELETE FROM threads  WHERE uri LIKE &#39;%&#39; || &#39;</span>$POST_URI<span style="color:#e6db74">&#39; || &#39;%&#39;; VACUUM;&#34;</span>
</span></span></code></pre></div><hr>
<p><strong>Isso container: quick checks &amp; fixes for future me! (thank past me later ^^)</strong></p>
<p><strong>Find the container</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker ps -a --format <span style="color:#e6db74">&#39;table {{.ID}}	{{.Names}}	{{.Status}}	{{.Ports}}&#39;</span> | grep -i isso
</span></span></code></pre></div><p><strong>Inspect basic state</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker inspect -f <span style="color:#e6db74">&#39;Name={{.Name}}  Running={{.State.Running}}  Status={{.State.Status}}  StartedAt={{.State.StartedAt}}  FinishedAt={{.State.FinishedAt}}&#39;</span> isso
</span></span></code></pre></div><p><strong>Logs</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker logs --tail<span style="color:#f92672">=</span><span style="color:#ae81ff">200</span> isso
</span></span><span style="display:flex;"><span>docker logs -f isso
</span></span></code></pre></div><p><strong>Ports &amp; reachability</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker port isso
</span></span><span style="display:flex;"><span>sudo ss -ltnp | grep <span style="color:#e6db74">&#39;:8080 &#39;</span>
</span></span><span style="display:flex;"><span>curl -i http://127.0.0.1:8080/
</span></span></code></pre></div><p><strong>Restart policy (keep it running)</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker inspect -f <span style="color:#e6db74">&#39;{{.HostConfig.RestartPolicy.Name}}&#39;</span> isso
</span></span><span style="display:flex;"><span>docker update --restart unless-stopped isso
</span></span></code></pre></div><p><strong>Start/stop the container</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker start isso
</span></span><span style="display:flex;"><span>docker stop isso
</span></span></code></pre></div><p><strong>Who/what stopped it (events)</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker events --since 24h --filter container<span style="color:#f92672">=</span>isso
</span></span></code></pre></div><p><strong>Common config issue: duplicate options/sections</strong>
If logs show DuplicateOptionError or DuplicateSectionError, fix <code>/config/isso.cfg</code> (remove duplicate <code>[server]</code> blocks or duplicate <code>public-endpoint</code>).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nl -ba /config/isso.cfg | sed -n <span style="color:#e6db74">&#39;1,120p&#39;</span>
</span></span><span style="display:flex;"><span>grep -n <span style="color:#e6db74">&#39;^\[server\]&#39;</span> /config/isso.cfg
</span></span><span style="display:flex;"><span>grep -n <span style="color:#e6db74">&#39;^public-endpoint&#39;</span> /config/isso.cfg
</span></span></code></pre></div><p><strong>Using Docker Compose</strong> <em>(run these in the directory with your compose file)</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose ps
</span></span><span style="display:flex;"><span>docker compose logs --tail<span style="color:#f92672">=</span><span style="color:#ae81ff">200</span> isso
</span></span><span style="display:flex;"><span>docker compose up -d isso
</span></span></code></pre></div><p><strong>If the container doesn’t exist (create/recreate)</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker image ls | grep -i isso
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>docker run -d   --name isso   -p 8080:8080   -v /srv/isso/config:/config   -v /srv/isso/db:/db   --restart unless-stopped   ghcr.io/posativ/isso:latest
</span></span></code></pre></div><p><strong>Optional: exclude from auto-updaters (like Watchtower)</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker update --label-add com.centurylinklabs.watchtower.enable<span style="color:#f92672">=</span>false isso
</span></span></code></pre></div><p><em>This note is intentionally self-contained so I can paste it at the end of the blog post as a maintainer-only appendix.</em></p>
<hr>
<div class="signature-block" style="margin-top:1rem">
  <p><strong>Downloads:</strong>
    <a href="/sources/posts/new_features.md">Markdown</a> ·
    <a href="/sources/posts/new_features.md.asc">Signature (.asc)</a>
  </p><details class="signature">
    <summary>View OpenPGP signature</summary>
    <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbuMACgkQtYgoUOBM
jSr2VxAAgdHpf&#43;4EIx2ASmSB&#43;MeAHp7s1&#43;2EPhmn96QuhiQO9Dr1e9250LbF/R8S
A0zcN8mmyOtKv2rediU6HsGy6ddwdTAtpQDRvMw2Xuk2pBUZaG5LuvlNgSse9zVB
dcG3GVLuMSRgNmyolhFoSWn46aa&#43;3wZGrzYZQVUt8op&#43;2fVp36agq2YoB4zeGKSm
Ri43rO/kTingD0bclOA&#43;SMbHpKQOcLyHwDlVrqIjVXcJ/kKLcm1G3Z5owo&#43;gB6jY
EMjxYiqyf5Zcbt4q/WzojbHLffjXoMzOgZ1sDOIObVQni9atgV49X4oIZ3&#43;xSXSH
W7ZbH6sg9LlrU8djddBXUKB0i72IVZ/4F5icVFUcK&#43;szOmASy8fFP7gCOcCRATtb
eNIFtiAlRXI0CqJdeq5fE9b&#43;LX8lRpNnX229tg7GFgddzYUmFkKAsoJ9EUzUvUHm
Xzqlm9DKy9LG/CeOxo473fIo4YCT2fcmMnt9nCZW4iDKOVl1nCupkTn5qsfNdpQM
KycaNwsLBHPZWV&#43;jDon8NEzYQk07n1Q9rlEWdL0egvn2S&#43;pYnvLZbfi5dRhe&#43;ciC
qcEW/I1NZZRdU7MUEzhWvhbWsZtTkm6OevXjnqACv91DIQv7tNrKwZuASnpFeDRa
GE0QYRrsaI3vCLR3cUmBAFsvZdwgzH0RQLb8w21XBW/BoFvixbA=
=9k&#43;S
-----END PGP SIGNATURE-----
</pre>
  </details><p><em>Verify locally:</em></p>
  <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/new_features.md
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/new_features.md.asc
gpg --verify new_features.md.asc new_features.md
  </pre>
</div>


]]></content:encoded>
    </item>
    <item>
      <title>A Tiny &#39;Views&#39; Badge Sent Me Down a Rabbit Hole (PaperMod &#43; Busuanzi &#43; Debugging --&gt; swapped with GoatCounter the GOAT)</title>
      <link>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/papermod-views-debugging-story/</link>
      <pubDate>Sat, 18 Oct 2025 10:45:00 +0000</pubDate>
      <guid>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/papermod-views-debugging-story/</guid>
      <description>I tried to add a simple &amp;lsquo;views&amp;rsquo; counter to my PaperMod blog. It worked—until it didn’t. Here’s the story of blank numbers, a mysterious Chinese message, and the final fix.</description>
      <content:encoded><![CDATA[<p>I only wanted a tiny <strong>“views”</strong> badge next to my RSS link—just a friendly nudge that real humans were reading my words. PaperMod makes it easy to tuck little UI bits in the footer, so I dropped in Busuanzi (that neat, no-account page/site view counter) and called it a day.</p>
<p>The thing is, I don&rsquo;t think the counter even works correctly.
Or maybe I&rsquo;m undermining my blog popularity!
But regardless, it was a small stiching task that I did on this lovely Friday evening.
The day before my next term of German class starts.
The day before playing deadface CTF that I&rsquo;ve been counting days for.</p>
<h3 id="the-neat-footer-row">The neat footer row</h3>
<p>First I got the layout right. PaperMod supports extension partials, so I used a simple flex row to keep things side by side:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;site-meta-row&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/index.xml&#34;</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;alternate&#34;</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;application/rss+xml&#34;</span> <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;RSS&#34;</span>
</span></span><span style="display:flex;"><span>     <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rss-link&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:inline-flex;align-items:center;gap:.35rem;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    { partial &#34;svg.html&#34; (dict &#34;name&#34; &#34;rss&#34;) }
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">span</span>&gt;RSS&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>&gt;·&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;busuanzi_container_site_pv&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    Site views: &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;busuanzi_value_site_pv&#34;</span>&gt;—&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>&gt;·&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;busuanzi_container_site_uv&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    Visitors: &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;busuanzi_value_site_uv&#34;</span>&gt;—&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span></code></pre></div><p>So far, so good. The labels showed up. The numbers, though? <strong>Nothing.</strong> Just an em dash. I shrugged—maybe it was a caching thing.</p>
<h3 id="the-first-round-of-checks">The first round of checks</h3>
<p>I added the script site‑wide in <code>layouts/partials/extend_head.html</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;//cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.min.js&#34;</span> <span style="color:#a6e22e">defer</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt;
</span></span></code></pre></div><p>Refreshed. Still blank.</p>
<p><strong>DevTools time.</strong> Network tab: the script <strong>does</strong> load:</p>
<ul>
<li><strong>Request URL:</strong> <code>https://cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.min.js</code></li>
<li><strong>Request Method:</strong> GET</li>
<li><strong>Status Code:</strong> 304 Not Modified</li>
<li><strong>Referrer Policy:</strong> strict-origin-when-cross-origin</li>
<li><strong>User-Agent:</strong> a normal Chromium build (nothing exotic)</li>
</ul>
<p>No red flags. Just a perfectly normal 304.</p>
<p>Maybe my IDs were wrong? Busuanzi has a couple of flavors: <code>busuanzi_value_*</code> (with container wrappers) or shorthand like <code>busuanzi_site_pv</code>. I tried <strong>both</strong>, carefully, one at a time. Still… <strong>—</strong>.</p>
<h3 id="the-odd-message">The odd message</h3>
<p>Then it flashed for a moment—the Chinese line that made me google twice to be sure I saw it right:</p>
<blockquote>
<p><strong>“域名过长，已被禁用 views”</strong></p></blockquote>
<p>That’s “domain name too long, disabled.” Wait, what?</p>
<p>I checked my hostname: <strong><code>blog.alipourimjourneys.ir</code></strong>. I counted: <strong>27 characters</strong>. Busuanzi’s limit? <strong>22.</strong> Suddenly, everything clicked. The script loads, but the service refuses to count for long domains, so the placeholders never fill.</p>
<h3 id="two-ways-out">Two ways out</h3>
<p>At this point I had two clean options:</p>
<ol>
<li><strong>Keep Busuanzi</strong> and move the site to the <strong>apex domain</strong> (short enough).</li>
<li>Keep my beloved <code>blog.</code> subdomain and swap in <strong>Vercount</strong>, a Busuanzi‑style drop‑in without the 22‑char limit.</li>
</ol>
<p>I liked the subdomain (habit, mostly), so I tried <strong>Vercount</strong>.</p>
<h3 id="the-swap-five-minute-fix">The swap (five-minute fix)</h3>
<p>I removed the Busuanzi script and added Vercount instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- extend_head.html --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">defer</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://events.vercount.one/js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt;
</span></span></code></pre></div><p>I kept the same tidy footer row, but switched the IDs to Vercount’s:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;site-meta-row&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/index.xml&#34;</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;alternate&#34;</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;application/rss+xml&#34;</span> <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;RSS&#34;</span>
</span></span><span style="display:flex;"><span>     <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rss-link&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;display:inline-flex;align-items:center;gap:.35rem;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    { partial &#34;svg.html&#34; (dict &#34;name&#34; &#34;rss&#34;) }
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">span</span>&gt;RSS&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">a</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>&gt;·&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span>&gt;Site views: &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;vercount_value_site_pv&#34;</span>&gt;—&lt;/<span style="color:#f92672">span</span>&gt;&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>&gt;·&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">span</span>&gt;Visitors: &lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;vercount_value_site_uv&#34;</span>&gt;—&lt;/<span style="color:#f92672">span</span>&gt;&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span></code></pre></div><p>For per‑post counts (in the post meta), I added:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;vercount_value_page_pv&#34;</span>&gt;—&lt;/<span style="color:#f92672">span</span>&gt; views
</span></span></code></pre></div><p>Deploy. Refresh. <strong>Numbers.</strong></p>
<h3 id="but-if-you-want-to-stay-with-busuanzi">But if you want to stay with Busuanzi…</h3>
<p>If your apex domain is short enough, pointing the site there works fine. The gist:</p>
<ul>
<li>
<p>Set <code>baseURL</code> in Hugo to the apex:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;https://alipourimjourneys.ir/&#34;</span>
</span></span></code></pre></div></li>
<li>
<p>Add a 301 redirect from the long subdomain to the apex (Cloudflare, Netlify, Nginx—pick your tool).</p>
</li>
<li>
<p>Keep your Busuanzi spans as you had them; once the host fits the limit, they’ll populate.</p>
</li>
</ul>
<h3 id="postmortem-checklist-aka-things-i-learned">Post‑mortem checklist (a.k.a. things I learned)</h3>
<ul>
<li><strong>If the script loads but you get blanks</strong>, it’s not always IDs or caching. Sometimes it’s a <strong>service rule</strong>.</li>
<li><strong>Busuanzi refuses long hostnames</strong> (&gt;22 chars) and ignores localhost. On those hosts, the placeholders stay empty or show the Chinese “disabled” note.</li>
<li><strong>Don’t mix providers</strong> on the same page. Load exactly one script (Busuanzi <em>or</em> Vercount).</li>
<li><strong>CSP and blockers matter.</strong> If you run a strict Content‑Security‑Policy or heavy ad blockers, they can block the script. Test in a clean profile and allow the script origin.</li>
</ul>
<h3 id="happy-ending">Happy ending</h3>
<p>Now my footer shows RSS, site views, and visitors on a single neat line, and each post has a tiny “views” chip that quietly ticks upward. I got the tiny badge I wanted—and a reminder that the smallest features can hide the most interesting puzzles.</p>
<p>If you hit the same wall, check the hostname length first. It might save you an afternoon—and lead you to a solution you’ll feel weirdly proud of.</p>
<h3 id="final-notes">Final notes</h3>
<p>Yea&hellip; I just felt this feature was missing, and it&rsquo;s good to have it there.
I hope I stop adding things to the blog, and just keep writing in it.
I&rsquo;ve spent too much time on it, and it feels like wasted time.</p>
<hr>
<h2 id="change-of-plans-selfhosting-goatcounter-for-papermod-docker--cloudflare--onion">Change of plans: Self‑hosting GoatCounter for PaperMod (Docker + Cloudflare + Onion)</h2>
<p>This is the <strong>self‑host</strong> part I ended up with: Docker for GoatCounter, Cloudflare (orange‑cloud) in front, Nginx on the server as reverse proxy, plus my <code>.onion</code> mirror counting to the same GoatCounter site. Paste this whole section at the end of the post.</p>
<hr>
<h3 id="1-clean-up-old-counters-optional-once">1) Clean up old counters (optional, once)</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># From your Hugo site root</span>
</span></span><span style="display:flex;"><span>grep -RinE <span style="color:#e6db74">&#34;busuanzi|vercount|vercount_value_|busuanzi_value_&#34;</span> .
</span></span></code></pre></div><p>Remove any matching <code>&lt;script&gt;</code> and leftover <code>*_value_*</code> spans you no longer want.</p>
<hr>
<h3 id="2-run-goatcounter-with-docker-compose">2) Run GoatCounter with Docker Compose</h3>
<p>Minimal <code>docker-compose.yml</code> in <code>~/goatcounter</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">goatcounter</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">image</span>: <span style="color:#ae81ff">arp242/goatcounter:2.6</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">container_name</span>: <span style="color:#ae81ff">goatcounter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;127.0.0.1:8081:8080&#34;</span>   <span style="color:#75715e"># bind only to localhost; nginx will proxy</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">goatcounter-data:/home/goatcounter/goatcounter-data</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restart</span>: <span style="color:#ae81ff">unless-stopped</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">goatcounter-data</span>: {}
</span></span></code></pre></div><p>Start:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose pull <span style="color:#f92672">&amp;&amp;</span> docker compose up -d
</span></span></code></pre></div><p>Initialize the site (replace email if needed):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose exec goatcounter   goatcounter db create site   -vhost<span style="color:#f92672">=</span>statsblog.alipourimjourneys.ir   -user.email<span style="color:#f92672">=</span>iman.alip2001@gmail.com
</span></span></code></pre></div><blockquote>
<p>If you ever see “no site at this domain” on localhost: use the exact <strong>vhost</strong> above.</p></blockquote>
<hr>
<h3 id="3-cloudflare--origin-certificate-cf--origin-tls">3) Cloudflare + Origin certificate (CF → origin TLS)</h3>
<ul>
<li>DNS: <code>statsblog.alipourimjourneys.ir</code> → server IP (<strong>proxied/orange</strong>).</li>
<li>SSL/TLS mode: <strong>Full (strict)</strong>.</li>
<li>Issue a <strong>Cloudflare Origin Certificate</strong> for <code>statsblog.alipourimjourneys.ir</code> and save on the server:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mkdir -p /etc/ssl/cloudflare
</span></span><span style="display:flex;"><span>sudo nano /etc/ssl/cloudflare/origin.crt   <span style="color:#75715e"># paste PEM</span>
</span></span><span style="display:flex;"><span>sudo nano /etc/ssl/cloudflare/origin.key   <span style="color:#75715e"># paste key</span>
</span></span><span style="display:flex;"><span>sudo chown root:root /etc/ssl/cloudflare/origin.<span style="color:#f92672">{</span>crt,key<span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">644</span> /etc/ssl/cloudflare/origin.crt
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">600</span> /etc/ssl/cloudflare/origin.key
</span></span></code></pre></div><hr>
<h3 id="4-nginx-vhost-for-the-stats-subdomain">4) Nginx vhost for the stats subdomain</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># /etc/nginx/sites-available/statsblog.alipourimjourneys.ir.conf
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">server_name</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host$request_uri;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">server_name</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssl_certificate</span>     <span style="color:#e6db74">/etc/ssl/cloudflare/origin.crt</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssl_certificate_key</span> <span style="color:#e6db74">/etc/ssl/cloudflare/origin.key</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Proxy to the container on localhost
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span>              $host;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Real-IP</span>         $remote_addr;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span>   $proxy_add_x_forwarded_for;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> $scheme;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> { <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8081</span>; }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Enable &amp; reload:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ln -s /etc/nginx/sites-available/statsblog.alipourimjourneys.ir.conf /etc/nginx/sites-enabled/
</span></span><span style="display:flex;"><span>sudo nginx -t <span style="color:#f92672">&amp;&amp;</span> sudo systemctl reload nginx
</span></span></code></pre></div><hr>
<h3 id="5-hugo-integration-clearnet--onion-singularplural--0-views">5) Hugo integration (clearnet + onion, singular/plural + “0 views”)</h3>
<p>Self‑host <code>count.js</code> so the onion mirror never fetches a third‑party script:</p>
<ul>
<li>Save the official <code>count.js</code> as <code>static/js/count.js</code> in your Hugo repo.</li>
</ul>
<p>Then add this to <code>layouts/partials/extend_head.html</code> <strong>(loader + helper)</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- Loader: choose endpoint based on hostname --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>(<span style="color:#66d9ef">function</span> () {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/\.onion$/i</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hostname</span>);
</span></span><span style="display:flex;"><span>  window.<span style="color:#a6e22e">goatcounter</span> <span style="color:#f92672">=</span> { <span style="color:#a6e22e">endpoint</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;/count&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;https://statsblog.alipourimjourneys.ir/count&#39;</span> };
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#39;script&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">s</span>.<span style="color:#66d9ef">async</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>; <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">src</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;/js/count.js&#39;</span>;
</span></span><span style="display:flex;"><span>  document.<span style="color:#a6e22e">head</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">s</span>);
</span></span><span style="display:flex;"><span>})();
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!-- Helper: inline meta counts (post pages + lists) and totals --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>(<span style="color:#66d9ef">function</span> () {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/\.onion$/i</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hostname</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">GC_HOST</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;https://statsblog.alipourimjourneys.ir&#39;</span>; <span style="color:#75715e">// &#39;&#39; = same-origin on .onion
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">SING</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;view&#39;</span>, <span style="color:#a6e22e">PLUR</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;views&#39;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">nf</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Intl</span>.<span style="color:#a6e22e">NumberFormat</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">ready</span>(<span style="color:#a6e22e">fn</span>){ <span style="color:#66d9ef">if</span> (document.<span style="color:#a6e22e">readyState</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;loading&#39;</span>) <span style="color:#a6e22e">fn</span>(); <span style="color:#66d9ef">else</span> document.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;DOMContentLoaded&#39;</span>, <span style="color:#a6e22e">fn</span>); }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">gcPath</span>(){ <span style="color:#66d9ef">try</span> { <span style="color:#66d9ef">return</span> (window.<span style="color:#a6e22e">goatcounter</span> <span style="color:#f92672">&amp;&amp;</span> window.<span style="color:#a6e22e">goatcounter</span>.<span style="color:#a6e22e">get_data</span>) <span style="color:#f92672">?</span> window.<span style="color:#a6e22e">goatcounter</span>.<span style="color:#a6e22e">get_data</span>().<span style="color:#a6e22e">p</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">pathname</span>; } <span style="color:#66d9ef">catch</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">pathname</span>; } }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#a6e22e">path</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">GC_HOST</span><span style="color:#e6db74">}</span><span style="color:#e6db74">/counter/</span><span style="color:#e6db74">${</span>encodeURIComponent(<span style="color:#a6e22e">path</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">.json?t=</span><span style="color:#e6db74">${</span>Date.<span style="color:#a6e22e">now</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>, { <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span><span style="color:#e6db74">&#39;omit&#39;</span> });
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">status</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">404</span>) <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">ok</span>) <span style="color:#66d9ef">throw</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> Number(String(<span style="color:#a6e22e">j</span>.<span style="color:#a6e22e">count</span>).<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">/,/g</span>,<span style="color:#e6db74">&#39;&#39;</span>));
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> Number.isFinite(<span style="color:#a6e22e">n</span>) <span style="color:#f92672">?</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</span>; }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">label</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">n</span>) =&gt; (<span style="color:#a6e22e">n</span> <span style="color:#f92672">===</span> <span style="color:#66d9ef">null</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;—&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">nf</span>.<span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">n</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">n</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">SING</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">PLUR</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ready</span>(<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> () {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// A) Single post: add to existing meta row + small bottom
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">post</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.post-single&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">post</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">meta</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.post-header .post-meta, .post-header .entry-meta, .post-meta&#39;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">gcPath</span>();
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#a6e22e">path</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">meta</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.post-views&#39;</span>); <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">span</span>) { <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#39;span&#39;</span>); <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;post-views&#39;</span>; <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">span</span>); }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">label</span>(<span style="color:#a6e22e">n</span> <span style="color:#f92672">??</span> <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">after</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.post-content&#39;</span>) <span style="color:#f92672">||</span> <span style="color:#a6e22e">post</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">bottom</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.post-views-bottom&#39;</span>); <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">bottom</span>) { <span style="color:#a6e22e">bottom</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#39;div&#39;</span>); <span style="color:#a6e22e">bottom</span>.<span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;post-views-bottom&#39;</span>; <span style="color:#a6e22e">after</span>.<span style="color:#a6e22e">insertAdjacentElement</span>(<span style="color:#e6db74">&#39;afterend&#39;</span>, <span style="color:#a6e22e">bottom</span>); }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">bottom</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">label</span>(<span style="color:#a6e22e">n</span> <span style="color:#f92672">??</span> <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// B) Lists (/posts, home, sections): show “0 views” instead of hiding
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cards</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">querySelectorAll</span>(<span style="color:#e6db74">&#39;article.post-entry&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">await</span> Promise.<span style="color:#a6e22e">all</span>(Array.<span style="color:#a6e22e">from</span>(<span style="color:#a6e22e">cards</span>).<span style="color:#a6e22e">map</span>(<span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">card</span>) =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">meta</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">card</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.entry-footer, .post-meta&#39;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">link</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">card</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;a.entry-link, h2 a, .entry-title a&#39;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">meta</span> <span style="color:#f92672">||</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">link</span>) <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">u</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URL</span>(<span style="color:#a6e22e">link</span>.<span style="color:#a6e22e">getAttribute</span>(<span style="color:#e6db74">&#39;href&#39;</span>), <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">origin</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">origin</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">origin</span>) <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;.post-views&#39;</span>); <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">span</span>) { <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#39;span&#39;</span>); <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;post-views&#39;</span>; <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">span</span>); }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">pathname</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">label</span>(<span style="color:#a6e22e">n</span> <span style="color:#f92672">??</span> <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>      } <span style="color:#66d9ef">catch</span> {}
</span></span><span style="display:flex;"><span>    }));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// C) Site totals (optional placeholders)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">pvEl</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;vercount_value_site_pv&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pvEl</span>) { <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#e6db74">&#39;TOTAL&#39;</span>); <span style="color:#a6e22e">pvEl</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">n</span><span style="color:#f92672">===</span><span style="color:#66d9ef">null</span>) <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;—&#39;</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">nf</span>.<span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">n</span>); }
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>})();
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">script</span>&gt;
</span></span></code></pre></div><p>Style so meta items stay inline with middle dots (PaperMod‑like). Put this in <code>assets/css/extended/goatcounter.css</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">post-meta</span><span style="color:#f92672">,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">entry-meta</span><span style="color:#f92672">,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">entry-footer</span><span style="color:#f92672">,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">post-meta</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">display</span>:<span style="color:#66d9ef">flex</span>; <span style="color:#66d9ef">align-items</span>:<span style="color:#66d9ef">center</span>; <span style="color:#66d9ef">flex-wrap</span>:<span style="color:#66d9ef">wrap</span>; <span style="color:#66d9ef">gap</span>:<span style="color:#ae81ff">.35</span><span style="color:#66d9ef">rem</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">entry-meta</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">entry-footer</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">display</span>:<span style="color:#66d9ef">inline-flex</span>; <span style="color:#66d9ef">align-items</span>:<span style="color:#66d9ef">center</span>; <span style="color:#66d9ef">white-space</span>:<span style="color:#66d9ef">nowrap</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span><span style="color:#f92672">,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">entry-meta</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span><span style="color:#f92672">,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">entry-footer</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span><span style="color:#f92672">,</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">&gt;</span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">content</span>:<span style="color:#e6db74">&#34;·&#34;</span>; <span style="color:#66d9ef">margin</span>:<span style="color:#ae81ff">0</span> <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span>; <span style="color:#66d9ef">opacity</span>:<span style="color:#ae81ff">.6</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-views-bottom</span> { <span style="color:#66d9ef">margin</span>:<span style="color:#ae81ff">.6</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">0</span> <span style="color:#ae81ff">0</span>; <span style="color:#66d9ef">opacity</span>:<span style="color:#ae81ff">.75</span>; <span style="color:#66d9ef">font-size</span>:<span style="color:#ae81ff">.95</span><span style="color:#66d9ef">em</span>; }
</span></span></code></pre></div><hr>
<h3 id="6-count-tor-onion-visits-into-the-same-site">6) Count Tor (.onion) visits <strong>into the same site</strong></h3>
<p>In the <strong>.onion</strong> server block, proxy GoatCounter endpoints to the same site by forcing the Host header:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># inside: server { ... } for fjthpp2h3mj2r....onion
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">location</span> = <span style="color:#e6db74">/count</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8081/count</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span> $proxy_add_x_forwarded_for;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> <span style="color:#e6db74">http</span>; <span style="color:#75715e"># onion is http
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">location</span> <span style="color:#e6db74">/counter/</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8081/counter/</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span> $proxy_add_x_forwarded_for;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> <span style="color:#e6db74">http</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Cache-Control</span> <span style="color:#e6db74">&#34;no-store&#34;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">proxy_no_cache</span> <span style="color:#ae81ff">1</span>; <span style="color:#f92672">proxy_cache_bypass</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Because the loader above sets <code>window.goatcounter.endpoint</code> to <code>/count</code> on <code>.onion</code>, both <strong>hits</strong> and <strong>reads</strong> stay on the onion origin and are tallied into the same GoatCounter site.</p>
<blockquote>
<p><strong>Note on DNT/Tor:</strong> Tor Browser sends <code>DNT: 1</code>. GoatCounter respects DNT by default, so Tor users won’t be counted unless you disable “Respect Do Not Track” in GoatCounter settings. Your choice; I left the code compatible either way.</p></blockquote>
<hr>
<h3 id="7-troubleshooting-quick-notes">7) Troubleshooting quick notes</h3>
<ul>
<li><strong>400 “no site at this domain”</strong> → create the site with <code>-vhost=statsblog.alipourimjourneys.ir</code> or fix your proxy <code>Host</code> header.</li>
<li><strong>Counts don’t change on onion</strong> → verify Tor path via <code>torsocks</code>, watch logs:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose logs -f goatcounter
</span></span><span style="display:flex;"><span><span style="color:#75715e"># look for “ignored: bot” (curl UA), or DNT being respected</span>
</span></span></code></pre></div></li>
<li><strong>DevTools sanity</strong> → on clearnet you should see <code>https://statsblog…/count</code> and <code>…/counter/*.json</code>; on onion you should see <code>/count</code> and <code>/counter/*.json</code> (same‑origin).</li>
</ul>
<p>That’s it. One dashboard; clearnet + onion both increment; tiny inline counters everywhere.</p>
<p>Honestly, self-hosting GoatCounter wasn&rsquo;t as straightforward as I thought it would be. It almost made me cry a few times. I definitly thought about my life choices a few times along the way. But oh well, now it&rsquo;s done and dusted, and I&rsquo;m a happy puppy! :D</p>
<hr>
<div class="signature-block" style="margin-top:1rem">
  <p><strong>Downloads:</strong>
    <a href="/sources/posts/view_count.md">Markdown</a> ·
    <a href="/sources/posts/view_count.md.asc">Signature (.asc)</a>
  </p><details class="signature">
    <summary>View OpenPGP signature</summary>
    <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbuoACgkQtYgoUOBM
jSolRg//SwN9nMf9/Wgkr5h&#43;dQowZMCHXXcKWgO8J08ITVDN1&#43;weNNGANFrz63SQ
DajHGeSogYW1&#43;AAxJaAeQ7hLdOX9foV5pT&#43;kWANIjRBXnFyW&#43;WR7kt6tmN2rcSH8
z4GKxqE3kdd3kCRhqT0pLiwnurqLgdCK&#43;YldftO6GB8WP4oNDqOwHvoIE6o0ekdH
sn7ZBIxMaWc5UqijjqgBHhdHz3uSmL&#43;rN4UwLFM3WXXlwl3HdGyjHcNTG7k648s2
X5&#43;7a78OZAuDElpyp9y6WqiAFJQdrwBbTv3vvpU&#43;f911voV7ZA&#43;KbUqHsQrISTKz
cd&#43;tPw2lcayfBZRtHMrL3fygvT3AWktcSavZrU5EOX/u/P7eMWEYu0FYh6aHqRak
&#43;Ft2FR7EPlB2faS6wWYfoua6BYxFvevXX1fk&#43;0HhZn9UJxJPaa/WTgVWDgpt&#43;&#43;Ce
fdaDmsR69LIj2QTjPMXdQiUKkWPG2ziadTwJEWUHzOuREifmuBgp9Mwedk6zYkdT
m5Roi70IPtX3dDDHG5Votw3AKng3gaU7YcNzZVyi3iZkQkUHPUK47Wj21FajikMP
gCcWsoYWBRqdhL5XDrodt28AuLpm0cslz79DZdbLnielcjOS&#43;GaUynjLzEWveOib
dxLcbIIap&#43;nt315cjvkfatGv14Dres/K7vhQAhqGy5o0LM1D0qc=
=o387
-----END PGP SIGNATURE-----
</pre>
  </details><p><em>Verify locally:</em></p>
  <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/view_count.md
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/view_count.md.asc
gpg --verify view_count.md.asc view_count.md
  </pre>
</div>


]]></content:encoded>
    </item>
    <item>
      <title>Running my blog on Tor (.onion) and the Clearnet with Hugo &#43; PaperMod</title>
      <link>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/tor_clearnet_blog/</link>
      <pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate>
      <guid>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/tor_clearnet_blog/</guid>
      <description>How I hosted a Hugo &#43; PaperMod site both as a Tor onion service and a normal HTTPS site behind Cloudflare, with clean configs, dual builds, and a one-command deploy.</description>
      <content:encoded><![CDATA[<blockquote>
<p>TL;DR — The site is built once (Hugo project), <strong>published twice</strong>:</p>
<ul>
<li><strong>Onion</strong>: <code>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/</code> via Tor, no TLS/HSTS, bound to <code>127.0.0.1:3301</code>.</li>
<li><strong>Clearnet</strong>: <code>https://blog.alipourimjourneys.ir</code> behind Cloudflare, Let’s Encrypt cert, <code>Onion-Location</code> header pointing to the onion mirror.</li>
</ul></blockquote>
<hr>
<h2 id="1-tor-hidden-service-onion-basics">1) Tor hidden service (onion) basics</h2>
<p>I used Tor’s v3 onion services and mapped onion port 80 → my local web server on <code>127.0.0.1:3301</code>.</p>
<p><strong>Install &amp; configure Tor (Debian/Ubuntu):</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update <span style="color:#f92672">&amp;&amp;</span> sudo apt install -y tor
</span></span><span style="display:flex;"><span>sudoedit /etc/tor/torrc
</span></span></code></pre></div><p>Add:</p>
<pre tabindex="0"><code>HiddenServiceDir /var/lib/tor/hidden_site/
HiddenServicePort 80 127.0.0.1:3301
</code></pre><p>Make sure the directory is owned by Tor’s user and private:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mkdir -p /var/lib/tor/hidden_site
</span></span><span style="display:flex;"><span>sudo chown -R debian-tor:debian-tor /var/lib/tor/hidden_site
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">700</span> /var/lib/tor/hidden_site
</span></span></code></pre></div><p><strong>Important:</strong> use the right systemd unit:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># validate as the Tor user</span>
</span></span><span style="display:flex;"><span>sudo -u debian-tor tor -f /etc/tor/torrc --verify-config
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># (re)start the real service</span>
</span></span><span style="display:flex;"><span>sudo systemctl enable --now tor@default
</span></span><span style="display:flex;"><span>sudo systemctl restart tor@default
</span></span></code></pre></div><p>Get the onion address:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo cat /var/lib/tor/hidden_site/hostname
</span></span></code></pre></div><p>Quick check (do remote DNS via SOCKS):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -I --socks5-hostname 127.0.0.1:9050 <span style="color:#e6db74">&#34;http://</span><span style="color:#66d9ef">$(</span>sudo cat /var/lib/tor/hidden_site/hostname<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><hr>
<h2 id="2-nginx-for-the-onion-localhost-only">2) Nginx for the onion (localhost-only)</h2>
<p>I keep the onion site strictly on localhost: <strong>no HTTPS, no redirects, no HSTS</strong>. Tor already provides e2e encryption and authenticity.</p>
<p><code>/etc/nginx/sites-available/onion-blog</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> 127.0.0.1:<span style="color:#ae81ff">3301</span> <span style="color:#e6db74">default_server</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">&lt;your-56-char&gt;.onion</span> 127.0.0.1 <span style="color:#e6db74">localhost</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># keep onion simple; no HSTS/redirects here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Referrer-Policy</span> <span style="color:#e6db74">no-referrer</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">add_header</span> <span style="color:#e6db74">X-Content-Type-Options</span> <span style="color:#e6db74">nosniff</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">add_header</span> <span style="color:#e6db74">X-Frame-Options</span> <span style="color:#e6db74">SAMEORIGIN</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># (optional) strict CSP so nothing leaks to clearnet
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># add_header Content-Security-Policy &#34;default-src &#39;self&#39;; img-src &#39;self&#39; data:; style-src &#39;self&#39; &#39;unsafe-inline&#39;; script-src &#39;self&#39;&#34; always;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">root</span> <span style="color:#e6db74">/srv/hugo/mysite/public-onion</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">index</span> <span style="color:#e6db74">index.html</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> { <span style="color:#f92672">try_files</span> $uri $uri/ <span style="color:#e6db74">/index.html</span>; }
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> ~<span style="color:#e6db74">*</span> <span style="color:#e6db74">\.(css|js|ico|png|jpg|jpeg|gif|svg|webp|txt|xml)</span>$ {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">access_log</span> <span style="color:#66d9ef">off</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Cache-Control</span> <span style="color:#e6db74">&#34;public,</span> <span style="color:#e6db74">max-age=31536000,</span> <span style="color:#e6db74">immutable&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">try_files</span> $uri =<span style="color:#ae81ff">404</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Enable/reload:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ln -sf /etc/nginx/sites-available/onion-blog /etc/nginx/sites-enabled/onion-blog
</span></span><span style="display:flex;"><span>sudo nginx -t <span style="color:#f92672">&amp;&amp;</span> sudo systemctl reload nginx
</span></span></code></pre></div><blockquote>
<p>Gotcha I hit: I had <strong>two</strong> server blocks on <code>127.0.0.1:3301</code>, which caused 404s via onion. Make sure only the intended vhost listens there (or mark it <code>default_server</code>). Also, if you ever use a regex in <code>server_name</code>, the syntax is <code>server_name ~* \.onion$</code> (note the space after <code>~*</code>).</p></blockquote>
<hr>
<h2 id="3-hugo--papermod-setup-and-version-bumps">3) Hugo + PaperMod setup (and version bumps)</h2>
<p>PaperMod now requires <strong>Hugo Extended ≥ 0.146.0</strong>. I installed the extended binary from the official tarball to avoid Snap’s sandbox limitations (Snap can’t read <code>/srv</code> paths by default).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># install Hugo extended (example)</span>
</span></span><span style="display:flex;"><span>VER<span style="color:#f92672">=</span>0.146.0
</span></span><span style="display:flex;"><span>cd /tmp
</span></span><span style="display:flex;"><span>wget https://github.com/gohugoio/hugo/releases/download/v<span style="color:#e6db74">${</span>VER<span style="color:#e6db74">}</span>/hugo_extended_<span style="color:#e6db74">${</span>VER<span style="color:#e6db74">}</span>_Linux-amd64.tar.gz
</span></span><span style="display:flex;"><span>tar -xzf hugo_extended_<span style="color:#e6db74">${</span>VER<span style="color:#e6db74">}</span>_Linux-amd64.tar.gz
</span></span><span style="display:flex;"><span>sudo mv hugo /usr/local/bin/hugo
</span></span><span style="display:flex;"><span>hugo version  <span style="color:#75715e"># should say &#34;extended&#34; and &gt;= 0.146.0</span>
</span></span></code></pre></div><p>Create the site and theme:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mkdir -p /srv/hugo <span style="color:#f92672">&amp;&amp;</span> sudo chown -R <span style="color:#e6db74">&#34;</span>$USER<span style="color:#e6db74">&#34;</span>:<span style="color:#e6db74">&#34;</span>$USER<span style="color:#e6db74">&#34;</span> /srv/hugo
</span></span><span style="display:flex;"><span>cd /srv/hugo
</span></span><span style="display:flex;"><span>hugo new site mysite
</span></span><span style="display:flex;"><span>cd mysite
</span></span><span style="display:flex;"><span>git init
</span></span><span style="display:flex;"><span>git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod
</span></span></code></pre></div><p><strong>Config updates:</strong> in newer Hugo, <code>paginate</code> is deprecated → use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#75715e"># config/_default/hugo.toml</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">title</span> = <span style="color:#e6db74">&#34;My Blog&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">theme</span> = <span style="color:#e6db74">&#34;PaperMod&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">enableRobotsTXT</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">pagination</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">pagerSize</span> = <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">defaultTheme</span> = <span style="color:#e6db74">&#34;auto&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">showReadingTime</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">showPostNavLinks</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">showBreadCrumbs</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">showCodeCopyButtons</span> = <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>I added per-environment overrides so I can build two outputs with different <code>baseURL</code>s:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#75715e"># config/clearnet/hugo.toml</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;https://blog.alipourimjourneys.ir/&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># config/onion/hugo.toml</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/&#34;</span>
</span></span></code></pre></div><hr>
<h2 id="4-dual-builds-clearnet--onion-and-one-command-deploy">4) Dual builds (clearnet + onion) and one-command deploy</h2>
<p>I publish the same content twice—once for each base URL and docroot:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd /srv/hugo/mysite
</span></span><span style="display:flex;"><span><span style="color:#75715e"># clearnet build (served over HTTPS)</span>
</span></span><span style="display:flex;"><span>hugo --minify --environment clearnet -d public-clearnet
</span></span><span style="display:flex;"><span><span style="color:#75715e"># onion build (served via Tor)</span>
</span></span><span style="display:flex;"><span>hugo --minify --environment onion    -d public-onion
</span></span><span style="display:flex;"><span>sudo systemctl reload nginx
</span></span></code></pre></div><p>Helper script I use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo tee /usr/local/bin/build-both &gt;/dev/null <span style="color:#e6db74">&lt;&lt;&#39;EOF&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">set -euo pipefail
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">cd /srv/hugo/mysite
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">hugo --minify --environment clearnet -d public-clearnet
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">hugo --minify --environment onion    -d public-onion
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">sudo systemctl reload nginx
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">echo &#34;Deployed both at $(date)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">EOF</span>
</span></span><span style="display:flex;"><span>sudo chmod +x /usr/local/bin/build-both
</span></span></code></pre></div><p>While editing, I sometimes auto-rebuild on file save:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install -y entr
</span></span><span style="display:flex;"><span>cd /srv/hugo/mysite
</span></span><span style="display:flex;"><span>find content layouts assets static config -type f | entr -r build-both
</span></span></code></pre></div><hr>
<h2 id="5-clearnet-behind-cloudflare--lets-encrypt-manual-dns-01">5) Clearnet behind Cloudflare + Let’s Encrypt (manual DNS-01)</h2>
<p>Cloudflare is set to <strong>Full (strict)</strong>. I issued a public cert for <code>blog.alipourimjourneys.ir</code> using <strong>manual DNS-01</strong> (no API token):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo snap install --classic certbot
</span></span><span style="display:flex;"><span>sudo certbot certonly --manual --preferred-challenges dns   -d blog.alipourimjourneys.ir   --agree-tos -m you@example.com --no-eff-email
</span></span></code></pre></div><p>Certbot tells you to add a TXT record <code>_acme-challenge.blog.alipourimjourneys.ir</code>. Add it in Cloudflare DNS, verify with <code>dig</code>, then continue. Cert ends up at:</p>
<pre tabindex="0"><code>/etc/letsencrypt/live/blog.alipourimjourneys.ir/fullchain.pem
/etc/letsencrypt/live/blog.alipourimjourneys.ir/privkey.pem
</code></pre><p>Clearnet Nginx vhosts:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># HTTP → HTTPS redirect (optional; CF usually talks HTTPS to origin anyway)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span>; <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:80</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">server_name</span> <span style="color:#e6db74">blog.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://blog.alipourimjourneys.ir</span>$request_uri;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># HTTPS origin (behind Cloudflare)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>; <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">server_name</span> <span style="color:#e6db74">blog.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssl_certificate</span>     <span style="color:#e6db74">/etc/letsencrypt/live/blog.alipourimjourneys.ir/fullchain.pem</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssl_certificate_key</span> <span style="color:#e6db74">/etc/letsencrypt/live/blog.alipourimjourneys.ir/privkey.pem</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># HSTS on clearnet only
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Strict-Transport-Security</span> <span style="color:#e6db74">&#34;max-age=31536000</span>; <span style="color:#f92672">includeSubDomains</span>; <span style="color:#f92672">preload&#34;</span> <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Help Tor Browser discover the onion mirror (safe on clearnet)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Onion-Location</span> <span style="color:#e6db74">&#34;http://&lt;your-56-char&gt;.onion</span>$request_uri&#34; <span style="color:#e6db74">always</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">root</span> <span style="color:#e6db74">/srv/hugo/mysite/public-clearnet</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">index</span> <span style="color:#e6db74">index.html</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> { <span style="color:#f92672">try_files</span> $uri $uri/ <span style="color:#e6db74">/index.html</span>; }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><blockquote>
<p>Note: don’t add HSTS or HTTPS redirects to the <strong>onion</strong> vhost. Keep onion pure HTTP on localhost.</p></blockquote>
<hr>
<h2 id="6-troubleshooting-i-ran-into-and-fixes">6) Troubleshooting I ran into (and fixes)</h2>
<ul>
<li><strong>Tor unit confusion:</strong> <code>tor.service</code> is a tiny master; the real daemon is <code>tor@default</code>. Use that unit and verify config as <code>debian-tor</code>.</li>
<li><strong>Permissions:</strong> <code>HiddenServiceDir</code> must be owned by <code>debian-tor</code> and mode <code>700</code>.</li>
<li><strong>Mapping mismatch:</strong> If <code>HiddenServicePort 80 127.0.0.1:3301</code> is set, visit <code>http://&lt;onion&gt;/</code> (no <code>:3301</code>). If you set <code>HiddenServicePort 3301 127.0.0.1:3301</code>, you must use <code>http://&lt;onion&gt;:3301/</code>.</li>
<li><strong>Curl &amp; .onion:</strong> modern curl refuses <code>.onion</code> unless you use remote DNS via SOCKS:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -I --socks5-hostname 127.0.0.1:9050 <span style="color:#e6db74">&#34;http://&lt;onion&gt;/&#34;</span>
</span></span></code></pre></div></li>
<li><strong>Two Nginx vhosts on the same port:</strong> I had a duplicate server on <code>127.0.0.1:3301</code> pointing somewhere else, which caused onion 404s. Keep only one (or mark one <code>default_server</code>).</li>
<li><strong>Regex in <code>server_name</code>:</strong> if you use it, write <code>server_name ~* \.onion$</code> (space after <code>~*</code>). I fixed an <code>invalid variable name</code> error caused by a missing space.</li>
<li><strong>PaperMod with old Hugo:</strong> upgraded to <strong>extended ≥ 0.146.0</strong>. Also updated <code>paginate</code> → <code>[pagination].pagerSize</code>.</li>
<li><strong>Snap confinement:</strong> Snap’s <code>hugo</code> couldn’t read <code>/srv</code> (<code>.../void: permission denied</code>). Switched to the tarball build in <code>/usr/local/bin</code>.</li>
</ul>
<hr>
<h2 id="7-not-secure-in-tor-browser">7) “Not secure” in Tor Browser?</h2>
<p>That message can appear because onion uses <strong>HTTP</strong>. It’s OK: Tor provides e2e encryption + onion auth. If you enable HTTPS-Only Mode, add an exception for the site. Also ensure the onion build doesn’t reference <strong>clearnet</strong> resources (scan the built HTML for <code>http(s)://</code> links that aren’t your onion).</p>
<hr>
<h2 id="8-day-to-day-workflow">8) Day-to-day workflow</h2>
<ul>
<li>Create content:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>hugo new posts/my-first-post.md  <span style="color:#75715e"># then set draft: false</span>
</span></span></code></pre></div></li>
<li>Publish both:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>build-both
</span></span></code></pre></div></li>
<li>(Optional) Auto on save:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find content layouts assets static config -type f | entr -r build-both
</span></span></code></pre></div></li>
<li>(Optional) Git push-to-deploy with a bare repo + post-receive hook that runs <code>build-both</code>.</li>
</ul>
<hr>
<h2 id="final-notes">Final notes</h2>
<ul>
<li>Clearnet gets <strong>HTTPS + HSTS</strong> and an <code>Onion-Location</code> header.</li>
<li>Onion gets <strong>no HSTS/redirects</strong>, and all assets are self-hosted to avoid mixed content.</li>
<li>Serving both worlds from one Hugo repo is easy: <strong>two builds, two vhosts, one workflow</strong>.</li>
</ul>
<hr>
<div class="signature-block" style="margin-top:1rem">
  <p><strong>Downloads:</strong>
    <a href="/sources/posts/tor_clearnet_blog.md">Markdown</a> ·
    <a href="/sources/posts/tor_clearnet_blog.md.asc">Signature (.asc)</a>
  </p><details class="signature">
    <summary>View OpenPGP signature</summary>
    <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbuQACgkQtYgoUOBM
jSr80hAAqr/XVnG0YNXXgG5FmVK/xBo5VVdYxba&#43;znAc1rCWJuzwHe2Ih0aYsq7d
DMWRkpE9YTfQl/kSYpzlk96KCKv&#43;6lHQXqcPyq&#43;nQTDl/D7iCaOhb8Dog3W/2qN4
6G05f47EoiOJY&#43;G/XgUHKqK75QhJrGUtom71t30alciaEGW2SauewBVTGmD&#43;x4y4
UN8LABLuB/ZE8sInjoTRr28LWVj6XeHMueY9/tpS9Fm3yw3nHnE4Fv77FtTHQiMo
FwGfnomCwVwd5GpaP7LQdtP/a&#43;x4s/bya2keFLW89xgwXolWB6U3y5BLFeVHptQm
slRx0&#43;P27gH&#43;CRtoXKI4kHThbmkmjM9ohJc7kxrkkbzB3ujkrxjC&#43;rZnHXPCdbsZ
TTiwtJ/jLLbX&#43;NfycW9qR6gVxloO35QA90AY7050tOgAsyH&#43;YUn&#43;Rtj2KVj91E0o
VzljG7RAly7yTe&#43;yxzC6OO&#43;iCJvLS6OpLEN5oIG9CmRm5cw/qB2rl8g/Qsru0t7/
OrTnJ8fJqq&#43;XRhBet/eR4zogcSEo7fTMp0pDdapMYkO3Xe5VPQ/3Gu7xr8utoXXZ
N6VbRTRfq2Vnp&#43;A9NshVsaKqXXaXsAL8GivMk37/bqteZ2BWedvzk1PpGf3f9HS8
700JFbZi9uEECoxtnv0uK67i8lkax/gsiTiGdjmx5mzfXfUQXVM=
=QlNw
-----END PGP SIGNATURE-----
</pre>
  </details><p><em>Verify locally:</em></p>
  <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/tor_clearnet_blog.md
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/tor_clearnet_blog.md.asc
gpg --verify tor_clearnet_blog.md.asc tor_clearnet_blog.md
  </pre>
</div>


]]></content:encoded>
    </item>
  </channel>
</rss>
