二进制及自命令命名统一简化

This commit is contained in:
2026-09-08 17:01:09 +08:00
parent 6bfe20ee00
commit edfc787e1a
18 changed files with 105 additions and 105 deletions
+25 -25
View File
@@ -129,7 +129,7 @@
│ HTTPS (Caddy 自动 TLS, 反代, 追加密名 XFF)
▼
┌──────────────────────────┐
│ pure-note 单一二进制 (Go) │
│ pn 单一二进制 (Go) │
│ │
│ net/http + ServeMux │
│ ├─ 公共 API /api/* │──┐
@@ -145,7 +145,7 @@
│ database/sql (SetMaxOpenConns=1)
▼
┌──────────────────────────┐
│ data/pure-note.db (SQLite)│
│ data/pn.db (SQLite) │
│ notes(含 deleted_at) / │
│ images(BLOB) / image_refs /│
│ sessions / settings │
@@ -176,8 +176,8 @@ pure-note/
│ ├── design.md # 本文档(v1.1)
│ └── review-round1.md # 第 1 轮审评报告(修订依据,留档)
├── cmd/
│ └── pure-note/
│ └── main.go # 入口 + 命令行(serve/init/reset-password/backup/gc/version)
│ └── pn/
│ └── main.go # 入口 + 命令行(start/init/passwd/backup/gc/version)
├── internal/
│ ├── config/ # 环境变量/flag 解析
│ ├── store/ # SQLite 打开、迁移(user_version 上界校验)、DAO
@@ -200,8 +200,8 @@ pure-note/
│ ├── features/ # notes / admin 业务 hooks
│ └── pages/ # Home / Note / Tags / AdminLogin / AdminList / AdminTrash / AdminEdit
└── deploy/
├── pure-note.service # systemd 服务单元
├── pure-note-maint.timer # 每日 gc + 备份 timer
├── pn.service # systemd 服务单元
├── pn-maint.timer # 每日 gc + 备份 timer
└── Caddyfile # 反代示例
```
@@ -213,7 +213,7 @@ pure-note/
```sql
-- 连接串(DSN)统一注入以下 pragma:
-- file:data/pure-note.db?_pragma=journal_mode(WAL)
-- file:data/pn.db?_pragma=journal_mode(WAL)
-- &_pragma=busy_timeout(5000)
-- &_pragma=foreign_keys(1)
-- &_pragma=synchronous(NORMAL)
@@ -281,7 +281,7 @@ CREATE TABLE IF NOT EXISTS settings (
| 迁移 | `PRAGMA user_version` 版本化,顺序执行内嵌迁移脚本;**仅追加式**(禁止删列/重命名/改类型);启动时校验 `user_version ≤ 代码支持的最高版本`,超出拒绝启动(`--allow-newer` 显式放行,§10.4) |
| 并发 | `db.SetMaxOpenConns(1)` — 单写者串行化,WAL 下个人规模足够;规模上来后按「多读者连接池」扩展 |
### 6.3 数据清理(`pure-note gc`)
### 6.3 数据清理(`pn gc`)
默认 `--dry-run` 只输出清理计划,`--commit` 才执行;systemd timer 每日调用一次(§10.3):
@@ -348,14 +348,14 @@ SecurityHeaders(含 HSTS,§9.2)
### 7.3 认证与会话
1. **初始化**:`pure-note init`(交互式设密码,或环境变量 `PN_ADMIN_PASSWORD` 非交互)→ Argon2id(m=19456, t=2, p=1, salt=16B 随机, keyLen=32B) **以 PHC 串 `$argon2id$v=19$m=19456,t=2,p=1$<b64salt>$<b64hash>` 存库**——参数随哈希走,未来调参可继续校验旧口令;
1. **初始化**:`pn init`(交互式设密码,或环境变量 `PN_ADMIN_PASSWORD` 非交互)→ Argon2id(m=19456, t=2, p=1, salt=16B 随机, keyLen=32B) **以 PHC 串 `$argon2id$v=19$m=19456,t=2,p=1$<b64salt>$<b64hash>` 存库**——参数随哈希走,未来调参可继续校验旧口令;
2. **登录**:Origin/Referer→Host 校验通过后,per-IP 10 次/5 分钟 + 账号维度 5 次/10 分钟令牌桶(只计失败尝试);校验用 `subtle.ConstantTimeCompare`;失败统一返回 401「用户名或密码错误」不泄露差异;
3. **会话 Cookie**:`__Host-pn_session` = 256bit 随机 token;属性 `Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=7d`(`__Host-` 前缀自带 Secure + 根路径约束);
4. **CSRF**:会话行内存随机 `csrf_token`,登录响应下发、前端仅存内存并附到所有 `/api/admin/*` 变更请求的 `X-CSRF-Token` 头;**刷新页面后经 `GET /api/me` 重取**;明确禁止任何 localStorage 持久化;服务端同时校验 Origin/Referer 与 Host 匹配;
5. **轮换**:活跃访问距过期 < 3 天时重建会话行、旋转 Cookie token,**csrf_token 保持不变**(前端无感);
6. **登出**:删除会话行;
7. **改密**:`POST /api/admin/password` 校验旧密码后写新 PHC 哈希;改密不失效当前会话(可选:改密后删除其他会话——单管理员场景仅本会话,不强制);
8. **口令重置(CLI)**:`pure-note reset-password` 免旧口令直接覆盖哈希并**吊销全部会话**——前提是具备服务器访问权限(读写数据目录即可改库,CLI 提供规范入口优于手工改库);口令来源与 `init` 一致,避免出现在 argv;
8. **口令重置(CLI)**:`pn passwd` 免旧口令直接覆盖哈希并**吊销全部会话**——前提是具备服务器访问权限(读写数据目录即可改库,CLI 提供规范入口优于手工改库);口令来源与 `init` 一致,避免出现在 argv;
9. **开发模式**:`--dev` 允许非 Secure Cookie(否则 `__Host-` 在 `http://局域网IP` 下无法登录);**强制约束:仅当监听地址为 loopback 时允许启动,防止误部署到生产**。
### 7.4 图片上传与访问
@@ -376,12 +376,12 @@ SecurityHeaders(含 HSTS,§9.2)
| 子命令 / 开关 | 说明 |
| --- | --- |
| `pure-note serve` | 启动;`--addr :8080`、`--data-dir`(默认 `./data`)、`--log-level`、`--log-format text|json`、`--behind-proxy`(声明位于可信反代之后,启用 XFF 处理)、`--dev`(loopback-only,§7.3-8)、`--allow-newer`(跳过 schema 版本上界校验,§10.4);环境变量:`PN_ADMIN_PASSWORD` |
| `pure-note init` | 首次初始化:设口令(Argon2id+PHC)、站点标题 |
| `pure-note reset-password` | 重设管理员口令(覆盖旧哈希)并吊销全部会话;口令来源同 `init`(环境变量 `PN_ADMIN_PASSWORD` 或交互输入,不经 argv 避免 ps 泄露)。CLI 可达即具备服务器权限,允许直接重设 |
| `pure-note backup [path]` | 在线备份:`VACUUM INTO`(一致快照,不停服);默认输出 0600 权限 |
| `pure-note gc` | 回收站过期清除 + 孤儿图片清除 + 会话清理;**默认 `--dry-run`**,`--commit` 才执行(§6.3) |
| `pure-note version` | 版本号(build 时注入 commit/时间) |
| `pn start` | 启动;`--addr :8080`、`--data-dir`(默认 `./data`)、`--log-level`、`--log-format text|json`、`--behind-proxy`(声明位于可信反代之后,启用 XFF 处理)、`--dev`(loopback-only,§7.3-8)、`--allow-newer`(跳过 schema 版本上界校验,§10.4);环境变量:`PN_ADMIN_PASSWORD` |
| `pn init` | 首次初始化:设口令(Argon2id+PHC)、站点标题 |
| `pn passwd` | 重设管理员口令(覆盖旧哈希)并吊销全部会话;口令来源同 `init`(环境变量 `PN_ADMIN_PASSWORD` 或交互输入,不经 argv 避免 ps 泄露)。CLI 可达即具备服务器权限,允许直接重设 |
| `pn backup [path]` | 在线备份:`VACUUM INTO`(一致快照,不停服);默认输出 0600 权限 |
| `pn gc` | 回收站过期清除 + 孤儿图片清除 + 会话清理;**默认 `--dry-run`**,`--commit` 才执行(§6.3) |
| `pn version` | 版本号(build 时注入 commit/时间);亦支持 `-v` / `--version` |
---
@@ -522,20 +522,20 @@ sync-assets:
build: web sync-assets
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \
-o pure-note ./cmd/pure-note
-o pn ./cmd/pn
linux: web sync-assets
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
-ldflags="-s -w" -o pure-note-linux-amd64 ./cmd/pure-note
-ldflags="-s -w" -o pn-linux-amd64 ./cmd/pn
dev: # 双端热重载:air(Go)+ vite dev(proxy /api → 127.0.0.1:8080)+ serve --dev
dev: # 双端热重载:air(Go)+ vite dev(proxy /api → 127.0.0.1:8080)+ start --dev
air & cd web && npm run dev
```
### 10.2 运行
```ini
# deploy/pure-note.service
# deploy/pn.service
[Unit]
Description=Pure Note
After=network.target
@@ -543,7 +543,7 @@ After=network.target
[Service]
User=purenote
WorkingDirectory=/opt/pure-note
ExecStart=/opt/pure-note/pure-note serve --addr 127.0.0.1:8080 \
ExecStart=/opt/pure-note/pn start --addr 127.0.0.1:8080 \
--data-dir /opt/pure-note/data --behind-proxy
Restart=on-failure
NoNewPrivileges=true
@@ -571,7 +571,7 @@ example.com {
**调度(systemd timer,替代 root cron)**
```ini
# deploy/pure-note-maint.timer
# deploy/pn-maint.timer
[Unit]
Description=Daily Pure Note maintenance (gc + backup)
@@ -583,7 +583,7 @@ Persistent=true
WantedBy=timers.target
```
timer 触发 `pure-note-maint.service`:先 `pure-note gc --commit`,后 `pure-note backup /backup/pure-note-$(date +\%F).db`。
timer 触发 `pn-maint.service`:先 `pn gc --commit`,后 `pn backup /backup/pn-$(date +\%F).db`。
**要点**
@@ -591,11 +591,11 @@ timer 触发 `pure-note-maint.service`:先 `pure-note gc --commit`,后 `pure
- `/backup` 目录属主 `purenote`、权限 0700;备份文件**chmod 0600**(内含全部私有笔记、图片、口令哈希、会话哈希的明文副本);
- 保留 30 天;**语义声明**:备份保留期内被删除的数据物理上仍存在于备份中;
- 异地同步(可选):`rclone` 远端启用 **crypt**(或先 `age` 加密再上传),禁止明文上云;
- **恢复 SOP**:停服 → 删除 `data/pure-note.db-wal` 与 `-shm`(残留会污染还原)→ 以备份文件替换 `pure-note.db` → 起服。
- **恢复 SOP**:停服 → 删除 `data/pn.db-wal` 与 `-shm`(残留会污染还原)→ 以备份文件替换 `pn.db` → 起服。
### 10.4 升级
1. **升级前第一步固定为 `pure-note backup`**(写入 SOP);
1. **升级前第一步固定为 `pn backup`**(写入 SOP);
2. 停服 → 替换二进制 → 起服触发迁移:迁移仅追加式(不删列/重命名/改类型),`user_version` 顺序递增;
3. **启动守卫**:代码校验 `user_version ≤ 本版本支持的最高版本`,超出**拒绝启动**并提示,`--allow-newer` 显式放行(防止旧二进制静默在陌生 schema 上读写);
4. 回滚:若迁移尚未执行(版本一致)可直接换回旧二进制;若已执行且不满足追加式约束,回滚 = 从步骤 1 的备份还原数据文件后再换回旧二进制。