Repository navigation
Releases: aichy126/knockbox
Release list
v1.1.1
v1.1.0
v1.0.3
后台现在有一套完整的 JSON 接口,并修掉三处「点了没反应」的缺陷。界面本身没有变化,仍是服务端直出的那几页。
数据库没有新迁移,从 v1.0.x 升上来直接换二进制或换镜像即可。
修复
- 注销别人的设备点了没反应。 成员详情页上那颗按钮此前只对自己的设备有效,而且跳转去一条从未注册过的路由(404)。同时它是硬删除,而 app 里的「注销」走的是软登出——硬删会让那台设备的历史投递记录在后台显示成空的设备名,也让「这台设备已登出,请重新配对」这句提示失效。现在两条路径一致,都是软登出。
- 打开别人的消息落到「这条消息不存在」。 消息搜索页列出全服务器的消息,详情页却只认自己的,于是从列表点进去是一条死链。
- 写操作失败时界面看不出来。 配额豁免这类动作把数据库错误吞掉了,于是「出错了」「这个成员不存在」「真的改成功了」三种结果长得一模一样。
变化
- 概览改成全站口径。 此前所有统计都按当前登录的管理员过滤,在公共实例上会让首页第一个数字恒显示 0,而服务器实际推了几万条。推送成功率尤其如此——它是这台服务器与 APNs 之间连接的健康度,不是某个人的属性。
- 推送成功率在一条都没推过时显示「—」而不是 0%。 「成功率 0%」和「还没推过」是两回事。
新增
/admin/api/*:后台自用的 JSON 接口,17 个端点。 它不是公开契约——/api/v1才是(发送,以及 iOS 客户端用的那些)。/admin/api/下的一切只服务本仓库自带的管理后台,与它共用会话 cookie,后台改版它就跟着改。要用脚本操作这台服务器,请走/api/v1或命令行。- 错误提示新增九条分类,按请求方的
Accept-Language返回中文或英文,同时带上稳定的data.error_code。
The admin now has a complete JSON API, and three "clicked it, nothing happened" defects are fixed. The interface itself is unchanged — it is still the same server-rendered pages.
No new database migrations. Upgrading from any v1.0.x is a straight binary or image swap.
Fixed
- Revoking someone else's device did nothing. The button on the member detail page only ever worked on your own devices, and it redirected to a route that was never registered (404). It was also a hard delete, while the app's own "sign out" is a soft logout — a hard delete leaves that device's delivery history showing a blank device name in the admin, and silences the "this device was signed out, pair it again" message. Both paths now do the same soft logout.
- Opening someone else's message landed on "this message does not exist". The message search lists every message on the server, but the detail page only accepted your own, so following a link from the list was a dead end.
- A failed write looked exactly like a successful one. Actions such as the quota exemption swallowed database errors, so "it failed", "no such member" and "it worked" were indistinguishable.
Changed
- The overview now counts the whole server. Every statistic used to be filtered by the logged-in admin, which on a public instance pins the first number on the homepage to 0 while the server has in fact pushed thousands. This matters most for the push success rate: it describes the health of this server's connection to APNs, not one person's traffic.
- The push success rate shows "—" instead of 0% when nothing has been pushed. "0% delivered" and "nothing sent yet" are different statements.
Added
/admin/api/*— a JSON API for the admin interface, 17 endpoints. It is not a public contract./api/v1is: sending, and the endpoints the iOS app uses. Everything under/admin/api/exists to serve the admin interface that ships in this repository, shares its session cookie, and will change shape whenever that interface does. To script this server, use/api/v1or the CLI.- Nine new error classifications, rendered in Chinese or English according to the caller's
Accept-Language, each carrying a stabledata.error_code.
v1.0.2
A security-shaped cleanup: SQL statements are no longer assembled at runtime.
Statements are constants again. CodeQL flagged four go/sql-injection paths. None
was exploitable — every fragment concatenated into a statement was a literal and every
value went through ?. But the alerts were fair: xorm's Exec/QueryString take
(...any) with the statement as one element of the slice, so once the statement and
user input share a slice, neither a static analyser nor a reader can tell them apart
without following twenty lines of building logic.
The three admin queries now pass the statement separately, and the channel update is
one constant statement with COALESCE(?, column) instead of a SET clause built from
whichever fields were present — which is what that API meant all along.
Fixed while testing it: ChannelInput accepts mute_until, and channel creation
never read it. The value was silently dropped while the caller assumed it was set.
Three new tests pin the partial-update semantics: updating one field leaves the others
alone, an empty string means clear this rather than leave it, and mute_until: 0
cancels a scheduled mute.
Upgrading: pull the new image — no schema changes, no configuration changes.
Images: ghcr.io/aichy126/knockbox:v1.0.2 · aichy66/knockbox:v1.0.2
v1.0.1
Errors a person reads now come back in their own language, with a stable code
alongside.
Errors are localised. Anything a user reads — a pairing code that expired, a
device that was signed out, an attachment link gone stale, a reply that arrived too
late — is rendered from the request's Accept-Language and carries a stable
identifier:
{"code":1,"msg":"This pairing code has expired. Generate a new one.",
"data":{"error_code":"pair.expired"}}Existing clients need no change: they read msg as before and now get it in the right
language. A client can instead read error_code and phrase it itself.
Adding a language is one file. Translations live in
internal/api/web/locales/*.json, embedded at build time. Copy en.json, name it
after the language, translate, open a pull request — no Go involved. Untranslated keys
fall back to English, so a half-finished translation is still worth sending.
The CLI, logs and configuration errors are English. They are read by whoever runs
the server, and everything around them already was.
Upgrading: pull the new image, that is all — no schema changes, no configuration
changes.
Images: ghcr.io/aichy126/knockbox:v1.0.1 · aichy66/knockbox:v1.0.1
v1.0.0
Knockbox is a self-hosted push notification server for iOS. Send a message with one
curl, get it on your phone. One Go binary, one SQLite file, pushed straight to
Apple — no relay in between and no account to sign up for.
Getting started
docker run -d --name knockbox -p 8080:8080 -v knockbox-data:/app/data \
-e IGO_SERVER_EXTERNAL_URL=https://push.example.com \
ghcr.io/aichy126/knockbox:v1.0.0
docker logs knockboxThe first start creates the database and an admin account, and prints that account's
password once. Sign in, open the pairing page, and scan the QR code with the iOS app.
Set IGO_SERVER_EXTERNAL_URL to the address your phone will actually reach — it goes
into the pairing links and the attachment URLs.
What it does
- Sending accepts four content types, and every scalar can also come from the
query string, socurlworks however you write it. Text, Markdown, links and cards. - A message can ask a question — buttons, a multi-select, a number or a text
field. The answer is POSTed to a URL you choose. - An MCP endpoint with the same rights as
/send, so an agent can knock when it
has finished something. - A web admin interface for members, channels, devices, message search and
settings.
Before you deploy
Two things are deliberate, and both are explained in full in the README:
- There is no end-to-end encryption, and the server stores history in plaintext. There
is no relay, so messages only ever touch your own server — which also has to read
them to build push summaries. - A topic-specific APNs key ships in this repository, so you can self-host without
a $99/year Apple developer account. It can only push to the App Store build of the
Knockbox app and nothing else.
SECURITY.md has the
honest version of what that costs you.
Downloads
Images: ghcr.io/aichy126/knockbox:v1.0.0 and aichy66/knockbox:v1.0.0, built for
linux/amd64 and linux/arm64. Binaries below for Linux, macOS, Windows and FreeBSD.
The iOS client is on the App Store.
中文说明见 README.zh-CN.md。