2026-04-19 12:36:55 +02:00
|
|
|
|
# Moving Helper
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-19 12:36:55 +02:00
|
|
|
|
这是一个面向可信家庭内网环境的小型搬家记录工具,当前采用轻量技术栈:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
- FastAPI
|
2026-04-19 12:36:55 +02:00
|
|
|
|
- Jinja2 服务端渲染
|
2026-04-19 12:13:07 +02:00
|
|
|
|
- SQLAlchemy
|
|
|
|
|
|
- SQLite
|
2026-04-19 12:54:25 +02:00
|
|
|
|
- Pillow
|
2026-04-19 12:13:07 +02:00
|
|
|
|
- pytest / FastAPI TestClient
|
|
|
|
|
|
- Docker / Docker Compose
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
项目目标是小而稳、容易继续扩展。它不是企业平台,也不是复杂运维系统,重点是本地开发简单、容器部署稳定、数据持久化清楚、后续几个月后自己回来看也能快速接上。
|
|
|
|
|
|
|
|
|
|
|
|
## 当前已支持
|
|
|
|
|
|
|
|
|
|
|
|
- 固定 3 级结构:`Box -> Item -> SubItem`
|
|
|
|
|
|
- Box / Item / SubItem 基础 CRUD
|
|
|
|
|
|
- Box / Item / SubItem 单图上传、替换、删除、展示
|
|
|
|
|
|
- Box / Item / SubItem 全局搜索
|
|
|
|
|
|
- Docker / Compose 长期运行
|
|
|
|
|
|
- SQLite 数据持久化
|
|
|
|
|
|
- 基础自动化测试
|
2026-04-19 12:36:55 +02:00
|
|
|
|
|
|
|
|
|
|
## 当前数据模型
|
|
|
|
|
|
|
|
|
|
|
|
这个项目不是无限树结构,而是固定最多 3 级:
|
|
|
|
|
|
|
|
|
|
|
|
- `Box`
|
|
|
|
|
|
- `Item`
|
|
|
|
|
|
- `SubItem`
|
|
|
|
|
|
|
|
|
|
|
|
关系如下:
|
|
|
|
|
|
|
|
|
|
|
|
- 一个 `Box` 包含多个 `Item`
|
|
|
|
|
|
- 一个 `Item` 属于一个 `Box`
|
|
|
|
|
|
- `Item` 通过 `is_container` 区分是否为“小容器”
|
|
|
|
|
|
- 只有 `is_container = true` 的 `Item` 才允许拥有 `SubItem`
|
|
|
|
|
|
- `SubItem` 是最后一级,不允许继续向下嵌套
|
|
|
|
|
|
|
2026-04-19 12:54:25 +02:00
|
|
|
|
结构固定为:
|
2026-04-19 12:36:55 +02:00
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
Box
|
|
|
|
|
|
└── Item
|
|
|
|
|
|
└── SubItem
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 12:54:25 +02:00
|
|
|
|
## 图片能力说明
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
图片系统保持简单直接:
|
2026-04-19 12:54:25 +02:00
|
|
|
|
|
|
|
|
|
|
- `Box` 最多支持 1 张图片
|
|
|
|
|
|
- `Item` 最多支持 1 张图片
|
|
|
|
|
|
- `SubItem` 最多支持 1 张图片
|
|
|
|
|
|
- 支持上传、替换、删除
|
|
|
|
|
|
- 不支持多图
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
图片主要用于帮助识别物品、提高浏览效率、方便手机拍照后直接附加到记录中。它不是原图归档系统。
|
2026-04-19 12:54:25 +02:00
|
|
|
|
|
|
|
|
|
|
上传图片后,系统会使用 Pillow 做统一处理:
|
|
|
|
|
|
|
|
|
|
|
|
- 读取上传图片
|
|
|
|
|
|
- 去除 EXIF 元数据
|
|
|
|
|
|
- 转换为 JPEG
|
|
|
|
|
|
- 按最长边缩放到不超过 `1600px`
|
|
|
|
|
|
- 使用约 `80` 质量保存
|
|
|
|
|
|
- 将处理后的 JPEG 二进制直接写入 SQLite `BLOB`
|
|
|
|
|
|
|
|
|
|
|
|
同时还会记录:
|
|
|
|
|
|
|
|
|
|
|
|
- `image_mime_type`
|
|
|
|
|
|
- `image_width`
|
|
|
|
|
|
- `image_height`
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
图片访问路由例如:
|
2026-04-19 12:54:25 +02:00
|
|
|
|
|
|
|
|
|
|
- `/boxes/{id}/image`
|
|
|
|
|
|
- `/items/{id}/image`
|
|
|
|
|
|
- `/subitems/{id}/image`
|
|
|
|
|
|
|
2026-04-19 13:00:11 +02:00
|
|
|
|
## 全局搜索
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
当前支持一个轻量的全局搜索页:
|
2026-04-19 13:00:11 +02:00
|
|
|
|
|
|
|
|
|
|
- 路由:`/search`
|
2026-04-19 13:33:43 +02:00
|
|
|
|
- 方式:`GET /search?q=关键词`
|
2026-04-19 13:00:11 +02:00
|
|
|
|
|
|
|
|
|
|
搜索范围包括:
|
|
|
|
|
|
|
|
|
|
|
|
- `Box.name`
|
|
|
|
|
|
- `Box.note`
|
|
|
|
|
|
- `Item.name`
|
|
|
|
|
|
- `Item.note`
|
|
|
|
|
|
- `SubItem.name`
|
|
|
|
|
|
- `SubItem.note`
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
当前使用 SQLite 上的简单模糊匹配,不引入外部搜索引擎或复杂全文系统。
|
2026-04-19 13:00:11 +02:00
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
搜索结果会显示:
|
2026-04-19 13:00:11 +02:00
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
- 对象类型:`Box / Item / SubItem`
|
|
|
|
|
|
- 名称和备注
|
|
|
|
|
|
- 归属路径
|
2026-04-19 13:00:11 +02:00
|
|
|
|
- 对 `Item` 展示所属 `Box`
|
|
|
|
|
|
- 对 `SubItem` 展示所属 `Item` 和 `Box`
|
|
|
|
|
|
- 如果对象已有图片,会显示一个小缩略图
|
|
|
|
|
|
|
2026-04-19 12:36:55 +02:00
|
|
|
|
## 当前未实现
|
|
|
|
|
|
|
|
|
|
|
|
这一阶段仍然没有实现以下内容:
|
|
|
|
|
|
|
2026-04-19 12:54:25 +02:00
|
|
|
|
- 多图上传
|
|
|
|
|
|
- OCR
|
|
|
|
|
|
- AI 识别物品
|
|
|
|
|
|
- 图片标签
|
|
|
|
|
|
- 图片分类
|
2026-04-19 12:36:55 +02:00
|
|
|
|
- 登录 / 鉴权
|
|
|
|
|
|
- 标签系统
|
|
|
|
|
|
- 前后端分离
|
|
|
|
|
|
- 复杂 UI
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
## 项目结构
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
.
|
|
|
|
|
|
├── app
|
|
|
|
|
|
│ ├── __init__.py
|
|
|
|
|
|
│ ├── config.py
|
|
|
|
|
|
│ ├── db.py
|
2026-04-19 12:54:25 +02:00
|
|
|
|
│ ├── images.py
|
2026-04-19 12:13:07 +02:00
|
|
|
|
│ ├── main.py
|
|
|
|
|
|
│ ├── models.py
|
|
|
|
|
|
│ ├── static
|
|
|
|
|
|
│ │ └── style.css
|
|
|
|
|
|
│ └── templates
|
|
|
|
|
|
├── data
|
2026-04-19 13:33:43 +02:00
|
|
|
|
├── scripts
|
|
|
|
|
|
│ ├── backup_db.sh
|
|
|
|
|
|
│ └── deploy.sh
|
2026-04-21 22:39:47 +02:00
|
|
|
|
│ ├── install.sh
|
|
|
|
|
|
│ └── nginx
|
|
|
|
|
|
│ └── moving-helper.nginx.template
|
2026-04-19 12:13:07 +02:00
|
|
|
|
├── tests
|
2026-04-19 13:33:43 +02:00
|
|
|
|
├── .dockerignore
|
|
|
|
|
|
├── .env.example
|
2026-04-19 12:13:07 +02:00
|
|
|
|
├── docker-compose.yml
|
|
|
|
|
|
├── Dockerfile
|
2026-04-19 12:36:55 +02:00
|
|
|
|
├── pytest.ini
|
2026-04-19 12:13:07 +02:00
|
|
|
|
├── README.md
|
2026-04-19 12:36:55 +02:00
|
|
|
|
└── requirements.txt
|
2026-04-19 12:13:07 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 轻量配置
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
项目通过环境变量支持以下部署时真正需要关心的配置:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
- `HOST_DOMAIN`
|
|
|
|
|
|
- `SSL_PATH`
|
|
|
|
|
|
- `APP_DIR`
|
|
|
|
|
|
- `BACKUP_DIR`
|
|
|
|
|
|
- `BACKUP_REMOTE`
|
|
|
|
|
|
- `APP_PORT`
|
2026-04-19 13:33:43 +02:00
|
|
|
|
- `DATA_DIR`
|
2026-04-21 22:39:47 +02:00
|
|
|
|
- `DATABASE_URL`
|
|
|
|
|
|
- `COMPOSE_PROJECT_NAME`
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
|
|
|
|
|
推荐从示例文件开始:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cp .env.example .env
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
默认值如下:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
```env
|
2026-04-21 22:39:47 +02:00
|
|
|
|
HOST_DOMAIN=moving-helper.lan
|
|
|
|
|
|
SSL_PATH=/etc/acme.sh/$HOST_DOMAIN
|
|
|
|
|
|
APP_DIR=$HOME/.local/share/moving-helper
|
|
|
|
|
|
BACKUP_DIR=$HOME/.local/backup/moving-helper
|
|
|
|
|
|
BACKUP_REMOTE=
|
|
|
|
|
|
APP_PORT=10000
|
2026-04-19 13:33:43 +02:00
|
|
|
|
DATABASE_URL=sqlite:////app/data/app.db
|
|
|
|
|
|
DATA_DIR=./data
|
2026-04-21 22:39:47 +02:00
|
|
|
|
COMPOSE_PROJECT_NAME=moving-helper
|
2026-04-19 13:33:43 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
- 容器内应用固定监听 `0.0.0.0:10000`
|
|
|
|
|
|
- `APP_PORT` 只控制宿主机暴露端口,nginx 默认反代到这个端口
|
|
|
|
|
|
- `APP_DIR` 是安装脚本复制 compose、`.env`、备份脚本等运行资产的目标目录
|
|
|
|
|
|
- `DATA_DIR` 默认为相对路径 `./data`,安装后会相对于 `APP_DIR` 解析
|
|
|
|
|
|
- `SSL_PATH` 由用户自行准备证书目录,安装脚本不会签发证书
|
|
|
|
|
|
- `.env` 会被 shell 脚本直接 `source`,请保持 shell 兼容写法
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
## 本地开发模式
|
|
|
|
|
|
|
|
|
|
|
|
推荐使用本地 Python `venv` 开发和调试。
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 创建虚拟环境
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python3 -m venv .venv
|
|
|
|
|
|
source .venv/bin/activate
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 安装依赖
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pip install -r requirements.txt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 启动开发服务器
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
uvicorn app.main:app --reload --host 0.0.0.0 --port 10000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
访问:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
http://localhost:10000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 12:36:55 +02:00
|
|
|
|
本地默认数据库位置:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
./data/app.db
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
## Docker 运行方式
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
Docker / Compose 是这个项目面向长期运行环境的方式。
|
|
|
|
|
|
|
2026-04-21 22:01:23 +02:00
|
|
|
|
当前 compose 同时保留了:
|
|
|
|
|
|
|
|
|
|
|
|
- `image`:固定指向 `code.wanderingbadger.dev/tliu93/2026-moving-helper:latest`
|
|
|
|
|
|
- `build`:用于本地开发时从当前代码构建镜像
|
|
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
当前部署约定已经收敛为:
|
|
|
|
|
|
|
|
|
|
|
|
- 容器内应用固定监听 `0.0.0.0:10000`
|
|
|
|
|
|
- compose 固定使用 `user: 1000:1000`
|
|
|
|
|
|
- 宿主机仅在 `127.0.0.1:${APP_PORT}` 暴露后端端口
|
|
|
|
|
|
- SQLite 固定写入容器内 `/app/data/app.db`
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
### 首次准备
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-19 13:33:43 +02:00
|
|
|
|
cp .env.example .env
|
|
|
|
|
|
mkdir -p data
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:01:23 +02:00
|
|
|
|
### 启动 / 更新:本地代码构建
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose up -d --build
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:01:23 +02:00
|
|
|
|
这个模式会使用当前仓库代码重新构建镜像,适合本地开发、调试或尚未发布 tag 的阶段。
|
|
|
|
|
|
|
|
|
|
|
|
### 启动 / 更新:直接拉取已发布镜像
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose pull
|
|
|
|
|
|
docker compose up -d
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
### 查看状态
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose ps
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 查看日志
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose logs -f web
|
2026-04-19 12:13:07 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
访问:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
http://localhost:10000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
### Compose 配置说明
|
|
|
|
|
|
|
|
|
|
|
|
当前 `docker-compose.yml` 保持尽量简单:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
- 固定镜像地址为 `code.wanderingbadger.dev/tliu93/2026-moving-helper:latest`
|
|
|
|
|
|
- 宿主机默认仅在 `127.0.0.1:10000` 暴露容器 `10000`
|
2026-04-19 12:36:55 +02:00
|
|
|
|
- `restart: unless-stopped`
|
2026-04-21 22:39:47 +02:00
|
|
|
|
- 容器固定使用 `1000:1000`
|
2026-04-19 13:33:43 +02:00
|
|
|
|
- 宿主机 `DATA_DIR` 挂载到容器内 `/app/data`
|
|
|
|
|
|
- SQLite 默认写入 `/app/data/app.db`
|
|
|
|
|
|
|
2026-04-21 22:01:23 +02:00
|
|
|
|
因此同一个 compose 文件可以覆盖两种使用方式:
|
|
|
|
|
|
|
|
|
|
|
|
- 本地开发容器:`docker compose up -d --build`
|
|
|
|
|
|
- 远端部署发布镜像:`docker compose pull && docker compose up -d`
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
## 自动化部署
|
|
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
这个项目现在额外提供一个面向本地网络环境的最小安装脚本:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
sh scripts/install.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
安装脚本会执行:
|
|
|
|
|
|
|
|
|
|
|
|
1. 检查项目根目录下是否存在 `.env`
|
|
|
|
|
|
2. 读取 `.env`
|
|
|
|
|
|
3. 把 `docker-compose.yml`、`.env` 和渲染后的 `backup_db.sh` 复制到 `APP_DIR`
|
|
|
|
|
|
4. 用 `HOST_DOMAIN`、`SSL_PATH`、`APP_PORT` 渲染 nginx 配置
|
|
|
|
|
|
5. 写入 `/etc/nginx/sites-available/moving-helper-nginx`
|
|
|
|
|
|
6. 创建到 `/etc/nginx/sites-enabled/` 的符号链接
|
|
|
|
|
|
7. 执行 `nginx -t` 并 reload nginx
|
|
|
|
|
|
8. 在 `APP_DIR` 下执行 `docker compose pull` 和 `docker compose up -d`
|
|
|
|
|
|
9. 为当前用户写入每日 `02:10` 的 backup cron
|
|
|
|
|
|
|
|
|
|
|
|
其中以下步骤需要 root 或 sudo:
|
|
|
|
|
|
|
|
|
|
|
|
- 写入 nginx 配置
|
|
|
|
|
|
- 执行 `nginx -t`
|
|
|
|
|
|
- reload nginx
|
|
|
|
|
|
|
|
|
|
|
|
如果 `.env` 不存在,脚本会直接退出,不会继续做任何安装动作。
|
|
|
|
|
|
|
|
|
|
|
|
如果你只想在仓库目录里做一次手动更新,也保留了一个轻量部署脚本:
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
./scripts/deploy.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
它会按顺序执行:
|
|
|
|
|
|
|
|
|
|
|
|
1. 检查 `.env`
|
|
|
|
|
|
2. 准备数据目录
|
|
|
|
|
|
3. 如果当前目录是 git 仓库,执行 `git pull --ff-only`
|
2026-04-21 22:39:47 +02:00
|
|
|
|
4. 执行 `docker compose pull web`
|
|
|
|
|
|
5. 执行 `docker compose up -d`
|
2026-04-19 13:33:43 +02:00
|
|
|
|
5. 输出容器状态
|
|
|
|
|
|
6. 输出最近日志
|
|
|
|
|
|
|
|
|
|
|
|
这个脚本的目标不是做平台化发布,而是让“更新代码并刷新容器”变成一个稳定、可重复执行的动作。
|
|
|
|
|
|
|
|
|
|
|
|
如果你不想自动拉代码,也可以直接手动运行:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-21 22:39:47 +02:00
|
|
|
|
docker compose pull
|
|
|
|
|
|
docker compose up -d
|
2026-04-19 13:33:43 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 数据持久化
|
|
|
|
|
|
|
|
|
|
|
|
当前 SQLite 文件默认会保存在:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
- 宿主机:`./data/app.db`
|
|
|
|
|
|
- 容器内:`/app/data/app.db`
|
|
|
|
|
|
|
|
|
|
|
|
这是因为 `docker-compose.yml` 把:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
|
|
|
|
|
```text
|
2026-04-19 13:33:43 +02:00
|
|
|
|
${DATA_DIR:-./data}
|
2026-04-19 12:13:07 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
挂载到了容器内:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
/app/data
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
因此:
|
|
|
|
|
|
|
|
|
|
|
|
- 容器重建不会删除宿主机上的数据库文件
|
|
|
|
|
|
- 更新镜像不会导致 SQLite 数据丢失
|
|
|
|
|
|
- 只要保留 `DATA_DIR` 目录,数据就还在
|
|
|
|
|
|
|
|
|
|
|
|
## 备份与恢复
|
|
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
### 备份机制
|
|
|
|
|
|
|
|
|
|
|
|
安装脚本会把渲染后的备份脚本安装到:
|
|
|
|
|
|
|
|
|
|
|
|
- `APP_DIR/backup_db.sh`
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
并为当前用户创建一条 cron:
|
|
|
|
|
|
|
|
|
|
|
|
- `10 2 * * *`
|
|
|
|
|
|
|
|
|
|
|
|
备份行为如下:
|
|
|
|
|
|
|
|
|
|
|
|
- 目标目录是 `BACKUP_DIR`
|
|
|
|
|
|
- 备份文件名带时间戳,例如 `app-20260421-021000.db`
|
|
|
|
|
|
- 最多保留 5 个本地备份
|
|
|
|
|
|
- 如果 `BACKUP_REMOTE` 非空,会在本地备份完成后调用 `rclone copyto`
|
|
|
|
|
|
|
|
|
|
|
|
SQLite 一致性策略:
|
|
|
|
|
|
|
|
|
|
|
|
- 备份脚本优先使用 `sqlite3` 的 `.backup`
|
|
|
|
|
|
- 不停容器
|
|
|
|
|
|
- 不直接 `cp` 正在写入的数据库文件
|
|
|
|
|
|
|
|
|
|
|
|
这样可以在应用仍然运行时生成事务一致的快照,避免简单文件复制带来的损坏风险。
|
|
|
|
|
|
|
|
|
|
|
|
### 手动执行备份
|
|
|
|
|
|
|
|
|
|
|
|
如果你想手动触发一次备份:
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-21 22:39:47 +02:00
|
|
|
|
sh "$APP_DIR/backup_db.sh"
|
2026-04-19 13:33:43 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
如果当前还没有执行安装脚本,也可以在仓库内手动准备 `.env` 后运行:
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
./scripts/backup_db.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
前提是先通过安装脚本把它渲染并部署到 `APP_DIR`,因为仓库内版本本身是带占位符的模板。
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
### 恢复大致步骤
|
2026-04-19 13:33:43 +02:00
|
|
|
|
|
|
|
|
|
|
停止容器后,把备份文件覆盖回去:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-21 22:39:47 +02:00
|
|
|
|
cd "$APP_DIR"
|
2026-04-19 13:33:43 +02:00
|
|
|
|
docker compose stop
|
2026-04-21 22:39:47 +02:00
|
|
|
|
cp "$BACKUP_DIR/app-YYYYMMDD-HHMMSS.db" "${DATA_DIR:-./data}/app.db"
|
2026-04-19 13:33:43 +02:00
|
|
|
|
docker compose up -d
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:39:47 +02:00
|
|
|
|
如果 `DATA_DIR` 是相对路径,记得在 `APP_DIR` 下执行这些命令。
|
|
|
|
|
|
|
|
|
|
|
|
### nginx 与证书约定
|
|
|
|
|
|
|
|
|
|
|
|
仓库提供的 nginx 模板位于:
|
|
|
|
|
|
|
|
|
|
|
|
- `scripts/nginx/moving-helper.nginx.template`
|
|
|
|
|
|
|
|
|
|
|
|
安装脚本会把它渲染成 Debian / Ubuntu 风格的站点配置:
|
|
|
|
|
|
|
|
|
|
|
|
- `/etc/nginx/sites-available/moving-helper-nginx`
|
|
|
|
|
|
- `/etc/nginx/sites-enabled/moving-helper-nginx`
|
|
|
|
|
|
|
|
|
|
|
|
模板约定:
|
|
|
|
|
|
|
|
|
|
|
|
- 80 端口强制跳转到 443
|
|
|
|
|
|
- 443 默认启用 SSL
|
|
|
|
|
|
- 反代到仅绑定在本机回环地址上的 `127.0.0.1:${APP_PORT}`
|
|
|
|
|
|
- `client_max_body_size 0`
|
|
|
|
|
|
|
|
|
|
|
|
证书文件需要由用户自己准备在 `SSL_PATH` 下,当前模板默认引用:
|
|
|
|
|
|
|
|
|
|
|
|
- `fullchain.pem`
|
|
|
|
|
|
- `privkey.key`
|
|
|
|
|
|
|
2026-04-19 13:33:43 +02:00
|
|
|
|
## 常见排查
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 查看容器日志
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose logs -f web
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 确认服务是否已启动
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose ps
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果状态是 `Up`,通常说明容器已经跑起来了。
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 确认端口映射
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker compose port web 10000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 确认数据库文件还在
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
ls -lh data
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果看到 `app.db`,说明宿主机持久化文件还在。
|
|
|
|
|
|
|
|
|
|
|
|
### 5. 容器更新后数据为什么没丢
|
|
|
|
|
|
|
2026-04-21 21:49:16 +02:00
|
|
|
|
因为数据库不放在镜像里,而是放在宿主机挂载目录 `DATA_DIR` 中。
|
2026-04-19 13:33:43 +02:00
|
|
|
|
镜像更新只会替换应用代码和运行环境,不会覆盖这个宿主机目录。
|
|
|
|
|
|
|
2026-04-19 12:13:07 +02:00
|
|
|
|
## 测试
|
|
|
|
|
|
|
|
|
|
|
|
运行测试:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-19 12:36:55 +02:00
|
|
|
|
python -m pytest
|
2026-04-19 12:13:07 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 12:36:55 +02:00
|
|
|
|
测试使用独立测试数据库,不会污染真实开发数据。
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-19 12:36:55 +02:00
|
|
|
|
当前测试覆盖包括:
|
2026-04-19 12:13:07 +02:00
|
|
|
|
|
2026-04-19 12:36:55 +02:00
|
|
|
|
- Box / Item / SubItem 基础 CRUD
|
2026-04-19 13:33:43 +02:00
|
|
|
|
- 图片上传、替换、删除与错误路径
|
|
|
|
|
|
- 全局搜索 name / note
|
|
|
|
|
|
- 创建后的重定向行为
|
|
|
|
|
|
- 关键页面结构和 UX 文案
|
2026-04-19 14:28:00 +02:00
|
|
|
|
|
2026-04-21 21:49:16 +02:00
|
|
|
|
## CI / CD
|
|
|
|
|
|
|
|
|
|
|
|
仓库现在包含两条基础自动化流程,文件位于:
|
|
|
|
|
|
|
|
|
|
|
|
- `.github/workflows/test.yml`
|
|
|
|
|
|
- `.github/workflows/docker-image.yml`
|
|
|
|
|
|
|
|
|
|
|
|
### CI:branch push 自动跑 pytest
|
|
|
|
|
|
|
|
|
|
|
|
`test.yml` 会在任意 branch 的 `push` 上执行:
|
|
|
|
|
|
|
|
|
|
|
|
1. checkout 代码
|
|
|
|
|
|
2. 使用 Python `3.12`
|
|
|
|
|
|
3. 安装 `requirements.txt`
|
|
|
|
|
|
4. 运行 `pytest`
|
|
|
|
|
|
|
|
|
|
|
|
当前测试不依赖外部数据库服务。
|
|
|
|
|
|
|
|
|
|
|
|
测试使用 `tmp_path` 创建独立 SQLite 文件,并通过 `configure_database(...)` 切换到临时数据库,因此:
|
|
|
|
|
|
|
|
|
|
|
|
- 不会污染 `./data/app.db`
|
|
|
|
|
|
- 不要求额外启动 Docker 或 Compose
|
|
|
|
|
|
- 不要求额外配置测试环境变量
|
|
|
|
|
|
|
|
|
|
|
|
### CD:tag 发布 Docker image
|
|
|
|
|
|
|
|
|
|
|
|
`docker-image.yml` 会在推送符合 `v*` 格式的 tag 时触发,例如:
|
|
|
|
|
|
|
|
|
|
|
|
- `v1.0.0`
|
|
|
|
|
|
- `v1.2.3`
|
|
|
|
|
|
|
|
|
|
|
|
workflow 会先校验该 tag 指向的提交是否可从 `origin/main` 到达;只有满足这个条件的 tag 才会继续构建并推送镜像。
|
|
|
|
|
|
|
|
|
|
|
|
镜像发布目标:
|
|
|
|
|
|
|
|
|
|
|
|
- Registry Host: `code.wanderingbadger.dev`
|
|
|
|
|
|
- Image Name: `${{ github.repository }}`
|
|
|
|
|
|
- Platforms:
|
|
|
|
|
|
- `linux/amd64`
|
|
|
|
|
|
- `linux/arm64`
|
|
|
|
|
|
|
|
|
|
|
|
推送的 tag 策略:
|
|
|
|
|
|
|
|
|
|
|
|
- `${tag}`
|
|
|
|
|
|
- `latest`
|
|
|
|
|
|
|
|
|
|
|
|
例如仓库名为 `tliu93/2026-moving-helper`,打出 `v1.0.0` 后会推送:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
code.wanderingbadger.dev/tliu93/2026-moving-helper:v1.0.0
|
|
|
|
|
|
code.wanderingbadger.dev/tliu93/2026-moving-helper:latest
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Actions / Gitea Secrets
|
|
|
|
|
|
|
|
|
|
|
|
需要在仓库的 Actions secrets 中配置:
|
|
|
|
|
|
|
|
|
|
|
|
- `REGISTRY_USERNAME`
|
|
|
|
|
|
- `REGISTRY_TOKEN`
|
|
|
|
|
|
|
|
|
|
|
|
推荐含义:
|
|
|
|
|
|
|
|
|
|
|
|
- `REGISTRY_USERNAME`: Gitea 用户名
|
|
|
|
|
|
- `REGISTRY_TOKEN`: 具备 Container Registry 推送权限的 Access Token
|
|
|
|
|
|
|
|
|
|
|
|
如果你的 Gitea 实例对 package / registry 权限做了单独控制,确保这个 token 至少具备对应仓库的镜像推送权限。
|
|
|
|
|
|
|
|
|
|
|
|
### 如何触发镜像发布
|
|
|
|
|
|
|
|
|
|
|
|
建议流程:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git checkout main
|
|
|
|
|
|
git pull --ff-only origin main
|
|
|
|
|
|
git tag v1.0.0
|
|
|
|
|
|
git push origin main --tags
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果只想推送单个 tag:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git push origin v1.0.0
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 本地手动构建镜像
|
|
|
|
|
|
|
|
|
|
|
|
单架构本地构建:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker build -t moving-helper:local .
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
本地运行:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker run --rm -p 10000:10000 \
|
|
|
|
|
|
-e DATABASE_URL=sqlite:////app/data/app.db \
|
|
|
|
|
|
moving-helper:local
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果要模拟发布时的多架构构建,可以使用 buildx:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker buildx build \
|
|
|
|
|
|
--platform linux/amd64,linux/arm64 \
|
|
|
|
|
|
-t code.wanderingbadger.dev/${USER}/2026-moving-helper:test \
|
|
|
|
|
|
--load \
|
|
|
|
|
|
.
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-19 14:28:00 +02:00
|
|
|
|
## 一次性 Notion 导入
|
|
|
|
|
|
|
|
|
|
|
|
项目内附带了一个一次性迁移脚本:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python scripts/import_notion.py --dry-run
|
|
|
|
|
|
python scripts/import_notion.py --apply
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 这是一次性 migration / import 工具,不是长期同步功能
|
|
|
|
|
|
- 运行时会交互要求输入:
|
|
|
|
|
|
- Notion API token
|
|
|
|
|
|
- Notion 页面完整 URL
|
|
|
|
|
|
- `--dry-run` 只读取和解析,不写数据库
|
|
|
|
|
|
- `--apply` 会真正写入当前 SQLite 数据库
|
|
|
|
|
|
- 建议导入前先备份 `data/app.db`
|
|
|
|
|
|
|
|
|
|
|
|
### 当前支持的 Notion 结构映射
|
|
|
|
|
|
|
|
|
|
|
|
- `heading_2` -> `Box`
|
|
|
|
|
|
- 某个 `heading_2` 下的一级 bullet -> `Item`
|
|
|
|
|
|
- 如果一级 bullet 下还有二级 bullet:
|
|
|
|
|
|
- 一级 bullet -> 容器型 `Item`
|
|
|
|
|
|
- 二级 bullet -> `SubItem`
|
|
|
|
|
|
|
|
|
|
|
|
当前最大只处理到这个层级:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
heading_2
|
|
|
|
|
|
└── 一级 bullet
|
|
|
|
|
|
└── 二级 bullet
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
更深层级会在日志中提示,但不会继续扩展成无限树。
|
|
|
|
|
|
|
|
|
|
|
|
### 这一版不导入图片
|
|
|
|
|
|
|
|
|
|
|
|
这一版导入脚本:
|
|
|
|
|
|
|
|
|
|
|
|
- 不下载图片
|
|
|
|
|
|
- 不导入图片
|
|
|
|
|
|
- 遇到图片或其他媒体 block 时会提示已跳过
|
|
|
|
|
|
|
|
|
|
|
|
图片后续可以在应用里手动补录。
|