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