diff --git a/.env.example b/.env.example index e3bf924..2540edc 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,9 @@ -# ---------- 数据库(docker-compose 自带的 MySQL) ---------- -MYSQL_PASSWORD=change-me -MYSQL_ROOT_PASSWORD=change-me-too -# 使用外部 MySQL 时,在 docker-compose.yml 里改 DATABASE_URL: -# DATABASE_URL=mysql+pymysql://user:pass@192.168.1.10:3306/rosdpi?charset=utf8mb4 +# ROS-DPI 全部可用环境变量(参考用) +# docker-compose.yml 已经写好了常用项;需要其它项时,加到 compose 的 environment 下即可。 + +# ---------- 数据库 ---------- +# 库不存在时会自动创建(账号需有建库权限),表结构由程序自动建立,无需手动导入 +DATABASE_URL=mysql+pymysql://rosdpi:password@mysql:3306/rosdpi?charset=utf8mb4 # ---------- 基本 ---------- TZ=Asia/Shanghai diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 40df172..0dc9f4e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -38,8 +38,24 @@ jobs: contents: read packages: write env: - HAS_DOCKERHUB: ${{ secrets.DOCKERHUB_TOKEN != '' }} + # 用户名不是敏感信息,放在 Secrets 或 Variables 里都可以 + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME || vars.DOCKERHUB_USERNAME }} + HAS_DOCKERHUB: ${{ secrets.DOCKERHUB_TOKEN != '' && (secrets.DOCKERHUB_USERNAME != '' || vars.DOCKERHUB_USERNAME != '') }} steps: + # 只报告各项是否存在及长度,不输出内容,用于排查 Docker Hub 没推送的问题 + - name: Check Docker Hub settings + env: + S_USER: ${{ secrets.DOCKERHUB_USERNAME }} + S_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + V_USER: ${{ vars.DOCKERHUB_USERNAME }} + V_TOKEN: ${{ vars.DOCKERHUB_TOKEN }} + run: | + for n in S_USER S_TOKEN V_USER V_TOKEN; do v="${!n}"; echo "$n: ${#v} chars"; done + echo "HAS_DOCKERHUB=$HAS_DOCKERHUB" + if [ "$HAS_DOCKERHUB" != "true" ]; then + echo "::warning::Docker Hub 未启用:在 Settings → Secrets and variables → Actions → Secrets 添加 DOCKERHUB_TOKEN 与 DOCKERHUB_USERNAME" + fi + - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 @@ -56,7 +72,7 @@ jobs: if: env.HAS_DOCKERHUB == 'true' uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} + username: ${{ env.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Image names @@ -97,7 +113,7 @@ jobs: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME || vars.DOCKERHUB_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DOCKERHUB_REPO: suyijun8182/ros-dpi DOCKERHUB_SHORT: "RouterOS per-device traffic & app (DPI) statistics via IPFIX + DNS. RouterOS 终端流量与应用统计。" diff --git a/CHANGELOG.md b/CHANGELOG.md index d6993b6..d018db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [0.1.1] - 2026-10-22 + +debug1 + +- README 与 Docker Hub 页面直接贴出完整的 `docker-compose.yml`;compose 改为自包含,不再需要 `.env` 文件。 +- 新增“首次使用”说明:首次打开网页只需设置管理员密码,MySQL 无需手动配置;补充使用已有 MySQL、修改密码、忘记管理员密码的方法。 +- 使用外部 MySQL 时,数据库不存在会自动创建(需要账号有建库权限)。 +- compose 默认镜像改为 Docker Hub(`suyijun8182/ros-dpi`),GHCR 同步发布。 + ## [0.1.0] - 2026-10-21 首个版本。 diff --git a/DOCKERHUB.md b/DOCKERHUB.md index d396d67..13e6606 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -8,6 +8,7 @@ Source & docs: https://github.com/suyijun8182/ros-dpi ## What's new +- **0.1.1**: full `docker-compose.yml` on this page (no `.env` needed), first-run notes, database auto-created on external MySQL. - **0.1.0**: first release. Overview, hosts, applications, flow Sankey, custom rules, setup guide. zh/en, light/dark. ## Features @@ -18,14 +19,66 @@ Source & docs: https://github.com/suyijun8182/ros-dpi ## Quick start -```bash -curl -O https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.yml -curl -o .env https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/.env.example -# edit .env (MySQL passwords, LAN_CIDRS) -docker compose up -d +Save as `docker-compose.yml`, change the two database passwords (they must match) and `LAN_CIDRS`, then `docker compose up -d`: + +```yaml +# ROS-DPI:RouterOS 终端流量与应用统计 +# 用法:保存为 docker-compose.yml,改掉标了「必改」的三处,然后 docker compose up -d +# 浏览器打开 http://<本机IP>:8080,首次打开设置管理员密码即可,数据库无需手动配置 +services: + ros-dpi: + image: suyijun8182/ros-dpi:latest # 也可用 ghcr.io/suyijun8182/ros-dpi:latest + container_name: ros-dpi + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + ports: + - "8080:8080/tcp" # Web 界面 + - "2055:2055/udp" # IPFIX / NetFlow:RouterOS Traffic Flow 发到这里 + - "37008:37008/udp" # TZSP:RouterOS 镜像的 DNS 应答发到这里 + environment: + TZ: Asia/Shanghai + # 「必改」密码与下方 MYSQL_PASSWORD 保持一致 + DATABASE_URL: mysql+pymysql://rosdpi:rosdpi-change-me@mysql:3306/rosdpi?charset=utf8mb4 + # 「必改」内网网段,逗号分隔(之后也可在 系统设置 → 网络 里改) + LAN_CIDRS: 192.168.88.0/24 + # 管理员 admin 的初始密码;留空则首次打开网页时设置 + ADMIN_PASSWORD: "" + # RouterOS 只读 API(可选:显示主机名、WAN 速率) + ROS_URL: "" # 例如 https://192.168.88.1 + ROS_USER: "" + ROS_PASSWORD: "" + ROS_VERIFY_TLS: "false" + volumes: + - ./data:/app/data + + mysql: + image: mysql:8.4 + container_name: ros-dpi-mysql + restart: unless-stopped + environment: + TZ: Asia/Shanghai + MYSQL_DATABASE: rosdpi # 首次启动自动创建库和账号,表由 ROS-DPI 自动建立 + MYSQL_USER: rosdpi + MYSQL_PASSWORD: rosdpi-change-me # 「必改」与上方 DATABASE_URL 中的密码一致 + MYSQL_ROOT_PASSWORD: root-change-me # 「必改」 + command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci", "--innodb-buffer-pool-size=256M"] + volumes: + - ./mysql:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD"] + interval: 10s + timeout: 5s + retries: 20 ``` -Open `http://:8080`. RouterOS commands are shown in **Settings → Setup guide**. +## First run / 首次使用 + +- Open `http://:8080` and set the admin password. 首次打开网页设置管理员密码即可。 +- **No MySQL setup needed.** The MySQL container creates the database and user on first start; ROS-DPI creates its tables (and the database, on an external MySQL). 无需手动配置数据库或导入 SQL。 +- First MySQL initialization takes 20–60 s. 首次初始化需要 20~60 秒。 +- RouterOS commands: **Settings → Setup guide** in the app. RouterOS 配置命令见 系统设置 → 接入向导。 ## Ports diff --git a/README.md b/README.md index 3e834e4..52f3638 100644 --- a/README.md +++ b/README.md @@ -41,17 +41,83 @@ RouterOS ◀─REST 只读(可选)── 主机名、IP↔MAC、WAN 口速 ### 部署 +**1. 新建目录,保存下面的内容为 `docker-compose.yml`**(也可以直接下载:`curl -O https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.yml`) + +```yaml +# ROS-DPI:RouterOS 终端流量与应用统计 +# 用法:保存为 docker-compose.yml,改掉标了「必改」的三处,然后 docker compose up -d +# 浏览器打开 http://<本机IP>:8080,首次打开设置管理员密码即可,数据库无需手动配置 +services: + ros-dpi: + image: suyijun8182/ros-dpi:latest # 也可用 ghcr.io/suyijun8182/ros-dpi:latest + container_name: ros-dpi + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + ports: + - "8080:8080/tcp" # Web 界面 + - "2055:2055/udp" # IPFIX / NetFlow:RouterOS Traffic Flow 发到这里 + - "37008:37008/udp" # TZSP:RouterOS 镜像的 DNS 应答发到这里 + environment: + TZ: Asia/Shanghai + # 「必改」密码与下方 MYSQL_PASSWORD 保持一致 + DATABASE_URL: mysql+pymysql://rosdpi:rosdpi-change-me@mysql:3306/rosdpi?charset=utf8mb4 + # 「必改」内网网段,逗号分隔(之后也可在 系统设置 → 网络 里改) + LAN_CIDRS: 192.168.88.0/24 + # 管理员 admin 的初始密码;留空则首次打开网页时设置 + ADMIN_PASSWORD: "" + # RouterOS 只读 API(可选:显示主机名、WAN 速率) + ROS_URL: "" # 例如 https://192.168.88.1 + ROS_USER: "" + ROS_PASSWORD: "" + ROS_VERIFY_TLS: "false" + volumes: + - ./data:/app/data + + mysql: + image: mysql:8.4 + container_name: ros-dpi-mysql + restart: unless-stopped + environment: + TZ: Asia/Shanghai + MYSQL_DATABASE: rosdpi # 首次启动自动创建库和账号,表由 ROS-DPI 自动建立 + MYSQL_USER: rosdpi + MYSQL_PASSWORD: rosdpi-change-me # 「必改」与上方 DATABASE_URL 中的密码一致 + MYSQL_ROOT_PASSWORD: root-change-me # 「必改」 + command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci", "--innodb-buffer-pool-size=256M"] + volumes: + - ./mysql:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD"] + interval: 10s + timeout: 5s + retries: 20 +``` + +**2. 改三处「必改」**:两处数据库密码(`DATABASE_URL` 里的和 `MYSQL_PASSWORD` 要一致)、`LAN_CIDRS` 改成你的内网网段。 + +**3. 启动** + ```bash -mkdir ros-dpi && cd ros-dpi -curl -O https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.yml -curl -o .env https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/.env.example -# 编辑 .env:修改 MYSQL_PASSWORD / MYSQL_ROOT_PASSWORD,填写 LAN_CIDRS docker compose up -d ``` -打开 `http://<主机IP>:8080`,第一次访问时设置管理员密码。 +镜像同时发布在 Docker Hub(`suyijun8182/ros-dpi`)和 GHCR(`ghcr.io/suyijun8182/ros-dpi`),支持 amd64 / arm64。群晖、飞牛、威联通等 NAS 可以在 Docker / Container Manager 里新建“项目”,把上面的内容粘贴进去即可。 -镜像:`ghcr.io/suyijun8182/ros-dpi`(amd64 / arm64)。已有 MySQL 时,删掉 compose 里的 `mysql` 服务,把 `DATABASE_URL` 指向你的数据库即可。 +### 首次使用 + +1. 浏览器打开 `http://<主机IP>:8080`。 +2. 第一次打开会显示 **“首次使用:设置管理员”**,设置 admin 的密码后自动登录。如果在 compose 里填了 `ADMIN_PASSWORD`,就直接用 `admin` + 该密码登录。 +3. 到 **系统设置 → 接入向导** 复制 RouterOS 命令,在路由器终端执行(见下一节)。大约 1 分钟后概览页开始有数据。 + +**需要手动配置 MySQL 吗?不需要。** + +- 用上面的 compose:MySQL 容器第一次启动时会按 `MYSQL_DATABASE / MYSQL_USER / MYSQL_PASSWORD` 自动建好库和账号;ROS-DPI 启动时会自动建表。不用导入 SQL,网页里也没有数据库设置步骤。 +- MySQL 第一次初始化需要 20~60 秒,ROS-DPI 会等它就绪再启动。期间打不开网页属于正常现象,可以用 `docker logs -f ros-dpi` 查看进度。 +- 用已有的 MySQL:删掉 compose 里的 `mysql` 服务和 `depends_on`,把 `DATABASE_URL` 改成你的数据库地址。库不存在时会自动创建(账号需要有建库权限),否则请先执行 `CREATE DATABASE rosdpi CHARACTER SET utf8mb4;`。 +- 改密码要趁早:MySQL 数据目录(`./mysql`)一旦初始化,再改 compose 里的密码不会生效。如果确实要改,需要先删掉 `./mysql` 目录重新初始化(会清空统计数据)。 +- 忘记管理员密码:执行 `docker exec ros-dpi-mysql mysql -urosdpi -p<数据库密码> rosdpi -e "DELETE FROM users"`,然后刷新网页重新设置。统计数据不受影响。 ### RouterOS 配置 @@ -77,7 +143,7 @@ docker compose up -d | 变量 | 默认值 | 说明 | |---|---|---| -| `DATABASE_URL` | compose 中已设置 | `mysql+pymysql://user:pass@host:3306/rosdpi?charset=utf8mb4` | +| `DATABASE_URL` | compose 中已写好 | `mysql+pymysql://user:pass@host:3306/rosdpi?charset=utf8mb4` | | `LAN_CIDRS` | 私网三段 + `fd00::/8` | 内网网段,逗号分隔,界面里也能改 | | `ADMIN_PASSWORD` | 空 | 首次启动创建 `admin` 用的密码;留空则在登录页设置 | | `ROS_URL` / `ROS_USER` / `ROS_PASSWORD` | 空 | RouterOS REST 地址与只读账号,如 `https://192.168.88.1` | @@ -122,7 +188,9 @@ backend/.venv/bin/python scripts/simulate.py --minutes 5 **Limitations.** Clients using DoH/DoT (or resolving DNS elsewhere) show up as generic HTTPS/QUIC. -**Deploy.** Copy `docker-compose.yml` and `.env.example` (as `.env`), set the MySQL passwords and `LAN_CIDRS`, run `docker compose up -d`, then open `http://:8080`. RouterOS commands are listed above and in **Settings → Setup guide** inside the app. +**Deploy.** Save the `docker-compose.yml` shown above (or `curl -O https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.yml`), change the two database passwords (they must match) and `LAN_CIDRS`, then run `docker compose up -d`. Images: `suyijun8182/ros-dpi` on Docker Hub and `ghcr.io/suyijun8182/ros-dpi`, amd64/arm64. + +**First run.** Open `http://:8080` and set the admin password (or log in as `admin` with `ADMIN_PASSWORD` if you set it). No database setup is needed: the MySQL container creates the database and user on first start, and ROS-DPI creates its tables automatically (and the database itself, when using an external MySQL account allowed to create databases). MySQL's first initialization takes 20–60 s; ROS-DPI waits for it. RouterOS commands are listed above and in **Settings → Setup guide**. ## License diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 3dc1f76..485f44a 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/backend/app/database.py b/backend/app/database.py index cd3045f..24d72a7 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -56,8 +56,25 @@ def upsert_add(session, table, rows: list[dict], keys: list[str], add_cols: list session.execute(stmt) +def _create_database_if_missing(err: Exception) -> bool: + """MySQL 报 1049(库不存在)时,用同一账号连到服务器建库。成功返回 True。""" + if not is_mysql() or "1049" not in str(err): + return False + url = engine.url + name = url.database + server = create_engine(url.set(database=None), pool_pre_ping=True) + try: + with server.connect() as conn: + conn.exec_driver_sql( + f"CREATE DATABASE IF NOT EXISTS `{name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") + print(f"[db] 已自动创建数据库 {name}") + return True + finally: + server.dispose() + + def init_db(retries: int = 20, delay: float = 3.0): - """建表。容器启动时 MySQL 往往还没就绪,这里重试。""" + """建库建表。容器启动时 MySQL 往往还没就绪,这里重试。""" from app import models # noqa: F401 注册模型 for i in range(1, retries + 1): @@ -65,6 +82,11 @@ def init_db(retries: int = 20, delay: float = 3.0): Base.metadata.create_all(engine) return except Exception as e: # noqa: BLE001 + try: + if _create_database_if_missing(e): + continue + except Exception as ce: # noqa: BLE001 + print(f"[db] 数据库不存在,且自动创建失败(账号可能没有建库权限):{ce}") print(f"[db] 数据库未就绪({i}/{retries}):{type(e).__name__}: {str(e)[:120]}") if i == retries: raise diff --git a/docker-compose.yml b/docker-compose.yml index 23a8442..94db856 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,31 @@ -# ROS-DPI:RouterOS 终端流量 / 应用统计 -# 使用:cp .env.example .env,修改密码后 docker compose up -d +# ROS-DPI:RouterOS 终端流量与应用统计 +# 用法:保存为 docker-compose.yml,改掉标了「必改」的三处,然后 docker compose up -d +# 浏览器打开 http://<本机IP>:8080,首次打开设置管理员密码即可,数据库无需手动配置 services: ros-dpi: - image: ghcr.io/suyijun8182/ros-dpi:latest - # 本地构建时改用:build: . + image: suyijun8182/ros-dpi:latest # 也可用 ghcr.io/suyijun8182/ros-dpi:latest container_name: ros-dpi restart: unless-stopped depends_on: mysql: condition: service_healthy - env_file: .env - environment: - DATABASE_URL: mysql+pymysql://rosdpi:${MYSQL_PASSWORD}@mysql:3306/rosdpi?charset=utf8mb4 ports: - - "8080:8080/tcp" - - "2055:2055/udp" # IPFIX / NetFlow - - "37008:37008/udp" # TZSP(DNS 镜像) + - "8080:8080/tcp" # Web 界面 + - "2055:2055/udp" # IPFIX / NetFlow:RouterOS Traffic Flow 发到这里 + - "37008:37008/udp" # TZSP:RouterOS 镜像的 DNS 应答发到这里 + environment: + TZ: Asia/Shanghai + # 「必改」密码与下方 MYSQL_PASSWORD 保持一致 + DATABASE_URL: mysql+pymysql://rosdpi:rosdpi-change-me@mysql:3306/rosdpi?charset=utf8mb4 + # 「必改」内网网段,逗号分隔(之后也可在 系统设置 → 网络 里改) + LAN_CIDRS: 192.168.88.0/24 + # 管理员 admin 的初始密码;留空则首次打开网页时设置 + ADMIN_PASSWORD: "" + # RouterOS 只读 API(可选:显示主机名、WAN 速率) + ROS_URL: "" # 例如 https://192.168.88.1 + ROS_USER: "" + ROS_PASSWORD: "" + ROS_VERIFY_TLS: "false" volumes: - ./data:/app/data @@ -24,16 +34,16 @@ services: container_name: ros-dpi-mysql restart: unless-stopped environment: - MYSQL_DATABASE: rosdpi + TZ: Asia/Shanghai + MYSQL_DATABASE: rosdpi # 首次启动自动创建库和账号,表由 ROS-DPI 自动建立 MYSQL_USER: rosdpi - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - TZ: ${TZ:-Asia/Shanghai} + MYSQL_PASSWORD: rosdpi-change-me # 「必改」与上方 DATABASE_URL 中的密码一致 + MYSQL_ROOT_PASSWORD: root-change-me # 「必改」 command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci", "--innodb-buffer-pool-size=256M"] volumes: - ./mysql:/var/lib/mysql healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] + test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD"] interval: 10s timeout: 5s retries: 20 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 32230ea..4422140 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ros-dpi-frontend", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ros-dpi-frontend", - "version": "0.1.0", + "version": "0.1.1", "dependencies": { "@fontsource/barlow": "^5.3.0", "@fontsource/barlow-semi-condensed": "^5.3.0", diff --git a/frontend/package.json b/frontend/package.json index d7bf90a..1297e91 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ros-dpi-frontend", - "version": "0.1.0", + "version": "0.1.1", "private": true, "type": "module", "scripts": {