Initial import of Hugo site to Forgejo

This commit is contained in:
Iman Alipour 2026-07-28 12:47:20 +00:00
commit cb1ba0317f
1259 changed files with 236349 additions and 0 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,336 @@
<?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>Stories on AlipourIm journeys</title><link>https://blog.alipour.eu/categories/stories/</link><description>Recent content in Stories on AlipourIm journeys</description><generator>Hugo -- 0.146.0</generator><language>en</language><lastBuildDate>Sat, 18 Oct 2025 10:45:00 +0000</lastBuildDate><atom:link href="https://blog.alipour.eu/categories/stories/index.xml" rel="self" type="application/rss+xml"/><item><title>A Tiny 'Views' Badge Sent Me Down a Rabbit Hole (PaperMod + Busuanzi + Debugging --> swapped with GoatCounter the GOAT)</title><link>https://blog.alipour.eu/posts/papermod-views-debugging-story/</link><pubDate>Sat, 18 Oct 2025 10:45:00 +0000</pubDate><guid>https://blog.alipour.eu/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 didnt. Heres 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 sitewide 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>Thats “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>. Busuanzis 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 Busuanzistyle dropin without the 22char 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 Vercounts:</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 perpost 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, theyll populate.</p>
</li>
</ul>
<h3 id="postmortem-checklist-aka-things-i-learned">Postmortem checklist (a.k.a. things I learned)</h3>
<ul>
<li><strong>If the script loads but you get blanks</strong>, its not always IDs or caching. Sometimes its 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>Dont 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 ContentSecurityPolicy 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 youll 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: Selfhosting GoatCounter for PaperMod (Docker + Cloudflare + Onion)</h2>
<p>This is the <strong>selfhost</strong> part I ended up with: Docker for GoatCounter, Cloudflare (orangecloud) 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>Selfhost <code>count.js</code> so the onion mirror never fetches a thirdparty 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 (PaperModlike). 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 wont 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 dont 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> (sameorigin).</li>
</ul>
<p>Thats 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;">curl -fSLO https://blog.alipour.eu/sources/posts/view_count.md
curl -fSLO https://blog.alipour.eu/sources/posts/view_count.md.asc
gpg --verify view_count.md.asc view_count.md
</pre>
</div>
]]></content:encoded></item></channel></rss>

View file

@ -0,0 +1,2 @@
<!doctype html><html lang=en><head><title>https://blog.alipour.eu/categories/stories/</title>
<link rel=canonical href=https://blog.alipour.eu/categories/stories/><meta name=robots content="noindex"><meta charset=utf-8><meta http-equiv=refresh content="0; url=https://blog.alipour.eu/categories/stories/"></head></html>