Files
home-automation/README.md
T

402 lines
10 KiB
Markdown
Raw Normal View History

2025-07-15 17:44:39 +02:00
# Home Automation Backend
2026-04-20 20:40:04 +02:00
这是当前 `home-automation` 项目的首个 Python 版本。
2026-04-19 20:19:58 +02:00
2026-04-20 20:40:04 +02:00
当前系统已经包含:
2026-04-19 20:19:58 +02:00
2026-04-20 20:40:04 +02:00
- FastAPI Web 应用与服务端模板页面
- SQLite + SQLAlchemy + Alembic 的单库结构
2026-04-20 20:40:04 +02:00
- username/password + server-side session 鉴权
- runtime config 页面与 app DB 持久化
2026-04-29 13:07:59 +02:00
- public IPv4 monitor、历史持久化与定时检查
- SMTP 配置、测试发信与 public IPv4 changed 邮件通知
2026-04-20 20:40:04 +02:00
- location recorder
- poo recorder
- Home Assistant inbound / outbound integration
- TickTick OAuth 与 action task 集成
- pytest 测试与 OpenAPI 导出脚本
- Docker / Compose 部署入口
2026-04-19 20:19:58 +02:00
2026-04-20 20:40:04 +02:00
当前明确不包含:
2026-04-19 20:19:58 +02:00
- Notion 模块
## 当前配置现实
当前系统使用单一 SQLite 数据库文件(`app.db`),所有数据表都在其中:
- auth(单个 admin 用户、server-side session
- runtime config 持久化(`app_config` 表)
- public IPv4 当前状态与变化历史
- location 记录(`location` 表)
- poo 记录(`poo_records` 表)
配置层只保留一个数据库环境变量:
2026-04-20 15:16:47 +02:00
- `APP_DATABASE_URL`
`app.db` 不会在应用启动时自动创建,需要先运行:
2026-04-20 15:16:47 +02:00
```bash
python -m scripts.run_migrations
```
2026-04-20 15:16:47 +02:00
该命令会通过 Alembic 将 `app.db` 初始化或升级到最新 head(含 `location` / `poo_records` 表)。
2026-04-19 20:19:58 +02:00
## 当前目录
2026-04-20 20:40:04 +02:00
主要目录如下:
2026-04-19 20:19:58 +02:00
- `app/`: FastAPI 应用代码
- `alembic_app/`: App DB 的 Alembic migration 环境(同时管理 `location` / `poo_records` 表)
2026-04-19 20:19:58 +02:00
- `tests/`: pytest 测试
2026-04-20 20:40:04 +02:00
- `docs/`: 当前系统说明文档
2026-04-19 20:19:58 +02:00
- `scripts/`: 辅助脚本,例如 OpenAPI 导出
## 依赖管理
项目现在采用 `pip-tools` 管理依赖:
- 生产依赖源文件:`requirements.in`
- 开发依赖源文件:`dev-requirements.in`
- 编译产物:
- `requirements.txt`
- `dev-requirements.txt`
更新依赖时建议使用:
```bash
python -m venv .venv
source .venv/bin/activate
pip install pip-tools
pip-compile requirements.in
pip-compile dev-requirements.in
```
如果要升级某个依赖,可以用:
```bash
pip-compile --upgrade-package fastapi requirements.in
pip-compile dev-requirements.in
```
## 本地启动
建议使用 Python 3.11 或以上版本。
1. 创建虚拟环境并安装依赖
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r dev-requirements.txt
```
2. 准备环境变量
```bash
cp .env.example .env
```
2026-04-20 15:16:47 +02:00
3. 初始化数据库
```bash
2026-04-22 13:28:00 +02:00
python -m scripts.run_migrations
2026-04-20 15:16:47 +02:00
```
4. 启动服务
2026-04-19 20:19:58 +02:00
```bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
启动后可访问:
- 应用首页:`http://localhost:8000/`
- 健康检查:`http://localhost:8000/status`
- Swagger UI`http://localhost:8000/docs`
- ReDoc`http://localhost:8000/redoc`
## 数据库与 Alembic
当前使用单一 SQLite 数据库文件:
2026-04-19 20:19:58 +02:00
2026-04-20 15:16:47 +02:00
- App DB`sqlite:///./data/app.db`
2026-04-19 20:19:58 +02:00
- 数据目录:`./data/`
所有模型(auth / config / public_ip / location / poo)共用同一个 `Base`,均通过单一 Alembic 链管理:
2026-04-19 20:19:58 +02:00
- Alembic 环境:`alembic_app.ini` + `alembic_app/`
2026-04-22 13:28:00 +02:00
- 统一 migration job`python -m scripts.run_migrations`
- App DB 接管 / 初始化:`python scripts/app_db_adopt.py`
历史 location / poo 数据(旧版本遗留的独立 DB 文件)已通过以下脚本一次性迁移至 `app.db`(幂等,不删除旧文件):
```bash
python -m scripts.migrate_legacy_data
```
2026-04-19 20:19:58 +02:00
2026-04-20 15:16:47 +02:00
## 基础鉴权
2026-04-20 20:40:04 +02:00
当前项目提供一个单用户 admin 鉴权层,用于保护配置页面与管理能力。
2026-04-20 15:16:47 +02:00
- 认证模型:`username/password`
- 会话模型:server-side session + cookie
- 当前主要受保护页面:`/config`
- 当前公开页面:`/login`
2026-04-20 15:16:47 +02:00
- 当前公开 API:现有业务 API 暂未在这一轮统一收口到 auth 下
安全实现的当前边界:
2026-04-20 15:26:36 +02:00
- 密码使用 Argon2 做哈希存储
2026-04-20 15:16:47 +02:00
- session cookie 使用 `HttpOnly`
- `Secure` 默认随 `APP_ENV` 切换:非 development 时默认开启
- `SameSite=Lax`
- 登录表单和登出表单都有基础 CSRF 防护
首次启动时,如果 `APP_DATABASE_URL` 对应的 auth DB 里还没有用户,应用会使用:
- `AUTH_BOOTSTRAP_USERNAME`
- `AUTH_BOOTSTRAP_PASSWORD`
创建初始 admin 用户。当前默认就是:
- username: `admin`
- password: `admin`
首次登录后会被要求立即修改密码。这个 bootstrap 只用于首个用户落库,不是后续的完整配置管理方案。
2026-04-20 20:40:04 +02:00
当前前端主要有两条页面路径:
- `/login`
- `/config`
无论是本地 `host:port` 还是反向代理后的域名访问,登录成功后都使用相对路径跳转到 `/config`
## Config 持久化
2026-04-20 20:40:04 +02:00
当前 config 页面不会把修改写回 `.env`
当前原则是:
- `.env` 只负责 bootstrap / fallback
- app 启动先从 `.env` 读取数据库地址等基础配置
- 请求期读取配置时,优先使用 app DB 中的 `app_config`
- 如果数据库里没有对应值,再 fallback 到 `.env`
这意味着:
- app DB 地址(`APP_DATABASE_URL`)仍然属于 bootstrap 范畴
- 运行时可编辑配置主要通过 `app_config` 表持久化
- token / secret 这类运行时必须可取回的配置,目前允许明文存储在 config 表中
- 登录密码仍然单独使用 Argon2 哈希,不走 config 表明文存储
2026-04-29 13:07:59 +02:00
当前已经接入 config 页面的运行时配置包括:
- 基础系统配置
- auth cookie 相关配置
- SMTP 基础配置
- TickTick OAuth 配置
- Home Assistant 配置
其中 SMTP password 与其他 secret 字段一致:
- 页面不明文回显
- 留空提交时保留旧值
- 用于测试发信与自动通知时不会写入响应
## Public IPv4 Monitor
当前系统已经提供最小可用的 public IPv4 monitor
- 使用单一 provider 检查当前公网 IPv4
- 将状态与变化历史持久化到 app DB
- 提供受保护的手动检查入口:`GET /public-ip/check`
- 启动时注册 APScheduler job,默认每 4 小时检查一次
当前 app DB 中与此功能相关的新表:
- `public_ip_state`
- `public_ip_history`
状态语义如下:
- `first_seen`:首次发现当前公网 IPv4
- `unchanged`:与上次状态一致
- `changed`:公网 IPv4 发生变化
- `error`:provider 请求失败或返回无效值
## SMTP 与邮件通知
当前系统已经提供最小可用的 SMTP 能力:
- SMTP 配置可在 `/config` 页面填写并保存到 `app_config`
- 可通过 config 页面发送测试邮件
- 邮件 `From` 头支持显示名,例如 `Home Automation <sender@example.com>`
当前 SMTP 配置项包括:
- `SMTP_ENABLED`
- `SMTP_HOST`
- `SMTP_PORT`
- `SMTP_USERNAME`
- `SMTP_PASSWORD`
- `SMTP_FROM_NAME`
- `SMTP_FROM_ADDRESS`
- `SMTP_TO_ADDRESS`
- `SMTP_USE_STARTTLS`
当前 public IPv4 monitor 已与 SMTP sender 接通,但只处理一个很小的通知场景:
- 当 public IPv4 check 结果为 `changed` 时,自动发送一封英文纯文本邮件
以下情况不会发邮件:
- `first_seen`
- `unchanged`
- `error`
当前通知邮件内容固定,不提供模板系统,正文会包含:
- previous IP
- current IP
- detected time
手动测试时,如果需要再次模拟一次 IP 变化,可以临时修改 `public_ip_state.current_ipv4` 为一个保留测试地址,然后再次调用 `GET /public-ip/check`
2026-04-20 20:40:04 +02:00
## OpenAPI
可使用下面的脚本重新导出当前 API 定义:
```bash
python scripts/export_openapi.py
```
导出结果会写入:
- `openapi/openapi.json`
- `openapi/openapi.yaml`
## Docker Compose
当前默认 Compose 服务名为 `app`,容器名固定为 `home-automation-app`
当前 Compose 分成两层:
- `docker-compose.yml`:默认使用 registry image,适合部署 / 生产拉取
- `docker-compose.override.yml`:仅为本地开发追加 `build: .`
本地开发启动方式:
2026-04-20 20:40:04 +02:00
```bash
docker compose up -d --build
```
上面的命令会自动叠加 `docker-compose.override.yml`,因此本地仍然会按当前工作目录重新 build。
如果要按生产方式直接从 registry 拉取并启动,显式只使用基础 compose 文件:
```bash
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d
```
2026-04-20 20:40:04 +02:00
持续查看日志:
```bash
docker compose logs -f app
```
2026-04-20 22:18:54 +02:00
## Container Image CI
项目提供了一个 release image workflow
- workflow 文件:`.github/workflows/docker-image.yml`
- 触发条件:push 匹配 `v*` 的 tag,例如 `v1.0.0`
- registry`code.wanderingbadger.dev`
2026-04-20 23:05:27 +02:00
- image`code.wanderingbadger.dev/<owner>/<repo>`
`docker-compose.yml` 中生产默认使用的 app image 当前为:
- `code.wanderingbadger.dev/tliu93/home-automation:latest`
2026-04-20 23:05:27 +02:00
当前 workflow 不再把 image name 硬编码到特定 user package 路径,而是直接使用当前仓库标识生成镜像路径:
- `code.wanderingbadger.dev/${github.repository}:${tag}`
在 Gitea 这里,package 更贴近 repo 归属的语义,主要体现在镜像命名路径本身,而不是额外的“绑定”动作。也就是说,当前发布方式是按仓库路径约定来对齐 repo/package 语义。
2026-04-20 22:18:54 +02:00
这个 workflow 会构建并推送 multi-arch image
- `linux/amd64`
- `linux/arm64`
推送的 tag
- release tag 本身,例如 `v1.0.0`
- `latest`
workflow 依赖以下 secrets
- `REGISTRY_USERNAME`
- `REGISTRY_TOKEN`
CI 产出的 image 是给部署机直接 `docker pull` 使用的。部署机不需要 checkout 本仓库,也不需要本地执行 `docker build`
2026-04-19 20:19:58 +02:00
## 运行测试
```bash
pytest
```
当前测试包含:
- app 启动与 `/status` 检查
- 登录 / session / 鉴权流程
- runtime config 读写
- public IPv4 monitor
- SMTP 配置与测试发信
- location / poo recorder 端点
- Home Assistant inbound 集成
- TickTick OAuth
- 部署与迁移(`run_migrations`
- legacy 数据迁移脚本(`migrate_legacy_data`
2026-04-19 20:19:58 +02:00
## OpenAPI 导出
FastAPI 默认会暴露 OpenAPI。若需要导出静态 schema 文件,可运行:
```bash
python scripts/export_openapi.py
```
输出文件会写到:
- `openapi/openapi.json`
- `openapi/openapi.yaml`
2026-04-19 23:25:13 +02:00
`openapi/` 当前纳入版本控制。接口发生变更时,应重新运行导出脚本并同步提交生成的 schema 文件。
2026-04-19 20:19:58 +02:00
## 容器启动
1. 准备环境变量文件
```bash
cp .env.example .env
```
2. 启动容器
```bash
docker compose up --build
```
默认端口:
- `8000:8000`
SQLite 持久化目录:
- 本地 `./data`
- 容器内 `/app/data`