{"id":49850005,"url":"https://github.com/xxl6097/argus-app","last_synced_at":"2026-05-23T06:12:54.331Z","repository":{"id":357614212,"uuid":"1237354701","full_name":"xxl6097/argus-app","owner":"xxl6097","description":"仪表板上的对外标题已经改成了 WiFi 考勤 · 工时统计。","archived":false,"fork":false,"pushed_at":"2026-05-13T13:39:26.000Z","size":1405,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-13T15:35:36.190Z","etag":null,"topics":["openwrt","wifi","worktime"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xxl6097.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T05:31:13.000Z","updated_at":"2026-05-13T13:49:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/xxl6097/argus-app","commit_stats":null,"previous_names":["xxl6097/argus-app"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/xxl6097/argus-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xxl6097%2Fargus-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xxl6097%2Fargus-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xxl6097%2Fargus-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xxl6097%2Fargus-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xxl6097","download_url":"https://codeload.github.com/xxl6097/argus-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xxl6097%2Fargus-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33030380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["openwrt","wifi","worktime"],"created_at":"2026-05-14T15:00:37.552Z","updated_at":"2026-05-23T06:12:54.321Z","avatar_url":"https://github.com/xxl6097.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# argus-app\n\n[![Release](https://img.shields.io/github/v/release/xxl6097/argus-app?include_prereleases\u0026sort=semver)](https://github.com/xxl6097/argus-app/releases)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/xxl6097/argus-app)](go.mod)\n\n基于 [argusd](https://github.com/xxl6097/argusd) 的 OpenWrt 设备监控工具，\n在原有 WiFi 上下线探测能力之上扩展了一套面向**个人考勤 / 加班统计**的 Web 仪表板。\n路由器把 WiFi 上线时间当作\"打卡\"，离线当作\"下班\"，自动算出每天的在岗时长、加班时长、迟到 / 早退状态，\n按月汇总并推送到 Webhook / ntfy。\n\n\u003e 发布流程: `git tag vX.Y.Z \u0026\u0026 git push --tags` 触发 CI + Release 两个工作流;\n\u003e 跨编译 5 个架构 (linux arm64 / armv7 / amd64 / mips / mipsle), 上传到 GitHub Releases。\n\u003e 推到 main 分支本身不跑 CI — 本地用 `go test ./... \u0026\u0026 go build ./...` 验证就好。\n\n\n## 目录\n\n- [界面截图](#界面截图)\n- [快速开始](#快速开始)\n- [安装教程](#安装教程)\n  - [一、确认架构](#一确认架构)\n  - [二、下载发行包](#二下载发行包)\n  - [三、安装到系统目录](#三安装到系统目录)\n  - [四、启动并设置开机自启](#四启动并设置开机自启)\n  - [五、首次配置](#五首次配置)\n  - [六、升级与卸载](#六升级与卸载)\n  - [七、故障排查](#七故障排查)\n- [设计目标](#设计目标)\n- [项目结构](#项目结构)\n- [后端能力](#后端能力)\n- [Web UI 功能](#web-ui-功能)\n- [安装与部署](#安装与部署)\n- [HTTP API 一览](#http-api-一览)\n- [开发约定](#开发约定)\n- [许可](#许可)\n\n## 界面截图\n\n\u003cdiv align=\"center\"\u003e\n\n**主页全景** — 设备列表 + 「设为打卡」 / 「踢下线」 徽章 + 顶部 ⚙ 设置 / 版本徽章\n\n\u003cimg src=\"docs/screenshots/dashboard-overview.png\" alt=\"主页全景\" width=\"780\"\u003e\n\n**设备详情面板** — 单台设备点开后的多 tab 视图\n\n\u003cimg src=\"docs/screenshots/device-detail.png\" alt=\"设备详情\" width=\"780\"\u003e\n\n**📊 工作时长 tab** — 月度汇总 + 每日列表 + 状态徽章\n\n\u003cimg src=\"docs/screenshots/worktime-tab.png\" alt=\"工作时长\" width=\"780\"\u003e\n\n**📅 月统计 tab** — 近 12 月聚合 + 顶部 5 列汇总\n\n\u003cimg src=\"docs/screenshots/monthly-stats.png\" alt=\"月统计\" width=\"780\"\u003e\n\n**⚙ 信息设置 tab** — 每设备 Webhook + ntfy + res 主题消息\n\n\u003cimg src=\"docs/screenshots/notify-tab.png\" alt=\"信息设置\" width=\"780\"\u003e\n\n**⚙ 系统设置 modal** — 全局通知 / 钉钉关键词 / 账户 / 系统 / 备份与恢复\n\n\u003cimg src=\"docs/screenshots/settings.png\" alt=\"系统设置\" width=\"780\"\u003e\n\n**版本徽章 + 在线检测升级** — 发现新版变橙 + 🆙, 一键升级\n\n\u003cimg src=\"docs/screenshots/upgrade.png\" alt=\"在线升级\" width=\"780\"\u003e\n\n\u003c/div\u003e\n\n\u003e 想替换截图？看 [`docs/screenshots/README.md`](docs/screenshots/README.md) 的命名规范, 用 `mask_macs.py` 处理隐私后提交。\n## 快速开始\n\n### 方式 0：一键脚本（最简）\n\n直接 SSH 到 OpenWrt 路由器执行：\n\n```sh\nwget -O- https://github.com/xxl6097/argus-app/releases/latest/download/install.sh | sh\n```\n\n或：\n\n```sh\ncurl -fsSL https://github.com/xxl6097/argus-app/releases/latest/download/install.sh | sh\n```\n\n**国内 GitHub 直连不通时**，下面三条任选一条，**安装脚本自己后续会再走加速镜像下载二进制**：\n\n```sh\n# 首选 — jsDelivr (CloudFlare CDN, 走仓库 main 分支)\nwget -O- https://cdn.jsdelivr.net/gh/xxl6097/argus-app@main/install.sh | sh\n\n# 备选 — gh-proxy.com (走 GitHub Releases, 拿到的就是 latest 版本)\nwget -O- https://gh-proxy.com/https://github.com/xxl6097/argus-app/releases/latest/download/install.sh | sh\n\n# 备选 — gh-proxy 走 raw (不依赖 Release 是否发布)\nwget -O- https://gh-proxy.com/https://raw.githubusercontent.com/xxl6097/argus-app/main/install.sh | sh\n```\n\n脚本会自动识别架构、下载对应包、校验 SHA256、安装 init 脚本、启用开机自启并启动服务。**直连 GitHub 失败时会自动回退到内置加速镜像列表**，国内环境也能一行搞定。常用环境变量：\n\n```sh\n# 指定版本（默认拉 latest）\nVERSION=v0.1.0 sh install.sh\n\n# 改监听端口\nPORT=18099 sh install.sh\n\n# 强制走某个加速前缀（默认是先直连，失败自动 fallback 到内置镜像）\nPROXY=https://gh-proxy.com sh install.sh\n\n# 强制只走 GitHub 直连（适合海外服务器）\nPROXY=none sh install.sh\n\n# 自定义镜像列表（空格分隔，按顺序回退）\nGH_MIRRORS=\"https://your.mirror https://another.mirror\" sh install.sh\n\n# 强制覆盖已有 init 脚本（默认升级时只换二进制）\nFORCE=1 sh install.sh\n```\n\n### 方式 A：下载预编译二进制（手动）\n\n到 [Releases](https://github.com/xxl6097/argus-app/releases) 下载对应架构的压缩包，\n里面包含 `argus-app` 可执行文件 + OpenWrt init 脚本 + 部署文档：\n\n```bash\n# 以 aarch64（MT7981/高通 etc.）为例\nwget https://github.com/xxl6097/argus-app/releases/latest/download/argus-app_vX.Y.Z_linux_arm64.tar.gz\ntar xzf argus-app_vX.Y.Z_linux_arm64.tar.gz\nscp argus-app/argus-app root@192.168.1.1:/usr/bin/argus-app\nscp argus-app/packaging/openwrt/argus-app.init root@192.168.1.1:/etc/init.d/argus-app\nssh root@192.168.1.1 'chmod +x /usr/bin/argus-app /etc/init.d/argus-app \u0026\u0026 /etc/init.d/argus-app enable \u0026\u0026 /etc/init.d/argus-app start'\n```\n\n浏览器访问 `http://192.168.1.1:9099` 即可。完整步骤见 [packaging/openwrt/README.md](packaging/openwrt/README.md)。\n\n### 方式 B：从源码构建\n\n见下面 [安装与部署](#安装与部署) 一节的 `buildAndUpRun.sh` 脚本。\n\n## 安装教程\n\n完整的从零到运行流程，针对预编译 release 包。\n\n### 一、确认架构\n\nSSH 到路由器，运行：\n\n```bash\nuname -m\n```\n\n对应关系：\n\n| `uname -m` 输出 | 应下载的包 | 典型设备 |\n|---|---|---|\n| `aarch64` | `linux_arm64` | MT7981、IPQ60xx、RK3568 等 ARM64 路由 |\n| `armv7l` | `linux_armv7` | MT7621、Raspberry Pi 2/3、老款 ARMv7 |\n| `mips` | `linux_mips_softfloat` | 大端 MIPS（部分老款联发科） |\n| `mipsel` 或 `mips64el` | `linux_mipsle_softfloat` | 小端 MIPS（MT7620/MT7628 等） |\n| `x86_64` | `linux_amd64` | x86 软路由（J4125、N5105、N100 等） |\n\n\u003e 拿不准时打 `aarch64` 包试一下，跑不起来再换。\n\n### 二、下载发行包\n\n到 [Releases](https://github.com/xxl6097/argus-app/releases/latest) 找到对应架构的 `.tar.gz`。\n\n**方式 1：从你的电脑下载并 scp 上传**（推荐，路由器存储紧）\n\n```bash\n# 本机执行\nTAG=v0.1.0   # 替换成最新 tag\nARCH=arm64   # 替换成你的架构\nwget \"https://github.com/xxl6097/argus-app/releases/download/${TAG}/argus-app_${TAG}_linux_${ARCH}.tar.gz\"\n\ntar xzf argus-app_${TAG}_linux_${ARCH}.tar.gz\nscp argus-app/argus-app          root@192.168.1.1:/tmp/\nscp argus-app/packaging/openwrt/argus-app.init root@192.168.1.1:/tmp/argus-app.init\n```\n\n**方式 2：路由器直接下载**（仅适用 OpenWrt 自带 wget-ssl 且有公网）\n\n```bash\n# 路由器上执行\ncd /tmp\nTAG=v0.1.0\nARCH=arm64\nwget \"https://github.com/xxl6097/argus-app/releases/download/${TAG}/argus-app_${TAG}_linux_${ARCH}.tar.gz\"\ntar xzf argus-app_${TAG}_linux_${ARCH}.tar.gz\ncd argus-app\n```\n\n### 三、安装到系统目录\n\nSSH 到路由器后：\n\n```bash\n# 二进制\ninstall -m 0755 /tmp/argus-app /usr/bin/argus-app\n# 或上面方式 2 走这一行：\n# install -m 0755 /tmp/argus-app/argus-app /usr/bin/argus-app\n\n# init 脚本\ninstall -m 0755 /tmp/argus-app.init /etc/init.d/argus-app\n# 或上面方式 2：\n# install -m 0755 /tmp/argus-app/packaging/openwrt/argus-app.init /etc/init.d/argus-app\n\n# 数据目录（持久化 JSON 都在这里）\nmkdir -p /etc/argus-app /etc/argus-app/history\n```\n\n校验一下：\n\n```bash\nargus-app -version            # 或 argus-app -v\n# 输出形如：argus-app v0.1.0 (commit abc1234, built 2026-05-14T...)\n\nargus-app -help               # 或 argus-app -h, argus-app help\n# 打印中文帮助: 用法 / 安装 / 卸载 / 服务管理 / 信号 / 完整选项\n```\n\n### 四、启动并设置开机自启\n\n```bash\n/etc/init.d/argus-app enable    # 开机自启\n/etc/init.d/argus-app start     # 立即启动\n\n# 查看状态\n/etc/init.d/argus-app status\npidof argus-app\nlogread | grep argus-app | tail -20\n```\n\n成功后浏览器访问 `http://\u003c路由器 IP\u003e:9099`，例如 `http://192.168.1.1:9099`。\n\n\u003e **想改监听端口或参数？** 编辑 `/etc/init.d/argus-app`，修改 `LISTEN=` 或 `procd_set_param command` 那一段后 `/etc/init.d/argus-app restart`。\n\n### 五、首次配置\n\n1. **首次登录**：浏览器访问 `http://\u003c路由器 IP\u003e:9099`，被重定向到 `/login`。默认账号 `admin / admin`，登录后强制改密；新密码至少 6 位。后续在右上角 ⚙ 「设置 → 账户」 里随时改密 / 退出。\n2. **设置工作时间**：右上角 ⚙ 「设置」 → 不在这里，标准工时入口仍在打卡设备的「工作时长」 tab → 「设置」 按钮 → 填写 `work_start` / `work_end`，保存。\n3. **挑选打卡设备**：在主表里点设备行最右的「**设为打卡**」 徽章，把你常用的手机 / 笔记本加为打卡设备。\n4. **重命名设备**（可选）：每行「✎」 按钮起一个易记名字（`iphone17`、`work-laptop`），后续所有持久化文件都会用别名做 key，比 MAC 友好。\n5. **配置全局 Webhook**（可选）：⚙ 「设置 → 全局通知 Webhook」 填一条 URL，**任何设备**上下线都会推一份过去（payload 带 `scope:\"global\"`），适合「全屋设备总线」 这种监控。\n6. **配置每设备通知**（可选）：进入设备详情 → 「⚙ 信息设置」 tab，填 Webhook / ntfy 服务器，保存即生效；与全局 webhook 并行触发，不互相替代。\n7. **设静态 IP**（可选）：每行 IP 旁边「📌」 按钮 → 弹窗勾「立即生效」 可以瞬断一次让设备拿到新 IP。\n8. **导出备份**（可选）：⚙ 「设置 → 备份与恢复」 → 「📦 导出全部数据」 一键下载 `argus-app-backup-\u003c时间戳\u003e.tar.gz`，部署到新路由器后用同一个面板的「📥 从备份恢复」 还原。\n\n### 六、升级与卸载\n\n**升级（推荐：UI 一键升级）**：\n\n仪表板右上角的版本徽章 (`v0.1.x`) 会在后台轮询 GitHub Releases，发现新版会变橙并加 🆙 标记。点击徽章 → 「立即升级」，路由器会自己下载、替换二进制、重启服务，整个过程 30–60 秒，期间页面短暂不可用。\n\n**升级（手动）**：\n\n```bash\n/etc/init.d/argus-app stop\ninstall -m 0755 /tmp/argus-app /usr/bin/argus-app\n/etc/init.d/argus-app start\n# /etc/argus-app/ 下的数据无需任何处理，新版自动兼容旧格式\n```\n\n**卸载**（保留数据）：\n\n```bash\n/etc/init.d/argus-app stop\n/etc/init.d/argus-app disable\nrm /usr/bin/argus-app /etc/init.d/argus-app\n```\n\n**彻底清除**（含数据）：\n\n```bash\nrm -rf /etc/argus-app\n```\n\n### 七、故障排查\n\n| 现象 | 检查方向 |\n|---|---|\n| 启动后 `pidof argus-app` 为空 | `logread | tail -30` 看错误；常见是端口被占用或架构不匹配（`-bash: ./argus-app: cannot execute binary file`） |\n| 浏览器打不开 9099 | 防火墙是否拦截 LAN：`uci show firewall | grep input`；或换个端口 |\n| 上下线不刷新 | 本工具依赖 [argusd](https://github.com/xxl6097/argusd) 的探测能力，确认路由器有可用数据源（`hostapd-cli` / `dhcp.leases` 至少一种） |\n| 工时一直是 0 | 「设置」里 `work_start` / `work_end` 有没有保存？设备是否加入打卡集合？数据目录 `/etc/argus-app/history/` 是否可写？ |\n| 节假日不更新 | 路由器是否能访问 `timor.tech`？`logread | grep -i holiday`；可手动在「工作时长」tab 上切换日子类型作为应急 |\n| 通知没收到 | `/api/notifications/test` 触发一次合成事件，看 webhook 服务端 / ntfy 客户端有无收到；URL 是否带协议前缀 `https://` |\n\n---\n\n## 设计目标\n\n- **单文件部署**：纯 Go，零外部依赖（HTML 嵌入二进制）。CGO 关闭，交叉编译到 ARM64\n  跑在主流 OpenWrt 路由器上（MT7981、ipq60xx 等）。\n- **零侵入**：不动路由器原生功能。所有持久化都在 `/etc/argus-app/*.json` 单独管理。\n- **Web UI 自带登录**：cookie session + bcrypt，首次启动播种 `admin/admin`，强制改密。\n- **自治**：每天凌晨从公开 API 拉取国家法定节假日，省下手工维护。\n- **在线升级**：仪表板可直接探测最新 release 并触发自我升级，无需 SSH。\n- **数据可携**：⚙ 系统设置里一键导出 `/etc/argus-app` 全量备份, 一键恢复到新路由器。\n\n---\n\n## 项目结构\n\n```\nargus-app/\n├── cmd/app/main.go                  # 入口 + 命令行参数\n├── interval/web/\n│   ├── server.go                    # Options / Server struct / NewServer / 路由注册\n│   ├── auth.go                      # cookie session 中间件 + 登录/退出/改密 + LAN 鉴权谓词\n│   ├── events.go                    # OnEvent / OnSyslog / SSE 流 / 离线缓存\n│   ├── devices.go                   # / + /favicon + /api/devices + capabilities\n│   ├── handlers_aliases.go          # /api/aliases CRUD\n│   ├── handlers_settings.go         # /api/settings + /api/holidays\n│   ├── handlers_worktime.go         # /api/history + /api/worktime{,/month,/override}\n│   ├── handlers_notify.go           # /api/notifications{,/test,/messages}\n│   ├── handlers_version.go          # /api/version{,/check} + /api/upgrade\n│   ├── notify_dispatch.go           # dispatchNotify + 打卡分类 + 自动写下班时间\n│   ├── kick.go                      # /api/devices/kick (踢下线)\n│   ├── credentials.go               # bcrypt 登录凭据 + session 存储\n│   ├── version.go                   # GitHub Releases 探测 + 自升级触发\n│   ├── backup.go                    # /etc/argus-app tar.gz 打包/解包\n│   ├── backup_handlers.go           # /api/backup/export | /api/backup/import\n│   ├── aliases.go                   # MAC → 友好名 持久化\n│   ├── dhcp.go                      # OpenWrt uci 静态 IP 管理\n│   ├── system.go                    # 重启网络 / 重启路由器\n│   ├── history.go                   # 上下线历史 + 工时计算核心\n│   ├── settings.go                  # 打卡设备 + 标准工时 + 全局 Webhook + 钉钉关键词\n│   ├── overrides.go                 # 按月嵌套的 (alias, date) 手动工时覆写\n│   ├── holidays.go                  # 双层节假日存储 + timor.tech 自动拉取\n│   ├── notify.go                    # 每设备 Webhook + ntfy 推送 / 订阅\n│   ├── assets/dashboard.html        # 单文件 Web UI（vanilla JS + EventSource）\n│   └── assets/login.html            # 登录页（含强制改密流程）\n├── packaging/openwrt/argus-app.init # procd init 脚本\n├── install.sh                       # 一键安装脚本（带镜像回退）\n├── buildAndUpRun.sh                 # 交叉编译 + 上传 + 启动 一键脚本\n└── go.mod\n```\n\n---\n\n## 后端能力\n\n### 1. 设备探测（来自 argusd）\n- 自动选择数据源：`ahsapd` / `dhcp.leases` / `arp` 等\n- 每秒轮询，cooldown / 抖动抑制\n- 监听 OpenWrt syslog 捕获 `MAC表新增 / 无线接入 / 认证完成 / DHCP分配` 等底层事件\n\n### 2. MAC 别名 (`aliases.json`)\n为某 MAC 起易记名字（如 `iphone17`、`lenovo`），仪表板以及内部存储均优先使用别名。\n\n### 3. 静态 IP 管理（DHCP）\n通过 OpenWrt `uci` 写 `dhcp` 段：\n- 设静态 IP / 修改 / 移除\n- 可选「立即生效」：`wifi reload` 让设备瞬断重连，新 IP 即刻拿到\n- 冲突检测：同一 IP 已属于其他 MAC 时弹替换确认\n\n### 4. 上下线历史 (`history/\u003cmac\u003e.jsonl`)\n每个 MAC 一份 JSONL 追加日志，记录 ONLINE / OFFLINE 事件。\n- **保留 30 天**，超出阈值自动压缩\n- 启动时把当前在线设备播种为 ONLINE，避免长期无事件丢上线时点\n\n### 5. 工时统计核心（`history.go`）\n按日 (`ComputeWorktime`) / 按月 (`MonthlyReport`) 两套算法。\n\n**日级输出字段**：\n\n| 字段 | 含义 |\n|---|---|\n| `present_secs` | 在岗时长 = 末次下线 − min(首次上线, 标准上班) |\n| `early_ot_secs` | 早到加班 = max(0, 标准上班 − 首次上线) |\n| `late_ot_secs` | 晚走加班 = max(0, 末次下线 − 标准下班) |\n| `overtime_secs` | 加班时长 = `early_ot + late_ot`（OT 日例外，详见下文） |\n| `arrival_status` | `\"\"` / `late` / `missed_in`（迟到 / 漏刷卡） |\n| `departure_status` | `\"\"` / `early_leave`（早退） |\n| `day_kind` | `workday` / `weekend` / `holiday` / `makeup` / `otday` |\n| `ot_day` | 是否「整天算加班」的日子 |\n| `manual` | 是否使用了手动 override |\n| `missing_out` | 仅有上班记录、没有下班 |\n\n**日子类型与口径**：\n\n| 类型 | 触发 | 加班口径 | 迟到/早退判定 |\n|---|---|---|---|\n| workday | 工作日（默认） | 早到 + 晚走 | ✅ |\n| weekend | 周六/周日（未被调休覆盖） | 整天 | ❌ |\n| holiday | 法定节假日（API 推送） | **不算加班** | ❌ |\n| makeup | 调休工作日（API 推送 `workday`） | 早到 + 晚走 | ✅ |\n| otday | 用户手动标记的工作日加班 | 整天 | ❌ |\n\n**月度聚合**：累计加班 / 累计在岗 / 出勤天数 / 周末加班天数 / 迟到 / 漏刷卡 / 早退 / 日均加班 / 周均加班（按 5 个工作日折算）。\n\n### 6. 标准工时与打卡设备 (`settings.json`)\n- 工时窗口：`work_start` / `work_end` 全局共用，支持 HH:MM 与 HH:MM:SS\n- 打卡设备：`punch_macs[]` —— 多选，每台设备独立统计\n\n### 7. 手动覆写 (`overrides.json`)\n- 当系统漏检（路由器宕机、忘带手机）时手动补录某天的上班/下班时间\n- 文件按月嵌套：`{alias: {YYYY-MM: {YYYY-MM-DD: {in, out}}}}`\n- 兼容旧扁平结构，启动时自动迁移\n- **下班时间自动落盘**：打卡设备在 `work_end` 之后每次离线都把 `out` 字段写到当日记录（last-write-wins）。已有的 `in`（用户手动补录）保留不动。这样工时报告反映真实下班时间，而且数据不受 `history/` 30 天保留期影响。\n\n### 8. 节假日双层存储 (`holidays.json` + `holidays_system.json`)\n- **手动层**（`holidays.json`）：UI 中「设为工作日 / 设为加班日 / 设为节假日 / 恢复默认」\n- **系统层**（`holidays_system.json`）：从 `timor.tech/api/holiday/year/YYYY` 拉取\n  - 启动立即拉一次当前年 + 后续 9 年\n  - 之后每天 **03:00 本地时间** 重新拉\n  - 单年失败不影响其他年；网络全挂保留旧缓存\n  - 中国国务院通常只公布次年节假日，未公布年份自动跳过\n- 查询优先级：手动 \u003e 系统 \u003e 周末判定\n\n### 9. 通知派发 (`notifications.json`)\n每台设备可独立配置：\n- **Webhook**：HTTP(S) 端点，POST 一份 JSON（含结构化字段 + `markdown` 字段）\n- **ntfy**：服务器 + 用户名/密码 + req 主题（推送上下线消息）+ res 主题（订阅外部消息）\n- res 主题消息保留每设备最近 100 条，UI 实时展示\n\n**消息内容（Markdown）**：\n- 打卡设备 ONLINE → 「【alias】上班了」+ 上班时间 + 今日加班 + 本月加班\n- 打卡设备 OFFLINE → 「【alias】下班了」+ 下班时间 + 今日加班 + 本月加班\n- 普通设备 → 「【alias】上线啦 / 下线啦」+ 设备 / IP / MAC / 时间\n\n### 10. 全局 Webhook（`settings.json`）\n\n独立于每设备 webhook 的「**全屋总线**」：在 ⚙ 设置中填一条 URL，所有**已配置 per-device 通知**的设备的 ONLINE/OFFLINE 会额外推一份过去；payload 多带一个 `scope` 字段（`\"global\"` vs `\"device\"`），方便消费端区分两种来源、避免重复处理。每设备 webhook + 全局 webhook 同时生效, 互不替代。\n\n**Opt-in 闸刀**：只有\"在设备详情 → 信息设置中配过 webhook 或 ntfy\"的设备会走 webhook 派发。没配 per-device 通知的 MAC（路过的邻居手机、智能灯泡等）一律静默，避免群里被刷屏。\n\n**打卡设备智能去抖**（v0.1.21+）：打卡设备一天内会反复 ONLINE/OFFLINE（午饭、WiFi 抖动），这些瞬态事件按以下规则路由：\n\n| 场景 | 全局 Webhook | 每设备 Webhook | 消息体 |\n|---|---|---|---|\n| 当天首次 ONLINE（真签到） | ✅ | ✅ | 上班了 + 工时 |\n| OFFLINE ≥ `work_end`（真下班） | ✅ | ✅ | 下班了 + 工时 |\n| 当天再次 ONLINE（午休回来） | ✅ | ❌ | 上线啦（轻量） |\n| OFFLINE \u003c `work_end`（午饭出门 / 抖动） | ✅ | ❌ | 下线啦（轻量） |\n| 普通设备（非打卡） | 走 per-device 通道，无去抖 | | |\n\n**钉钉/飞书关键词字段**：⚙ 设置里可填一条「钉钉关键词」，会自动追加到每条 webhook markdown 的标题和正文末尾，用于通过群机器人的「自定义关键词」安全策略（否则 dingtalk 会以 `errcode 310000 关键词不匹配` 拒收）。留空 = 关闭。\n\n### 11. Web UI 登录（`credentials.json`）\n- **bcrypt + cookie session**：首次启动播种 `admin / admin` 并标记 `must_change`，登录后强制改密；密码不少于 6 位\n- 文件强制 `0600`，保存的是 bcrypt 哈希（cost=10），明文密码不落盘\n- 服务端 session 存于内存（24 小时 TTL），改密后所有其它会话失效\n- 路径置空（`-credentials=\"\"`）禁用登录闸刀，仅供本地开发\n\n### 12. 在线升级（GitHub Releases）\n- 后台轮询 `api.github.com/.../releases/latest`（30 分钟缓存，失败回退到 `gh-proxy.com`）\n- 仪表板版本徽章自动比对 semver，发现新版变橙 + 🆙\n- 「立即升级」 → 服务器写一条 bootstrap 脚本，detach 后通过 `setsid` 运行 `install.sh`，整个 `procd` 服务自我停-换-启\n- `Cache-Control: no-cache` + ETag 保证升级后页面不读到旧 HTML\n\n### 13. 数据备份与恢复（`/api/backup/*`）\n- `GET /api/backup/export` 流式打包整个 `-data-dir` 为 `tar.gz`，含 `manifest.json` (`format` / `format_version` / `exported_at` / `exporter_hostname`)\n- `POST /api/backup/import` 接收 multipart 上传：先校验 manifest 与 `format == argus-app-backup`、再两步 rename 原子替换 (失败时自动回滚, 成功后立即清理 .bak)\n- 防 zip-slip: 拒绝绝对路径 / `..` / 异常文件类型 (symlink/device/socket); 限 32 MiB 上传 / 16 MiB 单文件 / 100 MiB 解压总量\n- 可选「同时恢复账户/凭据」：默认勾选，取消则跳过 `credentials.json` + `notifications.json` 并从 live 目录复制保留\n- 凭据被替换时自动 `RevokeAll()` 全员重新登录, 避免 session/hash 不匹配\n\n### 14. 设备踢下线（`/api/devices/kick`）\n\n设备列表每行（仅在线 + 非有线设备）会渲染一个红色 「踢下线」 徽章，二次确认后调 `POST /api/devices/kick {mac}` 强制断开 WiFi 关联。命令链 (best-effort，按顺序尝试):\n\n1. `ubus call ahsapd.roaming staDisconnect`（MTK 厂商 band-steering 提示，多数固件上是 noop 但便宜尝试）\n2. `iwpriv ra*/rax* set DisConnectSta=\u003cMAC\u003e`（**真正 deauth** 的命令——MTK7981 / clife 上验证过；自动遍历所有活跃 ra*/rax* VAP）\n3. 可选 `restart_wifi=true` 时再走 `wifi reload` / `/etc/init.d/ahsapd restart`（核选项，所有客户端瞬断几秒）\n\n后端校验:\n- MAC 必须是合法的 `aa:bb:cc:dd:ee:ff` 格式\n- 有线设备 (`wired:true`) 直接拒掉 — Ethernet 侧没有 deauth 类比\n- 设备一般 30 秒内自动重连（厂商 `dismissTime` 默认 30s）\n\n---\n\n## Web UI 功能\n\n主页面：左栏 = 局域网设备，右栏 = 打卡事件 SSE 流。\n\n### 设备行（每行）\n- 状态徽章（在线 / 离线 + 离线时长）\n- MAC 字段右侧「**设为打卡 / 打卡设备**」徽章 — 点击即加入 / 移出打卡集合（可多选）\n- 仅在线 + 非有线设备额外渲染红色「**踢下线**」徽章 — 二次确认后强制 deauth, 设备会自动重连（厂商默认 dismissTime=30s）\n- 主机名 / 别名 + ✎ 内联重命名\n- IP 显示 + 🔒 静态租约标记 + 📌 设静态 IP 弹窗\n- 整行点击展开**详情面板**\n\n### 详情面板（按 tab 分）\n\n#### 📊 工作时长（仅打卡设备显示）\n- 顶部月度汇总卡：累计加班 / 在岗 / 出勤 / 日均加班 / 周均加班\n- 中部每日列表：日期 / 周几（按日子类型变色）/ 上班 / 下班 / 在岗 / 加班 / 状态徽章 / 🗑 删除\n- 月份 ◀ / ▶ / 「本月」按钮 + 上班/下班时间快速调整 + 「保存为默认」+ 「+ 补录」\n- 选中某天后下方展开当日详情卡 + 「手动编辑 / 设为工作日 / 设为加班日 / 设为节假日 / 恢复默认」按钮组\n- 节假日 / 周末加班 / 调休 / 手动加班 / 缺下班 / 仍在线等情况会显示带颜色的横条提示\n- 日期/月份维度的迟到、漏刷卡、早退会用红字突出\n\n#### 📅 月统计（仅打卡设备显示）\n- 近 12 个月一表罗列：月份 / 加班 / 在岗 / 出勤 / 日均加班 / 周均加班 / 状态\n- 顶部 5 列汇总：近 12 月累计加班 / 在岗 / 出勤天数 / 月均加班 / 有记录月份数\n- 点击任意月份 → 自动跳到「工作时长」tab 并加载该月\n\n#### 📜 上下线记录\n- 保留 30 天的上线 / 离线时间线，**单日视图** + 左右键 / ◀ ▶ 按钮翻页\n- 显示 IP、主机名等附加字段\n\n#### ⚙ 信息设置\n- Webhook 地址输入框\n- ntfy：服务器 / 用户名 / 密码 / req 主题 / res 主题\n- 「保存」/ 「移除」按钮 + 状态提示\n- 下方实时显示 res 主题最近 100 条消息（标题 + 内容）\n\n### 顶部全局按钮\n- **版本徽章**：显示当前 `v0.1.x`，发现新版会变橙 + 🆙 提示。点击弹版本 modal，可查看 release notes 并触发 「立即升级」。\n- **⚙ 设置**：统一入口，4 个区块：\n  - **全局通知 Webhook** —— 任何设备 ONLINE/OFFLINE 都额外推送到这里\n  - **账户** —— 修改密码 / 退出登录\n  - **系统** —— 重启网络服务（5–15 秒瞬断、保留配置）/ 重启路由器（30–60 秒断网、二次确认）\n  - **备份与恢复** —— 一键导出整个 `/etc/argus-app` 为 `tar.gz`，一键导入恢复（导入二级确认: 是否恢复账户/凭据）\n\n---\n\n## 安装与部署\n\n### 1. 准备工作（首次）\n- 路由器：OpenWrt 21.02+，aarch64 / armv7 / x86_64，开启 SSH\n- 本机：Go 1.25+，`sshpass`（macOS：`brew install hudochenkov/sshpass/sshpass`）\n\n### 2. 一键脚本\n\n```bash\n./buildAndUpRun.sh\n```\n\n可通过环境变量覆盖默认值：\n\n```bash\nROUTER_HOST=192.168.1.1 \\\nROUTER_USER=root \\\nROUTER_PASS='your-pass' \\\nROUTER_PORT=22 \\\nLISTEN_ADDR=0.0.0.0:9099 \\\nGO_BIN=/usr/local/go/bin/go \\\n./buildAndUpRun.sh\n```\n\n脚本流程：\n1. `CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build` 交叉编译\n2. SSH 到路由器：`killall argus-app`\n3. SCP 上传 `/tmp/argus-app`\n4. 后台启动：`nohup /tmp/argus-app -listen=0.0.0.0:9099 ... \u0026`\n\n### 3. 命令行参数\n\n| 参数 | 默认值 | 说明 |\n|---|---|---|\n| `-version` / `-v` / `version` | — | 打印版本号并退出 |\n| `-help` / `-h` / `--help` / `help` | — | 打印中文帮助 (用法 + 安装 + 卸载 + 服务管理 + 完整选项) 并退出 |\n| `-listen` | `\"\"`（关闭 Web UI） | Web 监听地址，例 `0.0.0.0:9099` |\n| `-data-dir` | `/etc/argus-app` | 数据根目录，`/api/backup/export\\|import` 以此为源 |\n| `-credentials` | `/etc/argus-app/credentials.json` | 登录凭据（bcrypt 哈希 + 用户名）；置空禁用登录 |\n| `-aliases` | `/etc/argus-app/aliases.json` | MAC 别名存储 |\n| `-settings` | `/etc/argus-app/settings.json` | 打卡设备 + 标准工时 + 全局 Webhook + 钉钉关键词 |\n| `-overrides` | `/etc/argus-app/overrides.json` | 手动工时覆写（按月嵌套） |\n| `-notifications` | `/etc/argus-app/notifications.json` | Webhook / ntfy 配置 |\n| `-holidays` | `/etc/argus-app/holidays.json` | 用户手动节假日（不被自动刷新触碰） |\n| `-holidays-system` | `/etc/argus-app/holidays_system.json` | 自动拉取的节假日缓存 |\n| `-holidays-years` | `10` | 拉取年数（当年 + 未来 N−1） |\n| `-history-dir` | `/etc/argus-app/history` | 上下线历史目录 |\n\n任意路径置空（`-foo=\"\"`）即禁用对应功能。\n\n`argus-app -h` 是最完整的速查表 — 同一份内容会跟着二进制走, 适合 SSH 上路由器后忘记某条命令时直接看。\n\n### 4. 信号\n\n| 信号 | 行为 |\n|---|---|\n| SIGINT / SIGTERM | 优雅退出 |\n| SIGHUP | 重启 Watcher（保留 known / cooldown） |\n| SIGUSR1 | 打印 metrics 快照到 stderr |\n\n### 5. 环境变量\n- `ARGUSD_DEBUG=1` — 开启 slog Debug + 决策 trace\n\n---\n\n## HTTP API 一览\n\n| 路径 | 方法 | 用途 |\n|---|---|---|\n| `/` | GET | 嵌入式仪表板（未登录跳 `/login`） |\n| `/login` | GET | 登录页 |\n| `/api/login` | POST | 用户名/密码 → 写 session cookie |\n| `/api/logout` | POST | 销毁当前 session |\n| `/api/password` | POST | 修改密码（要求当前密码 + 新密码）；成功后 RevokeAll |\n| `/api/version` | GET | 当前二进制版本（version / commit / date / upgrade_open） |\n| `/api/version/check` | GET | 探测 GitHub Releases 最新版（`?force=1` 跳缓存） |\n| `/api/upgrade` | POST | 触发自升级，可选 `{\"version\":\"vX.Y.Z\"}` 否则取 latest |\n| `/api/devices` | GET | 当前设备 + 离线缓存 |\n| `/api/devices/kick` | POST | 踢一台 WiFi 设备下线（deauth），可选 `restart_wifi=true` |\n| `/api/events` | GET (SSE) | 上下线 / Change 事件流 |\n| `/api/aliases` | GET / POST / DELETE | MAC 别名增删改查 |\n| `/api/dhcp` | GET / POST / DELETE | 静态 IP 租约 |\n| `/api/history` | GET | 某 MAC 上下线记录（最多 30 天，可加 `from=YYYY-MM-DD\u0026to=YYYY-MM-DD` 取单日） |\n| `/api/worktime` | GET | 单日工时报告 |\n| `/api/worktime/month` | GET | 月度工时报告 |\n| `/api/worktime/override` | GET / POST / DELETE | 手动覆写 |\n| `/api/settings` | GET / POST / DELETE | 打卡设备 + 标准工时 + 全局 Webhook |\n| `/api/holidays` | GET / POST / DELETE | 合并视图（手动 + 系统）|\n| `/api/notifications` | GET / POST / DELETE | 每设备 Webhook / ntfy 配置 |\n| `/api/notifications/messages` | GET | res 主题最近消息 |\n| `/api/notifications/test` | POST | 触发一次合成事件用于调试 |\n| `/api/backup/export` | GET | 流式下载 `/etc/argus-app` 全量备份 (`tar.gz`) |\n| `/api/backup/import` | POST | multipart 上传备份并恢复，可选 `restore_credentials` |\n| `/api/system/reboot` | POST | 重启路由器 |\n| `/api/system/restart-network` | POST | 重启网络服务 |\n\n除 `/login` / `/api/login` / `/favicon.ico` 外的所有路由均要求有效 session cookie；写操作（POST / DELETE）目前不再做 LAN 来源限制，由登录闸刀负责身份验证。\n\n---\n\n## 开发约定\n\n- 所有时间显示统一为 24 小时制 `HH:MM:SS`，时长统一为 `H时M分S秒` 或紧凑 `1h7m13s`\n- 日期解析使用 `ParseInLocation(..., time.Local)` 避免 UTC 偏移\n- 持久化文件使用「写临时文件 + 原子 rename」保证 crash 安全\n- 别名重命名后，旧的 MAC-keyed 条目在下一次写入时自动迁移到 alias key\n\n## 许可\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxxl6097%2Fargus-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxxl6097%2Fargus-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxxl6097%2Fargus-app/lists"}