Files
home-automation/docs/roadmap.md
T
tliu93 962ba26c7c
frontend / frontend (push) Successful in 1m15s
pytest / test (push) Successful in 1m30s
frontend / frontend (pull_request) Successful in 1m16s
pytest / test (pull_request) Successful in 1m30s
docs(roadmap): add Future Ideas — TOTP 2FA for the public dashboard
Record TOTP (RFC 6238) as a deferred hardening idea for the now public-facing
Web dashboard: second factor on the single-admin login, with CLI-only password
reset and a CLI TOTP reset/recovery path that works even if the recovery codes
are lost (no lock-out dead end). Not M2.5, not scheduled — parked under a new
Future Ideas section.
2026-06-13 15:29:20 +02:00

10 KiB
Raw Blame History

Roadmap

本文档记录 home-automationv1.0.3 之后的下一阶段规划。这一阶段不是小修补,而是几次较大的结构性改动:单库化、前端重写、以及远期的移动端试水。

每个里程碑的可执行原子任务展开在 docs/design/M1 m1-db-consolidation.md、M2 m2-frontend-v2.md、M3 m3-token-mobile.md。这些文档为 Orchestrator→Implementer→Reviewer 的多模型流水线设计。

当前基线(v1.0.3

  • FastAPI + 服务端 Jinja 模板页面(目前只有 /login/config
  • 三个独立 SQLite 库:
    • App DBsqlite:///./data/app.db
    • Location DBsqlite:///./data/locationRecorder.db
    • Poo DBsqlite:///./data/pooRecorder.db
  • 三条独立 Alembic 链:alembic_app/alembic_location/alembic_poo/
  • 单 admin 鉴权(Argon2 + server-side session cookie
  • Public IPv4 monitor、SMTP 通知、Location / Poo recorder、Home Assistant in/out、TickTick OAuth
  • 数据可视化目前由 Grafana provisioning 承担(仅 location / poo dashboard
  • 已有 OpenAPI 导出脚本:scripts/export_openapi.py

本阶段正式退役的架构约束

docs/architecture-overview.md 里有几条当时刻意写死的约束,这一阶段明确退役:

  • “不引入前后端分离” → 退役。本阶段改为 React SPA(仍由 FastAPI 同源托管,但渲染移到客户端)。
  • “三个独立 DB 不合并” → 退役。本阶段把 location / poo 合并进 app.db
  • Grafana 作为可视化方案 → 退役。可视化由 React 前端自己承担(热力图、地图等)。

保持不变的约束:

  • 继续使用 SQLite,本阶段不上 Postgres。
  • 不引入 Notion。

里程碑总览

里程碑 主题 一句话
M1 单库化地基 把三库合并成单一 app.db,清理散落数据层,删掉 Grafana
M2 前端 v2 React SPA 取代 Jinja,承载 config + 可视化 + 记录增删改
M3 开放与移动端(远期试水) token 鉴权 + React Native 移动端

排序原则:先清地基,再在干净结构上盖楼。 M2 的新 API 和 React 必须建立在合并后的单库之上,否则就是在准备推倒的旧数据层上盖新楼、之后回头返工。


M1 — 单库化地基( 已完成)

目标

把 location / poo 两个独立库合并进 app.db,借机清理项目早期散落各处的数据访问代码,并移除 Grafana。

范围

  • Alembic 收敛为单链(app 链)location / poo 的表此后纳入 app 链管理;alembic_location/alembic_poo/ 退出活跃使用(保留在 git 历史)。
  • 新建表(schema only:在 app 链上加一条 upgrade revision,把原来两个旧库里的表原样建到 app.db 中。Alembic 不需要知道任何旧数据——它只负责把 app DB 往上升一个版本、建出这两张新表。
  • 数据搬迁交给独立脚本scripts/migrate_legacy_data.py(见下方“迁移策略”),手动跑一次。
  • 配置层收敛:去掉 LOCATION_DATABASE_URL / POO_DATABASE_URL,统一到 APP_DATABASE_URL
  • 开启 SQLite WAL:单文件 + Web + APScheduler 并发写入,开 WAL 更稳。
  • 删除 Grafana:移除 compose 中的 grafana service、grafana/provisioning/grafana/dashboards/。直接删除,不再 re-point datasource。
  • 更新文档README、architecture-overview 同步反映单库现实。

注意

  • 可视化空窗可接受M1 删掉 Grafana 后、到 M2 React 可视化落地之前会有一段没有可视化面板的时间。已确认可以接受。
  • 历史数据是第一优先级,绝不能丢(见“数据安全原则”)。

迁移策略(M1 核心)

职责拆分得很清楚:Alembic 管 schema,脚本管数据。

Alembic revision(只建结构)

  • 一条 app 链上的 upgrade revision,建出与旧库完全相同的表结构。
  • 确定性、与环境无关:在生产机、CI、全新部署上都一样地建空表,不依赖任何旧文件是否存在。
  • 本步只原样挪表,不顺手改 schema。任何表结构清理留到之后一条单独的 migration 去做——不可替代的历史数据,一次只承担一种风险。

数据搬迁脚本(scripts/migrate_legacy_data.py

  • 把旧 locationRecorder.db / pooRecorder.db 里的行,拷进 app.db 的新表(SQLite ATTACH DATABASE 或单独连接均可)。
  • 幂等:重复运行不会重复插入。
  • 搬完对账:逐表核对源 / 目标行数,对不上就报错中止。
  • 只在生产机上手动跑一次,不进 Alembic 永久链路(避免把一次性历史搬迁焊死进每次全新建库都要跑的链路里)。

旧库的“撤掉”

  • “撤掉旧库” = ① 配置不再指向它们 + ② 文件归档保留
  • 绝不在任何脚本 / migration 里 os.remove 旧文件——那不可逆,且踩数据安全红线。
  • 真正的删除是人工、最后、确认无误之后单独的一步。

数据安全原则

历史数据(location / poo 记录)是这个项目里最不可替代的东西,迁移期间一律按以下原则:

  1. 迁移前先归档.db 文件一份。
  2. 先在副本上演练:把每日备份恢复到一个 scratch 目录,在副本上跑完整迁移、核对行数无误,再对真实库动手。
  3. 脚本幂等 + 行数对账,对不上立即中止。
  4. 旧文件只读归档、绝不自动删除,删除是事后人工动作。

M2 — 前端 v2React SPA 已完成

目标

用 React SPA 取代现有 Jinja 页面,由 FastAPI 同源托管(同一容器、同一 origin)。这一步合并了“前端重写为 React”和“前端做厚”两件原本分开的事——它们本质是同一坨活。

备注:React 是一次 agentic programming 试水。之前只手写过 Vue、没手写过 React,这一轮想全程靠 agent、尽量不读代码地把它做出来。OpenAPI 导出 → 生成类型化 TS client 作为 agent 的契约护栏,正好服务这个目标。

范围

  • React SPA,FastAPI 挂载打包后的静态产物(同源,省掉 CORS)。
  • Config 界面:取代现有 Jinja config 页。
  • 数据可视化:热力图、地图等,接管原先 Grafana 干的事。
  • 按需展示 DB 数据(例如 poo 记录)。
  • 记录的小幅增删改:用于修正不准确的记录。

后端配套

  • 补一套 JSON API:SPA 是客户端渲染,需要后端提供 config 读写、数据查询、记录 CRUD 等 JSON 端点。(同源不等于不需要 API——API 是“客户端怎么拿数据”,与文件托管在哪无关。)
  • 鉴权:浏览器面向的新端点(含记录 CRUD)复用现有 session cookie 保护。
  • 类型化 client:用 scripts/export_openapi.py 的输出生成 TS client。

鉴权边界(与 M3 衔接)

  • 现在那个”裸 API 记小狗日志”的 ingestion 端点(设备 / 脚本调用,非浏览器)维持现状到 M3
  • M2 新增的、浏览器调用的 CRUD 端点,用 session 保护即可,本步不引入 token。

M2 已完成M2-T01 至 M2-T13 全部 done)。Jinja 模板已移除,React SPA 同源托管,多阶段 Docker 构建通过,所有校验闸门绿。


M3 — 开放与移动端(远期试水)

目标

引入 token 鉴权并做一个 React Native 移动端。明确是很远期、低投入的试水——先把 React 前端做出来,之后才会碰移动端,且主要是想试试没做过的 React / React Native。

范围

  • OAuth-lite token 签发:移动端在内置浏览器里用账号密码登录,走一遍类 OAuth 流程,服务端签发一个 bearer token 给 app 存起来使用。(本质是没有第三方的 Authorization Code 简化版。)
  • React Native 移动端:试水性质。
  • 给 ingestion 端点上 token:把 M2 暂时维持裸奔的设备端点收口到 token 鉴权下。

为什么放最后

  • 移动端是这一阶段最远期、最不确定的部分。
  • token 主要是移动端的前置条件;Web 端 React 用现有 session cookie 即可,不需要为它提前引入 token。

Future Ideas(暂不排期,想到先记下)

这里收集还没排进里程碑的想法。不是承诺、也没有先后顺序;想做时再从这里捞出来细化成 docs/design/ 的任务卡。明确不开 M2.5——下列条目一律先躺在 Future Ideas,之后再说。

TOTP 二次验证(Dashboard 加固)

动机M2 之后多了一个 Web Dashboard。它虽有单 admin 密码保护,但大概率会暴露在公网上,只靠密码这一层不够。给登录再叠一层 TOTP(基于时间的一次性密码,RFC 6238) 作为第二因子,做纵深防御。

范围(粗略,待细化)

  • 在现有单 adminArgon2 + server-side session)登录之上,叠加 TOTP 第二步:密码校验通过后再验 6 位动态码,通过才发 session cookie。
  • 首次启用时生成 TOTP secret,给出可导入 Authenticator 的二维码 / 可手输密钥;同时生成一组一次性恢复码(recovery codes

运维 / 命令行要求(关键,实现时必须满足)

  1. 忘记密码:不需要任何 Web 端“找回密码”流程——直接在命令行里重置 admin 密码即可(沿用现有 CLI 思路)。
  2. TOTP 重置 / 恢复:必须提供命令行重置入口。要覆盖最坏情况——连恢复码(restore key)都丢了,也能纯靠 CLI 把 TOTP 关掉 / 重新发放新的 secret,从而恢复登录。即:CLI 是不依赖任何已存恢复凭据的最终逃生通道,不能出现“密钥丢了就彻底锁死”的死角。

先不做:本条仅记入 Future Ideas,不进 M2.5、不排期;之后再细化为 design 任务卡。