# Cents

家庭共享账本 App。

目标是做一个精简、可靠、可离线使用的手机应用，支持 Android 和 iOS。用户在无网络时也能记账，网络恢复后自动同步到家庭共享账本。

当前阶段：产品边界和技术设计。

- [产品边界](docs/产品边界.md)
- [技术方案](docs/技术方案.md)
- [数据与同步](docs/数据与同步.md)
- [账号与权限](docs/账号与权限.md)
- [测试策略](docs/测试策略.md)
- [内网穿透调试](docs/内网穿透调试.md)
- [HTML 原型图](docs/原型图/原型说明.md)
- [评审问题](docs/评审问题.md)

## 本地开发

```bash
npm install
npm run dev
```

`npm run dev` 会同时启动 API 和前端。前端运行在 `http://localhost:6064/`，并与 `compose.dev.yaml` 的 frp 调试入口保持一致。

常用检查：

```bash
npm run typecheck
npm run build
```

## 生产环境启动

在生产环境 clone 目录中更新代码。首次启动时从示例创建 `.env`；如果已经存在 `.env`，不要覆盖：

```bash
git pull
test -f .env || cp .env.example .env
```

编辑 `.env`，确认域名、frp 服务地址和数据库配置正确，并为 `POSTGRES_PASSWORD` 设置高强度密码：

```dotenv
DOMAIN=cents.homemade.net.cn
APP_VARIANT=pro
FRPC_SERVER_HOST=119.28.12.24
FRPC_SERVER_PORT=7000
TEST_MODE=false
POSTGRES_DB=cents
POSTGRES_USER=cents
POSTGRES_PASSWORD=replace-with-a-strong-password
```

构建并启动生产环境：

```bash
docker compose --env-file .env -f compose.pro.yaml up -d --build
```

检查容器状态和日志：

```bash
docker compose --env-file .env -f compose.pro.yaml ps
docker compose --env-file .env -f compose.pro.yaml logs -f api web frpc cert
```

后续更新同样执行 `git pull` 和 `up -d --build`，数据库卷会保留。停止环境时不要添加 `-v`，否则会删除生产数据库卷：

```bash
docker compose --env-file .env -f compose.pro.yaml down
```

## 生产环境添加用户

生产环境通过 Docker Compose 在 API 容器内运行 npm 脚本，宿主机无需安装 Node.js 或 npm：

```bash
docker compose --env-file .env -f compose.pro.yaml exec api \
  npm run user:add -- '用户名' '至少8位的密码'
```

用户与唯一的“个人账本”会在同一个数据库事务内创建。

## 生产数据库备份

`backup` 容器每天北京时间 `02:00` 使用 PostgreSQL custom format 执行一次逻辑备份，写入生产 clone 目录下的 `./backups/db`。备份先写入临时文件，能够读取归档目录后再改为正式文件；dump 权限为 `0600`，超过 30 天自动删除。

当前应用的业务数据全部存放在 PostgreSQL 中，因此不备份运行中的 Docker volume。证书可以重新签发，代码由 Git 保存。若将来增加上传文件等数据库外的持久化数据，需要再单独备份对应目录。

备份成功后会更新 `./backups/.last-success`。容器启动后的前 26 小时是健康检查宽限期，此后若定时器未运行，或超过 30 小时没有完整成功的备份，`backup` 容器会变为 unhealthy。

`.env` 不随数据库 dump 备份，其中的部署参数应另行保存在密码管理器等安全位置。

### 远程备份（可选）

远程服务器地址配置在生产部署使用的 `.env` 中。配置后，每次本地备份成功都会通过 SCP 上传本次新 dump；未配置 `BACKUP_SSH_HOST` 时只保留本地备份。

```text
BACKUP_SSH_HOST=backup.example.com   # 置空则禁用远程备份
BACKUP_SSH_USER=backup               # 建议使用只管理备份目录的非 root 用户
BACKUP_SSH_PORT=22
BACKUP_SSH_DIR=/home/backup/cents    # 必须是非根绝对路径，不能包含空格
```

SCP 使用生产宿主机当前用户的 `~/.ssh`，该目录以只读方式挂载到容器。建议为备份创建专用 SSH 密钥，并通过 `~/.ssh/config` 为备份主机指定该密钥。身份验证强制使用 SSH key，禁止密码登录。

首次启动备份容器前，先在宿主机手动连接一次，以核对并记录主机指纹：

```bash
ssh -p 22 backup@backup.example.com true
```

远端文件先以隐藏的临时文件上传，完成后再改为正式名称。远端 dump 同样保留 30 天；不会镜像删除本地目录中的其他内容。

查看调度和备份日志：

```bash
docker compose --env-file .env -f compose.pro.yaml logs -f backup
```

立即手动备份一次：

```bash
docker compose --env-file .env -f compose.pro.yaml exec backup \
  /usr/local/bin/cents-backup run
```

备份文件位于宿主机：

```text
./backups/db/cents-YYYYMMDD-HHMMSS.dump
```

建议在首次部署及之后偶尔将最新 dump 恢复到临时数据库，确认备份实际可用：

```bash
docker compose --env-file .env -f compose.pro.yaml exec db \
  createdb -U cents cents_restore
docker compose --env-file .env -f compose.pro.yaml exec backup \
  pg_restore --dbname=cents_restore /backups/db/cents-YYYYMMDD-HHMMSS.dump
docker compose --env-file .env -f compose.pro.yaml exec db \
  dropdb -U cents cents_restore
```
