Isso 0.14.0 pip + systemd 部署指南

Ubuntu 22.04 实测记录:官方主路径 pip+virtualenv+systemd 部署 Isso 0.14.0 全流程——apt 依赖链、最小 isso.cfg、systemd 单元与首启 PermissionError 修复、API/CORS/guard 限流/管理后台表单登录实测回显、embed.min.js 嵌入与备份恢复,附 10 条排障清单。

IssoIsso自托管systemd评论系统2026/11/02

Isso 0.14.0 pip + systemd 部署指南

本文按官方主安装路径(PyPI + virtualenv + isso.cfg + systemd)在 Ubuntu 22.04 服务器上从零部署 Isso 0.14.0 评论服务,覆盖依赖安装、配置编写、systemd 单元、首次启动踩坑修复、API/CORS/防刷实测、前端嵌入、管理后台与备份升级,全部命令与回显均为 2026-11-02 实测记录。选型评估见 《Isso 0.14.0 介绍与选型指南》,配置参数逐项语义见 《Isso 0.14.0 配置参数详解》,API 路由细节见 《Isso 0.14.0 API 使用详解》,容器形态部署见 《Isso Docker Compose 部署指南》。

一、版本确认与部署形态定夺

1.1 版本实查

2026-11-02 查询 PyPI(https://pypi.org/project/isso/ ),pip install isso 解析到的最新稳定版为 0.14.0,包描述明确其定位:"lightweight Disqus alternative",特性列表包含 Markdown 评论、SQLite 后端、Disqus/WordPress 导入、65kB(gzip 后 20kB)单文件 JS 客户端。GitHub 仓库为 isso-comments/isso。

1.2 部署形态决策树

官方安装文档(https://isso-comments.de/docs/reference/installation/ ,查询日期 2026-11-02)给出的路径优先级是:

  1. Install from PyPi(virtualenv + pip,官方开篇主路径,附 Debian/Fedora 依赖清单);
  2. Prebuilt Packages(Arch AUR;Debian 官方源已于 2021 年移除该包);
  3. Using Docker(官方镜像 ghcr.io/isso-comments/isso:release,2024 年 3 月起稳定版标签从 :latest 改为 :release,维护者建议钉住如 isso:0.13.0 的发布标签);
  4. Install from Source(git clone + make js + pip -e,面向开发者);
  5. Init scripts(systemd/SysVinit/OpenBSD/FreeBSD/Supervisor 参考单元)。

结论:B1(pip + isso.cfg + systemd)为本文主路径——它是官方文档的第一推荐,且 Isso 是单进程 WSGI 应用、SQLite 单文件落盘,源码/包安装与容器安装的能力完全等价;本专栏已收录 Docker Compose 形态的部署篇,本文补齐裸机 pip + systemd 形态,两篇互为对照。若你的目标是 5 分钟起服务且已有容器运行时,可直接读 Compose 篇;若要做系统级服务、反向代理与日志纳管,按本文走。

1.3 实验环境

  • 操作系统:Ubuntu 22.04(systemd 252)
  • Python:3.10.12(满足官方要求 Python 3.8+)
  • SQLite:3.37.2(满足 3.3.8+)
  • 反向代理/演示页:nginx 1.18.0
  • 服务监听:0.0.0.0:8080;演示站点对内 10.89.72.10:80

二、环境准备

2.1 安装系统依赖

官方要求 Python 开发头文件、venv、SQLite 与可用的 C 编译器(依赖 misaka 需源码构建,见 2.2 回显):

apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y python3-dev python3-venv python3-pip sqlite3 build-essential nginx curl

验证版本:

$ python3 --version
Python 3.10.12
$ sqlite3 --version
3.37.2 2022-01-06 13:25:41 872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5dalt1
$ nginx -v
nginx version: nginx/1.18.0 (Ubuntu)

2.2 创建专用运行用户

以 root 跑评论服务没有必要,创建系统账号 isso,家目录即数据目录:

$ useradd -r -s /usr/sbin/nologin -d /var/lib/isso isso
$ mkdir -p /var/lib/isso
$ chown isso:isso /var/lib/isso

三、从 PyPI 安装

官方反复强调不要在系统 Python 里直接 pip install(会污染发行版自带的 Python 环境),标准做法是独立 virtualenv:

$ python3 -m venv /opt/isso
$ /opt/isso/bin/pip install isso

实测回显(尾部):

Using legacy 'setup.py install' for misaka, since package 'wheel' is not installed.
Building wheels for collected packages: isso
  Building wheel for isso (pyproject.toml): started
  Building wheel for isso (pyproject.toml): finished with status 'done'
  Created wheel for isso: filename=isso-0.14.0-py3-none-any.whl size=355734 sha256=753554fe510a73829f372dc56491aaf051da489d36ace8caf6701530d556ead1
  Stored in directory: /root/.cache/pip/wheels/ac/75/88/b0611de29c5e141894c4e22b6437dafd78a67703671dd709bd
Successfully built isso
Installing collected packages: webencodings, typing-extensions, six, pycparser, markupsafe, itsdangerous, werkzeug, mistune, Jinja2, html5lib, cffi, bleach, misaka, isso
  Running setup.py install for misaka: started
  Running setup.py install for misaka: finished with status 'done'
Successfully installed Jinja2-3.1.6 bleach-6.4.0 cffi-2.1.1 html5lib-1.1 isso-0.14.0 itsdangerous-2.2.0 markupsafe-3.0.4 misaka-2.1.1 mistune-3.4.0 pycparser-3.11 six-1.17.0 typing-extensions-4.16.0 webencodings-0.6.1 werkzeug-3.1.9

注意两点:misaka 没有预置 wheel,走的是 setup.py install 源码编译,缺 C 编译器会在这里直接失败;isso 自身是纯 Python wheel(py3-none-any)。

按官方文档建议把可执行文件链接进 PATH(systemd 单元里其实用绝对路径即可,链接是为了手工敲命令方便):

$ ln -sf /opt/isso/bin/isso /usr/local/bin/isso
$ isso --version
isso 0.14.0

四、编写 /etc/isso.cfg

isso --help 显示配置默认路径就是 /etc/isso.cfg。最小可生产配置(实测使用版本):

[general]
dbpath = /var/lib/isso/comments.db
host =
    http://10.89.72.10/
    http://127.0.0.1:8149/
    http://localhost:8149/
notify = stdout
log-file = /var/log/isso.log

[admin]
enabled = true
password = TestPw2026!

[moderation]
enabled = false

[server]
listen = http://0.0.0.0:8080

[guard]
enabled = true
ratelimit = 2

要点(完整参数语义见 《Isso 0.14.0 配置参数详解》):

  • host 是 CORS 白名单,必须逐条列出前端页面的源(协议+主机+端口),浏览器里的嵌入脚本靠它放行;列表第一条同时充当 referer 被屏蔽时的回退源。
  • dbpath 指向 SQLite 单文件,所在目录必须对运行用户可写。
  • [admin] password 是明文存储的管理密码,部署后立即改成强口令并收紧文件权限:chmod 600 /etc/isso.cfg。
  • listen 支持 http://host:port 与 unix:///path.sock 两种形态;生产建议监听 127.0.0.1 或内网地址,由 nginx 反代对外。
  • 安装包内自带一份全量注释版默认配置,可直接抄模板:/opt/isso/lib/python3.10/site-packages/isso/isso.cfg。

五、systemd 服务

5.1 单元文件

官方 Init scripts 一节给出的 systemd 参考(Isso + Gunicorn)适合 uWSGI 场景;单进程内置服务器直接用 run 子命令即可:

# /etc/systemd/system/isso.service
[Unit]
Description=Isso comment server
After=network.target

[Service]
Type=simple
User=isso
ExecStart=/opt/isso/bin/isso -c /etc/isso.cfg run
Restart=on-failure

[Install]
WantedBy=multi-user.target

5.2 首次启动踩坑:log-file 权限

$ systemctl daemon-reload
$ systemctl enable --now isso
Created symlink /etc/systemd/system/multi-user.target.wants/isso.service → /etc/systemd/system/isso.service.
$ systemctl is-active isso
failed

查日志定位:

$ journalctl -u isso -o cat --no-pager | head -14
Started Isso comment server.
2026-11-02 06:35:40,865:INFO: Using configuration file '/etc/isso.cfg'
Traceback (most recent call last):
  File "/opt/isso/bin/isso", line 8, in <module>
    sys.exit(main())
  File "/opt/isso/lib/python3.10/site-packages/isso/__init__.py", line 286, in main
    handler = logging.FileHandler(conf.get("general", "log-file"))
  File "/usr/lib/python3.10/logging/__init__.py", line 1169, in __init__
    StreamHandler.__init__(self, self._open())
  File "/usr/lib/python3.10/logging/__init__.py", line 1201, in _open
    return open_func(self.baseFilename, self.mode,
PermissionError: [Errno 13] Permission denied: '/var/log/isso.log'
isso.service: Main process exited, code=exited, status=1/FAILURE
isso.service: Failed with result 'exit-code'.
isso.service: Scheduled restart job, restart counter is at 1.

原因:User=isso 下进程无权在 /var/log 创建新文件。修复:

$ touch /var/log/isso.log
$ chown isso:isso /var/log/isso.log

另一个连带坑:Restart=on-failure 会在秒级反复拉起失败进程,攒满重启预算后 systemd 拒绝再启动,修复后要先清计数器:

$ systemctl reset-failed isso
$ systemctl start isso
$ systemctl is-active isso
active

5.3 服务状态确认

$ systemctl status isso --no-pager -l
● isso.service - Isso comment server
     Loaded: loaded (/etc/systemd/system/isso.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-11-02 06:35:51 UTC; 3s ago
   Main PID: 4248 (isso)
      Tasks: 1 (limit: 2506)
     Memory: 22.0M
        CPU: 110ms
     CGroup: /system.slice/isso.service
             └─4248 /opt/isso/bin/python3 /opt/isso/bin/isso -c /etc/isso.cfg run

Nov 02 06:35:51 isso-srv systemd[1]: Started Isso comment server.
Nov 02 06:35:51 isso-srv isso[4248]: 2026-11-02 06:35:51,440:INFO: Using configuration file '/etc/isso.cfg'
Nov 02 06:35:51 isso-srv isso[4248]: 2026-11-02 06:35:51,456:INFO: Loading Mistune with plugins: ['strikethrough', 'subscript', 'superscript'] and parameters: ['escape', 'hard_wrap']
Nov 02 06:35:51 isso-srv isso[4248]: 2026-11-02 06:35:51,456:INFO: Using Mistune as Markdown rendering engine

单进程常驻内存约 22MB,与选型篇"轻量"定位一致。监听确认:

$ ss -ltnp | grep 8080
LISTEN 0      128          0.0.0.0:8080       0.0.0.0:*    users:(("isso",pid=4248,fd=4))

六、功能验证

6.1 评论提交与读取

$ curl -sS -X POST "http://127.0.0.1:8080/new?uri=/test/1" \
    -H "Content-Type: application/json" -H "Origin: http://10.89.72.10" \
    -d '{"text":"hello **world**","author":"alice","email":"a@b.c","title":"Test Post"}'
{"id": 1, "parent": null, "created": 1793601366.3152127, "modified": null, "mode": 1, "text": "<p>hello <strong>world</strong></p>", "author": "alice", "website": null, "likes": 0, "dislikes": 0, "notification": null, "hash": "b539304e6ef9"}

三个实测细节:uri 走 URL 查询参数而不是 JSON 体;Markdown **world** 被渲染为 <strong>(Mistune 引擎,启动日志可见插件 strikethrough/subscript/superscript);mode: 1 表示已发布(未开 moderation)。

计数接口注意请求体必须是 URI 字符串数组,写成 {"uri": [...]} 不报错但恒返回 0:

$ curl -sS http://127.0.0.1:8080/count -H "Origin: http://10.89.72.10" \
    -H "Content-Type: application/json" -d '["/test/1","/nonexist"]'
[2, 0]

读取线程:

$ curl -sS "http://127.0.0.1:8080/?uri=/test/1" -H "Origin: http://10.89.72.10"
{"id": null, "total_replies": 1, "hidden_replies": 0, "replies": [{"id": 1, "parent": null, "created": 1793601366.3152127, "modified": null, "mode": 1, "text": "<p>hello <strong>world</strong></p>", "author": "alice", "website": null, "likes": 0, "dislikes": 0, "notification": null, "hash": "b539304e6ef9", "total_replies": 0, "hidden_replies": 0, "replies": []}], "config": {"reply-to-self": false, "require-email": false, "require-author": false, "reply-notifications": false, "gravatar": false, "feed": false}}

6.2 CORS 白名单行为

白名单内的 Origin 原样回显;白名单外的 Origin 响应仍是 200,但 Access-Control-Allow-Origin 指向白名单第一条,浏览器据此拦截跨源读取:

$ curl -sS -D- -o /dev/null "http://127.0.0.1:8080/?uri=/test/1" -H "Origin: http://evil.example" | grep -iE "^HTTP|^access-control-allow-origin"
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://10.89.72.10

排障提示:嵌入页评论加载不出来,先核对页面源是否逐字符出现在 host 列表里(协议、端口、结尾斜杠语义见 C1)。

6.3 防刷(guard)

ratelimit = 2(每 IP /24 段每分钟)下第三次连发被 403 拦截:

$ curl -sS -X POST "http://127.0.0.1:8080/new?uri=/test/1" \
    -H "Content-Type: application/json" -H "Origin: http://10.89.72.10" \
    -d '{"text":"burst 3","author":"bob","email":"b@b.c","title":"Test Post"}' | grep -oE "ratelimit exceeded[^<]*"
ratelimit exceeded (1, 2)

投票同样受同段 IP 限制:

$ curl -sS -X POST http://127.0.0.1:8080/id/1/like -H "Content-Type: application/json" -H "Origin: http://10.89.72.10"
{"likes": 0, "dislikes": 0, "message": "Upvote denied because a vote has already been registered for this remote address: 127.0.0.0"}

127.0.0.0 是 remote_addr 按 /24 匿名化后的记录值,不是笔误。

6.4 内置演示页

服务自带一个联调用页面,无需自建站点即可先验证客户端:

$ curl -sS -o /dev/null -w "/demo/ -> %{http_code}\n" http://127.0.0.1:8080/demo/
/demo/ -> 200

七、前端嵌入

7.1 选对 JS 文件(实测坑)

/js/ 路由下的文件并非都能直接给浏览器用。实测大小:

js/embed.min.js -> 200 size=67146
js/count.min.js -> 200 size=51346
css/isso.css -> 200 size=9479

embed.js(5983 字节)是未打包源码,头部是 require("app/...") 的 CommonJS 调用,浏览器直接加载会报错;生产嵌入必须用 embed.min.js(67146 字节,即官方宣传的"65kB")。只要评论数徽章的页面可以用更小的 count.min.js。

7.2 演示页面

在 nginx 站点根目录放置:

<!doctype html>
<html><head><meta charset="utf-8"><title>Isso 嵌入演示</title></head>
<body>
<h1>Isso 嵌入演示页</h1>
<p>宿主机访问地址 http://127.0.0.1:8149/</p>
<script data-isso="http://10.89.72.10:8080/" src="http://10.89.72.10:8080/js/embed.min.js"></script>
<section id="isso-thread" data-title="Demo Page" data-isso-id="/demo/page1"></section>
</body></html>

验证:

$ systemctl is-active nginx
active
$ curl -sS http://10.89.72.10/ | grep -E "data-isso|isso-thread"
<script data-isso="http://10.89.72.10:8080/" src="http://10.89.72.10:8080/js/embed.min.js"></script>
<section id="isso-thread" data-title="Demo Page" data-isso-id="/demo/page1"></section>

data-isso 指向 Isso 服务源,section#isso-thread 是挂载点;data-isso-id 显式绑定线程 URI(缺省时取页面 location.pathname,与 host 白名单共同决定评论归属)。浏览器打开演示页即可看到提交框并完成发帖。

八、管理后台

登录接口只接受表单编码,不接受 JSON(源码注释明确 "Must use form data, not POST JSON"):

$ curl -sS -i -c /tmp/cookie.txt -X POST http://127.0.0.1:8080/login/ \
    -H "Origin: http://10.89.72.10" -d "password=TestPw2026!" | sed -n "1,7p"
HTTP/1.1 302 FOUND
Server: Werkzeug/3.1.9 Python/3.10.12
Date: Mon, 02 Nov 2026 06:36:27 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 243
Location: http://127.0.0.1:8080/admin/
Set-Cookie: admin-session=eyJsb2dnZWQiOnRydWV9.augvaw.Y-Z6SFKE-WQzgc560I-RMcwu-Gk; Expires=Tue, 03 Nov 2026 06:36:27 GMT; Path=/; SameSite=Lax

拿到 admin-session cookie(有效期 1 天)后访问管理页:

$ curl -sS -b /tmp/cookie.txt "http://127.0.0.1:8080/admin/" -H "Origin: http://10.89.72.10" | grep -oE "<title>[^<]*</title>"
<title>Isso admin</title>

浏览器打开 /admin/ 即为审核管理界面(支持按 page/mode/order_by 过滤排序)。注意:0.14.0 没有生成密码哈希的 CLI 子命令(isso gen-passwd 实测报 invalid choice),[admin] password 只能明文放在配置里,务必用 chmod 600 限制读取,并避免与其他系统口令复用。

九、备份、恢复与升级

9.1 备份

数据面就一个 SQLite 文件加配置。停服务后备份(实测):

$ systemctl stop isso
$ sqlite3 /var/lib/isso/comments.db ".backup /var/lib/isso/comments.backup.db"
$ cp /var/lib/isso/comments.db /var/lib/isso/comments.db.copied
$ systemctl start isso
$ systemctl is-active isso
active
$ sqlite3 /var/lib/isso/comments.db "select count(*) from comments;"   # 备份前基线
5
$ sqlite3 /var/lib/isso/comments.backup.db "select count(*) from comments;"
5

恢复即反向替换文件并 chown isso:isso,再重启服务。.backup 走 SQLite 在线备份 API,比裸 cp 更不易撕裂;若坚持热备份,至少配合 journalctl 校验行数。

库结构一览(sqlite3 /var/lib/isso/comments.db ".tables" → comments preferences threads):comments.mode 取值 1=已发布、2=待审、4=已删除但被引用;表上挂了 remove_stale_threads 触发器,删光某线程全部评论后线程行自动清理。

9.2 升级

$ /opt/isso/bin/pip install --upgrade isso
$ systemctl restart isso

升级前用 9.1 的备份流程留快照;0.x 系列配置项有改名先例(如 server.listen 格式、markup.renderer 默认从 misaka 切到 mistune),跨小版本先通读 《Isso 0.14.0 配置参数详解》 再重启。

十、排障实录与清单

#症状根因处置
1systemctl is-active 显示 failed,日志 PermissionError: '/var/log/isso.log'User=isso 无权在 /var/log 建文件touch + chown isso:isso,见 5.2
2修复后仍拒绝启动:Start request repeated too quicklyRestart=on-failure 攒满重启计数systemctl reset-failed isso 后再 start
3/count 恒返回 [0]请求体写成对象 {"uri":[...]}改为 URI 字符串数组,见 6.1
4嵌入页报 JS 语法错误/评论区空白引了未打包的 embed.js(含 require())换 embed.min.js,见 7.1
5POST /login/ 返回 400 Bad Request用了 JSON 体表单编码 -d "password=...",见八
6白名单外站点能 curl 通但浏览器加载失败CORS 由浏览器强制执行,服务端只控制回显头把页面源补进 host 列表并重启
7连发第三条 403 ratelimit exceeded (1, 2)guard 每分钟限额属预期防刷;压测时临时 enabled = false
8pip 安装阶段 misaka 编译失败缺 C 工具链安装 build-essential python3-dev
9手工 isso run 报 Address already in use / Port 8080 is in usesystemd 实例已占端口手工验证前先 systemctl stop isso
10反代后投票/限流失效或全被拦所有请求源 IP 变成代理 IP配置 [server] trusted-proxies 解析 X-Forwarded-For

十一、小结

至此,一台 Ubuntu 22.04 服务器上拥有了:/opt/isso virtualenv、/etc/isso.cfg、systemd 托管的 isso.service(22MB 常驻、单进程监听 8080)、经 CORS 白名单放行的前端嵌入链路、表单登录的管理后台,以及经实测的备份/升级路径。下一步深入方向:全部配置节参数语义见 《Isso 0.14.0 配置参数详解》;命令行迁移导入(Disqus/WordPress/Generic 三种格式实测)与运维工具见 《Isso 0.14.0 常用 CLI 命令与工具详解》;容器形态对照 《Isso Docker Compose 部署指南》。