Compare commits

...

505 Commits

Author SHA1 Message Date
mofeng-git
96a6e7edcd fix: 设置 gostc 监听地址为本地避免未授权访问 2025-12-03 13:41:28 +08:00
mofeng-git
50c3e6a32a feat: 支持 NOGOSTC dcoker 环境变量 #204 2025-12-03 13:37:03 +08:00
mofeng-git
c8305cc65d feat: 支持 turn 中转,可以远程访问 h264/webrtc #197 2025-12-03 13:09:41 +08:00
SilentWind
aae4e936db
Update README.md 2025-11-30 14:49:18 +08:00
mofeng-git
45a04f7570 feat: 添加 docker 环境 CH9329NUM #195 2025-11-27 17:41:34 +08:00
mofeng-git
53ba69f4aa fix: 删除多余参数 2025-11-05 09:15:26 +08:00
mofeng-git
53229a9055 feat: 优化编译参数 2025-11-04 22:55:12 +08:00
SilentWind
f97df0d830
Merge pull request #183 from nuintun/fix-html-tag
fix: 修复由于非法的 HTML 标签造成的宏中脚本事件个数信息位置错误的问题
2025-10-19 21:08:27 +08:00
nuintun
8ed5e4abc3
fix: 修复由于非法的 HTML 标签造成的宏中脚本事件个数信息位置错误的问题 2025-10-19 21:05:44 +08:00
mofeng-git
1e727ddc1b fix: 替换 Octopus-Planet dtb 修复 OTG 问题 2025-10-12 17:15:37 +08:00
mofeng-git
da84a6d09f feat: 为 Onecloud Pro、Octopus-Planet 添加 DRM 设备支持(HDMI 环出模拟) 2025-10-12 15:26:43 +08:00
SilentWind
9c35c68eda
fix: 修复 kvmd 访问硬件加速设备的权限规则 2025-10-12 09:22:26 +08:00
mofeng-git
651f9a4f4e feat: 更新演示站点地址 2025-10-11 15:43:04 +08:00
mofeng-git
7777f5e490 feat: 更新说明文档 2025-10-11 11:21:47 +08:00
mofeng-git
3ab5e2b431 fix: 修复 OEC TURBO 硬件编解码权限问题 2025-10-01 07:42:08 +08:00
mofeng-git
65874c6b43 fix: 修复 docker 版本VNC 服务权限问题 2025-09-30 23:53:35 +08:00
mofeng-git
67b943c151 fix: 修复 dnsmasq 软件包启动问题,解决引入的 DNS 解析 BUG 2025-09-30 23:53:28 +08:00
mofeng-git
593de19df5 fix: 更新网页部分文本 2025-09-30 21:32:35 +08:00
SilentWind
5296e61281
fix 2025-09-30 13:08:03 +08:00
mofeng-git
1729badc55 feat: 一些新功能和修复
1.启用初步 DRM 显示支持
2.预装 gostc 内网穿透
3.修复 systemd.journal 缺失问题
2025-09-29 21:18:41 +08:00
mofeng-git
9373790f37 feat: 更新设备配置和服务依赖
- 在 devices.sh 中启用 rc-local.service,以确保 kvmd 服务的正常运行
- 更新 ustreamer 配置,修正音频设备参数
- 修改 kvmd.service 文件,添加对 rc-local.service 的依赖
2025-09-28 23:43:04 +08:00
mofeng-git
edb9112435 feat: 大幅优化 RKMPP 在 ustreamer 中的编解码性能
feat: 添加 DRM 初步支持

fix: 修复 OEC TURBO 硬件编解码设备权限错误
2025-09-28 15:24:25 +08:00
mofeng-git
0328163a9e fix: 玩客云 sn 读取错误 2025-09-27 16:45:58 +08:00
mofeng-git
0c9d94e1c5 feat: 更新安装脚本以支持 dnsmasq 服务,解决 otgnet 服务所需依赖
- 新增 dnsmasq 包安装
- 启用 dnsmasq 服务并禁用 systemd-resolved
- 添加 IP 转发配置
2025-09-27 10:53:25 +08:00
mofeng-git
d4bd94cb8a feat: 更新网络配置和MAC地址生成逻辑
- 在 install.sh 中为 onecloud 和 onecloud-pro 平台启用基于 SN 的 MAC 地址生成机制
2025-09-27 09:50:34 +08:00
mofeng-git
e7c891353b feat: 新增OEC-Turbo设备支持和完善构建系统
- 新增OEC-Turbo设备构建支持,基于Debian 12 Armbian镜像
- 实现OEC-Turbo专用rootfs准备函数,支持GPT分区结构
- 添加VPU硬件编码支持,启用RK MPP加速
- 实现DTB自动下载和替换功能,避免loop设备冲突
- 修复设备特定配置函数命名机制,支持连字符转下划线
- 优化rc.local文件下载逻辑,允许文件可选不存在
- 完善系统FFmpeg包版本检测,支持bookworm和noble
- 更新GitHub Actions工作流支持OEC-Turbo设备CI构建
2025-09-20 12:49:58 +08:00
mofeng-git
3f8a9e3b2c feat: 新增香橙派 Zero One 设备构建支持
- 在 build_img.sh 中新增 orangepi-zero 构建目标支持
2025-09-20 11:31:57 +08:00
mofeng-git
4d4f528178 feat: 增强构建系统功能和设备兼容性
- 在 common.sh 中新增 download_rc_local 函数,支持自动下载平台特定的 rc.local 文件
- 集成 rc.local 自动下载到 install.sh 的 config_base_files 函数中
- 更新 cumebox2 设备配置,使用较新的 Armbian 镜像版本并增加 900MB 扩展空间
- 更新 octopus-flanet 设备使用最新的 Armbian 25.05.0 镜像
- 在 udev 规则中为 ttyUSB0 设备添加 kvmd-hid 符号链接支持
- 完善文件下载机制,支持 GitHub Actions 环境下的临时文件清理
2025-09-19 20:15:37 +08:00
SilentWind
201c615ce2
Merge pull request #178 from mofeng-git/dev
适配 Onecloud Pro 设备
2025-09-19 15:53:13 +08:00
mofeng-git
8cc9e22c91 适配 Onecloud Pro 设备 2025-09-19 15:52:30 +08:00
SilentWind
892d2b6f41
fix: 增加 ATX 初始值 2025-09-14 09:10:40 +08:00
SilentWind
30dd4290ab
Merge pull request #171 from mofeng-git/dev
Dev
2025-08-27 15:12:20 +08:00
mofeng-git
f900c4bb5a fix: 尝试修复视频格式环境变量不生效和 ttyd 下载失败问题 2025-08-27 15:11:12 +08:00
mofeng-git
6299f04127 fix: 修复初始化脚本报错 2025-08-26 10:51:57 +08:00
mofeng-git
08551e737e fix: 修复ARM64 Rockchip构建错误并优化Docker配置 2025-08-26 01:17:41 +08:00
mofeng-git
bbef7bb5c4 fix: 修复Docker多架构构建中FFmpeg库依赖问题
- 修复arm64-libs.tar.gz条件复制,使用通配符避免文件不存在错误
- 在stage-0中添加arm64架构FFmpeg库的条件复制
- 添加libyuv0依赖包支持
- 确保只在arm64下复制自定义编译的FFmpeg相关库文件
2025-08-25 22:39:27 +08:00
mofeng-git
b94cc14e2a feat: 增强初始化脚本功能
- 支持只设置WEB密码而保持admin用户名
- 添加视频格式参数设置支持(VIDEOFORMAT)
- 新增HTTP/HTTPS端口配置功能
- 修复依赖包管理和视频格式变量名错误
2025-08-25 21:13:16 +08:00
mofeng-git
ecc27c2be7 fix: 修复MSD上传功能和多项构建优化
- 修复MSD上传中prefix参数编码问题
- 移除重复的uploading-sub元素定义
- 优化Python依赖库清理和缓存管理
- 改进Rockchip硬件加速库构建流程
- 增强国际化语言检测和设置
- 修正ttyd下载地址和系统服务配置
2025-08-25 20:14:50 +08:00
mofeng-git
ccdfd52b75 fix: 修正libwebsockets的克隆地址为GitHub 2025-08-25 01:00:22 +08:00
mofeng-git
7ccac8bc9e feat: 添加ARM64 Rockchip硬件加速支持
- 集成Rockchip MPP和RGA硬件加速库
- 添加libx264和v4l2m2m支持
- 为不同架构优化FFmpeg依赖:
  * AMD64: 系统FFmpeg + Intel硬件加速
  * ARM: 系统FFmpeg
  * ARM64: 自编译FFmpeg + Rockchip硬件加速
2025-08-25 00:24:46 +08:00
SilentWind
6f4cf12c69
Merge pull request #170 from mofeng-git/dev
docs: 添加英文文档和完善中文README文档
2025-08-24 16:09:27 +08:00
mofeng-git
916a0483b4 docs: 添加英文文档和完善中文README文档 2025-08-24 16:08:25 +08:00
SilentWind
c262db4a18
Merge pull request #167 from mofeng-git/dev
合并 Dev  分支开发内容
2025-08-23 11:02:01 +08:00
mofeng-git
0b4d83dc93 fix: 完善ustreamer编译缓存解决方案
- 在编译完成后将目录重命名为标准路径 /tmp/ustreamer
- 确保后续构建步骤能正确引用 ustreamer 二进制文件
- 保持缓存破坏机制的同时维护构建流程的兼容性
2025-08-23 10:05:00 +08:00
mofeng-git
16878dc7ff feat: 基础软件包中添加FFmpeg库和v4l工具 2025-08-23 00:19:27 +08:00
mofeng-git
f80e063495 fix: 修复视频方向设置的多语言支持
- 在 navbar-system.pug 中为 video_orientation 添加 i18n 属性
- 修复 video_mode 的多语言显示问题
- 更新 menu_radio_td2 mixin 支持 i18n 属性传递
2025-08-22 23:33:47 +08:00
mofeng-git
d411affca4 fix: 禁用 ustreamer 编译步骤的 Docker 构建缓存
- 在 Dockerfile-stage-0 中添加 CACHEBUST 参数强制重新编译 ustreamer
- GitHub Actions 构建时传递时间戳作为 CACHEBUST 值
- 确保 ustreamer 每次构建都使用最新源码
2025-08-22 23:04:50 +08:00
mofeng-git
04b13b1215 feat: 添加硬件编码器支持和修复 janus.js 错误
- 添加 HWENCODER 环境变量支持,支持 vaapi、nvenc、amf、v4l2m2m、mediacodec、videotoolbox 等硬件编码器
- 修复 janus.js 相关错误,添加 adapter.js 支持
- 更新 Docker 构建配置以支持硬件编码
- 优化 ustreamer 配置,支持硬件编码回退机制
2025-08-22 21:25:13 +08:00
mofeng-git
bdd97c5ea3 fix: 修复 Firefox 登录界面布局问题
- 统一表格样式,设置 border-collapse 和 border-spacing
- 调整标签文本左对齐,减少行间距
- 修复输入框边框样式,添加统一的边框和圆角
- 统一记住我选择框和语言选择框的宽度样式
2025-08-22 15:51:54 +08:00
mofeng-git
fafd790b3e feat: 更新 janus.js 依赖库到官方最新版本
- 删除旧版本的 adapter.js 文件
- 更新 janus.js 到官方最新版本
- 修复 WebRTC 适配器兼容性问题
2025-08-22 15:37:40 +08:00
mofeng-git
432c61fd91 feat: 完善 Docker 镜像构建工作流和配置优化
- 重构 GitHub Actions 工作流,支持分阶段构建和多平台部署
- 优化 Dockerfile 依赖库配置,增加必要的系统包
- 完善初始化脚本和 KVMD 配置项
- 修复构建过程中的依赖和库文件处理
2025-08-22 15:19:56 +08:00
mofeng-git
10fbd0611f fix: 更新 GitHub Actions 镜像构建工作流配置 2025-08-22 02:21:48 +08:00
mofeng-git
e87942a5a9 feat: 添加 CH9329 HID 芯片断联自动重试功能
- 当 CH9329 芯片通信异常时自动发送复位命令
- 增加 2 秒延迟等待芯片恢复连接
- 防止 HID 功能因芯片断联而失效

代码来自 https://github.com/mofeng-git/One-KVM/pull/164
代码作者 https://github.com/snltty
2025-08-21 17:29:11 +08:00
mofeng-git
19d1c52ac4 feat: 完善 Web 界面国际化支持
- 将包含数字的无意义 i18n 键名替换为语义化名称 (如 kvm_text1 → about_title)
- 为缺失多语言支持的界面文本添加中英文翻译
- 修复不准确的翻译内容和 HTML 标签格式错误
- 更新所有 Pug 模板文件以使用新的 i18n 键名
- 新增登录页面"记住我"、USB 连接确认等功能的多语言支持
- 统一翻译键命名规范,提升代码可维护性
2025-08-21 13:23:33 +08:00
mofeng-git
2c056ca3e3 feat: merge upstream master - version 4.94
Merge upstream PiKVM master branch updates:

- Bump version from 4.93 to 4.94
- HID: improved jiggler pattern for better compatibility
- Streamer: major refactoring for improved performance and maintainability
- Prometheus: tidying GPIO channel name formatting
- Web: added __gpio-label class for custom styling
- HID: customizable /api/hid/print delay configuration
- ATX: independent power/reset regions for better control
- OLED: added --fill option for display testing
- Web: improved keyboard handling in modal dialogs
- Web: enhanced login error messages
- Switch: added heartbeat functionality
- Web: mouse touch code simplification and refactoring
- Configs: use systemd-networkd-wait-online --any by default
- PKGBUILD: use cp -r to install systemd units properly
- Various bug fixes and performance improvements
2025-08-21 11:26:59 +08:00
mofeng-git
caf3533872 chore: 更新项目配置文件
- 在 .gitignore 中添加 CLAUDE.md 排除规则
- 删除 AUTO_DOWNLOAD.md 文件
2025-08-20 19:28:39 +08:00
mofeng-git
187c713424 refactor: 完善代码质量检查和修复系统
主要改进:
- 添加 make tox-local 本地代码质量检查支持
- 创建 check-code.sh 脚本支持独立工具执行
- 修复 51+ flake8 代码风格问题(未使用导入、行尾空格、注释格式等)
- 解决 pylint 变量命名和日志格式问题
- 重构 make_image 方法解决 too-many-statements 警告
- 添加类型注解和修复方法签名不匹配问题
- 统一代码风格规范(引号使用、空格格式等)

工具配置:
- 更新 tox.ini 支持 Python 3.10 本地环境
- 添加缺失的核心依赖包定义
- 完善 Makefile 构建系统集成
2025-08-20 19:25:57 +08:00
mofeng-git
c8d1dcca30 feat: 完善 GitHub Actions 工作流和构建系统
- 添加自动下载缺失文件功能,支持 .xz 压缩格式
- 优化构建流程,增加文件清理和压缩功能
- 修复发布资产上传步骤,确保预发布标记正确设置
- 调整发布标签格式,包含版本号、设备目标和运行 ID
- 升级 Actions 版本,使用 softprops/action-gh-release@v1
- 移除 NFS 挂载依赖,简化部署流程
- 增强错误处理和日志输出
2025-08-20 16:12:40 +08:00
Maxim Devaev
0809ab4878 Bump version: 4.93 → 4.94 2025-08-12 22:08:08 +03:00
Maxim Devaev
678744ce91 pikvm/pikvm#1571: hid: improved jiggler pattern 2025-08-12 22:07:05 +03:00
Maxim Devaev
bd5e17da4b streamer: refactoring 2025-08-12 21:42:08 +03:00
Maxim Devaev
fd7bcbd88a Bump version: 4.92 → 4.93 2025-08-10 15:34:54 +03:00
Maxim Devaev
cfbb6f1be7 prometheus: tidying gpio channel name 2025-08-10 15:34:03 +03:00
Maxim Devaev
4a0029bab7 web: added __gpio-label class by user's request 2025-08-10 15:29:37 +03:00
mofeng-git
6002dfd9c7 更新说明文档 2025-07-30 00:58:17 +08:00
Maxim Devaev
42efb73c98 Bump version: 4.91 → 4.92 2025-07-28 21:01:17 +03:00
Maxim Devaev
9b5b6f6152 pikvm/pikvm#1563, pikvm/pikvm#1564: Customizable /api/hid/print delay 2025-07-28 21:00:32 +03:00
Maxim Devaev
dc7f38a1b6 hid: fix 2025-07-28 17:11:12 +03:00
Maxim Devaev
e5cee0ec5e Bump version: 4.90 → 4.91 2025-07-27 21:20:50 +03:00
Maxim Devaev
776b93cab6 lint fix 2025-07-27 21:05:47 +03:00
Pharrell
43eada0fef Update stream_mjpeg.js
fix typo
2025-07-27 20:28:18 +03:00
Maxim Devaev
ec994f4518 Bump version: 4.89 → 4.90 2025-07-16 20:27:48 +03:00
Ivan Shapovalov
70c5b9fc4b configs: use systemd-networkd-wait-online --any by default
This way, systemd-networkd-wait-online won't hang on boot for users
who only have _some_ of the configured interfaces online / in use
(which is the case for everyone who sets up Wi-Fi, since kvmd-bootconfig
does not remove eth0.network even if it was never intended to be used).

We deem this networking semantics typical for Pi-KVM in general; users
who need to wait for multiple interfaces to activate will have to
countermand this drop-in manually.

Fixes pikvm/pikvm#1514.
2025-07-16 20:26:38 +03:00
Ivan Shapovalov
296b1f3bda PKGBUILD: use cp -r to install systemd units
We are about to have subdirectories in configs/os/services/, so use
`cp` instead of `install` to copy everything wholesale.
2025-07-16 20:26:38 +03:00
Maxim Devaev
263e252db7 Bump version: 4.88 → 4.89 2025-07-15 19:46:10 +03:00
Maxim Devaev
9b433a909a hotfix 2025-07-15 19:45:29 +03:00
Maxim Devaev
0cf6f183c8 Bump version: 4.87 → 4.88 2025-07-15 18:40:06 +03:00
SilentWind
cf6addeb0f
Update README.md
更新 CDN 加速赞助信息
2025-07-15 19:28:35 +08:00
Maxim Devaev
d57c3c66cd atx: independent power/reset regions 2025-07-14 18:15:39 +03:00
Maxim Devaev
49638ed896 oled: --fill option to test the display 2025-07-09 13:09:47 +03:00
Maxim Devaev
fbf5e52b0f streamer: refactoring 2025-07-09 13:09:02 +03:00
Maxim Devaev
6bdda82822 Bump version: 4.86 → 4.87 2025-06-28 02:21:01 +03:00
Maxim Devaev
1142cc9d65 web: fixed keys handling with <input> inside the modal dialog 2025-06-28 02:20:14 +03:00
Maxim Devaev
1b5df61f61 Bump version: 4.85 → 4.86 2025-06-23 22:13:06 +03:00
Maxim Devaev
b4b1fb8d9a web: improved kb handling in modals 2025-06-19 08:27:56 +03:00
Maxim Devaev
f22e05ac88 web: login: Improved error messages 2025-06-19 07:50:06 +03:00
Maxim Devaev
6661efe61d Bump version: 4.84 → 4.85 2025-06-19 03:47:32 +03:00
Maxim Devaev
a68f860b8e switch: heartbeat 2025-06-19 03:46:42 +03:00
Maxim Devaev
e8498858bb web: mouse: simplified touch code 2025-06-12 03:00:21 +03:00
Maxim Devaev
8b5c87c893 web: mouse: refactoring 2025-06-12 02:46:48 +03:00
Maxim Devaev
824955fb83 Bump version: 4.83 → 4.84 2025-06-11 22:10:01 +03:00
SilentWind
8560a46f17
Update issue templates 2025-06-12 00:21:04 +08:00
SilentWind
d4b4cdc492
Update issue templates 2025-06-12 00:12:06 +08:00
SilentWind
687cea3658
Update issue templates 2025-06-12 00:11:17 +08:00
SilentWind
12c7566581
Update issue templates 2025-06-12 00:09:20 +08:00
Maxim Devaev
0e3c821863 pikvm/pikvm#1498: Option to suspend stream on inactive tab 2025-06-11 18:40:53 +03:00
Maxim Devaev
a5e226e168 pikvm/pikvm#1498: refactoring 2025-06-11 18:08:01 +03:00
Maxim Devaev
fe1f821715 otgconf: fixed read() awaiting 2025-06-11 05:12:54 +03:00
Maxim Devaev
b28275b042 Bump version: 4.82 → 4.83 2025-06-10 19:30:37 +03:00
Maxim Devaev
4e4ea9fcea pikvm/pikvm#1543: fixed /var/lib/kvmd/pst permissions warning 2025-06-10 05:51:04 +03:00
Maxim Devaev
735c2e6395 pikvm/pikvm#1537: /hid/inactivity api 2025-06-10 02:49:50 +03:00
Maxim Devaev
f25e5ef2b4 Bump version: 4.81 → 4.82 2025-06-03 21:02:39 +03:00
Maxim Devaev
0d8b7fd3aa otgnet: apply net.ipv4.ip_forward=1 on forwarding 2025-06-03 21:01:58 +03:00
Maxim Devaev
91312dd4be otgnet: moved ip_cmd and iptables_cmd to the commands section 2025-06-03 20:05:35 +03:00
Maxim Devaev
5bff6cadd4 Bump version: 4.80 → 4.81 2025-06-03 18:36:19 +03:00
Maxim Devaev
5d2c275f13 modal save option 2025-06-03 18:35:37 +03:00
Maxim Devaev
2a928a4a38 fixed gpio_mockup module name 2025-06-03 18:13:39 +03:00
Maxim Devaev
37e8aa2cec pikvm/pikvm#1518: web: switch: Toggle to disable MSD warning 2025-06-03 04:59:29 +03:00
Maxim Devaev
54cb364c2e fix 2025-06-03 03:38:32 +03:00
Maxim Devaev
007371d30b refactoring 2025-06-03 03:38:23 +03:00
Maxim Devaev
517e79fd65 refactoring 2025-06-02 23:16:10 +03:00
Maxim Devaev
86f73844dd Bump version: 4.79 → 4.80 2025-06-02 15:01:50 +03:00
Maxim Devaev
e04381555c pikvm/pikvm#1528: Fixed fr keymap 2025-06-02 15:01:07 +03:00
Maxim Devaev
82f45cd1fd Bump version: 4.78 → 4.79 2025-06-02 02:48:50 +03:00
Maxim Devaev
2c36d86075 pikvm/pikvm#1536: kvmd: Added new API /hid/events/send_shortcut 2025-06-02 02:48:10 +03:00
Maxim Devaev
6df1e55ffc Bump version: 4.77 → 4.78 2025-06-02 01:23:10 +03:00
Maxim Devaev
659e8f9169 web: show model name on the kvmd page 2025-06-02 01:22:14 +03:00
Maxim Devaev
38981a4108 Bump version: 4.76 → 4.77 2025-06-01 17:43:39 +03:00
SilentWind
10fb78abe6
Delete .github/workflows/docker-build-stage-0.yaml 2025-06-01 10:59:34 +08:00
Maxim Devaev
97ea7de7d3 number validator accepts hex numbers 2025-05-31 04:51:07 +03:00
Maxim Devaev
56d0d3aa8a plugin to forbid any auth 2025-05-28 19:31:10 +03:00
Maxim Devaev
92f635cdf8 Bump version: 4.75 → 4.76 2025-05-27 19:31:58 +03:00
Maxim Devaev
4a2c642c49 improved stream diagnostics 2025-05-27 19:30:57 +03:00
mofeng-git
1642ce73a0 一些更新
玩客云首次启动生成随机 mac 地址 #113
自签 SSL 证书修改为首次启动时生成,增强安全性
优化整合包镜像构建脚本和  Dockerfile
修复 docker 启动脚本错误
2025-05-27 12:43:53 +08:00
mofeng-git
64c83be0a4 修改 janus 日志等级为4,显示更多janus日志 2025-05-26 23:25:50 +08:00
Maxim Devaev
6f971a7c54 Bump version: 4.74 → 4.75 2025-05-26 15:27:18 +03:00
Maxim Devaev
1e3c90e94a web: fixed dummy switch on old firmware 2025-05-26 15:26:34 +03:00
Maxim Devaev
09884c54c0 refactoring 2025-05-26 15:22:57 +03:00
Maxim Devaev
cd2a801eae Bump version: 4.73 → 4.74 2025-05-23 23:47:58 +03:00
Maxim Devaev
183a6c2553 kvmd/client: removed queue machinery 2025-05-23 23:46:57 +03:00
Maxim Devaev
310b23edad pikvm/pikvm#1485, pikvm/pikvm#187: kvmd-localhid to pass USB keyboard and mouse through PiKVM to the host 2025-05-23 23:44:59 +03:00
Maxim Devaev
625b2aa970 refactoring 2025-05-20 17:48:56 +03:00
Maxim Devaev
741e94f2fd Bump version: 4.72 → 4.73 2025-05-20 03:33:40 +03:00
Maxim Devaev
ce3af61510 regen 2025-05-20 03:32:51 +03:00
Maxim Devaev
bf8761baa9 pikvm/pikvm#1525: Noop redfish system PATCH and boot override 2025-05-20 03:32:39 +03:00
Aleksandr Prokudin
8e2bc47cd3
Update copyright years in index.pug (#191)
This is 2025 now
2025-05-19 04:31:35 +03:00
Maxim Devaev
65d1cfd827 Bump version: 4.71 → 4.72 2025-05-18 22:35:34 +03:00
Maxim Devaev
d7963f3271 usc: using kvmd-selfauth group instead of users list 2025-05-18 22:16:20 +03:00
Maxim Devaev
c3eed7c497 pikvm/pikvm#1418: web: hold/lock key on keypad 2025-05-18 22:07:47 +03:00
Maxim Devaev
70ca478a78 web: fixed race for organize_hook 2025-05-18 00:39:10 +03:00
Ivan Shapovalov
49fb9a6f92
testenv: Dockerfile: refactor, use caching pervasively (#190) 2025-05-17 23:46:06 +03:00
Maxim Devaev
bd9f5bf9ee web: fixed window maximization behaviour without organize_hook 2025-05-17 23:23:20 +03:00
Maxim Devaev
193eaa48c8 using assert_never() 2025-05-17 23:13:01 +03:00
Maxim Devaev
47614a5724 lint fixes 2025-05-17 22:56:22 +03:00
Maxim Devaev
791e047a6b mypy: bumped version 2025-05-17 22:51:14 +03:00
Maxim Devaev
818ff6321e pikvm/pikvm#1316: web: keep stream window maximized 2025-05-17 20:42:17 +03:00
Maxim Devaev
53980c0e68 web: fixed touch handlers on chrome 2025-05-17 20:40:24 +03:00
Maxim Devaev
1195a9e3be web: moved clipboard to own file 2025-05-17 14:41:55 +03:00
Maxim Devaev
18122eff82 web: refactoring 2025-05-15 18:55:18 +03:00
Maxim Devaev
6910cebc00 web: refactoring 2025-05-15 18:38:51 +03:00
Maxim Devaev
3b39fcefd5 web: Fixed window activation when exiting the full tab mode 2025-05-15 17:50:07 +03:00
Maxim Devaev
3f309077f8 web: removed legacy option 2025-05-15 17:04:46 +03:00
Maxim Devaev
ed447a7cc2 web: Removed legacy for Safari<16.4 2025-05-15 16:48:30 +03:00
Maxim Devaev
93d60ac932 web: Removed :active pseudo-class Safari workaround 2025-05-15 16:47:52 +03:00
Maxim Devaev
39c13d31f3 web: refactoring 2025-05-14 21:15:56 +03:00
Maxim Devaev
8b97eed743 web: refactoring 2025-05-14 18:59:26 +03:00
Maxim Devaev
191eb4b430 web: changed touch scroll direction 2025-05-14 00:00:27 +03:00
Maxim Devaev
ac240e141b pikvm/pikvm#1406: Web: Fixed keypad keys overlapping 2025-05-13 23:46:53 +03:00
Maxim Devaev
af51d79502 web: Workaround Direct H.264 flickering on Firefox 2025-05-13 19:56:48 +03:00
Maxim Devaev
c551b9ff57 web: fixed window buttons for firefox 2025-05-12 19:52:37 +03:00
Maxim Devaev
df8898684f pikvm/pikvm#880: Fixed mouse position at edges 2025-05-12 19:26:54 +03:00
Maxim Devaev
5273199e0b web: color fix 2025-05-12 03:57:22 +03:00
Maxim Devaev
eb0fb04b72 web: better handling of windows with iframes 2025-05-12 03:57:07 +03:00
Maxim Devaev
cfdf225d10 web: improved scroll algorithm added two fingers touch scroll 2025-05-11 20:38:22 +03:00
mofeng-git
76ca81bbfd 修复音频错误
更新 janus 配置文件,将音频部分由 audio 重命名为 acap,与 ustreamer janus 插源代码保持一致。
2025-05-11 21:38:32 +08:00
mofeng-git
ed7b2e5b33 添加初步的 gitea actions 构建支持 2025-05-11 20:21:18 +08:00
Maxim Devaev
c80532fb73 pikvm/pikvm#1080: Fixed windows grabbing and moving on touch tablets using addEventListener() instad on* handlers 2025-05-10 19:49:05 +03:00
Maxim Devaev
9875d4686f web: removed legacy visibility code 2025-05-10 13:53:38 +03:00
Maxim Devaev
1b822c19ff vnc: idiomatic start_tls() 2025-05-10 02:20:00 +03:00
Maxim Devaev
1356187771 vnc: common key event handler 2025-05-09 23:24:21 +03:00
Maxim Devaev
8fb4bc6be7 vnc: split mouse handlers 2025-05-09 21:39:56 +03:00
Maxim Devaev
09eb5ebc2f vnc: using evdev codes 2025-05-09 12:26:04 +03:00
Maxim Devaev
bc880009c1 common BaseMagicHandler class 2025-05-09 10:08:44 +03:00
Maxim Devaev
3268c62bf3 vnc: magic alt-alt key 2025-05-09 04:08:33 +03:00
Maxim Devaev
21c83e6fca vnc: pass offline frames 2025-05-09 04:08:09 +03:00
Maxim Devaev
8f19d40566 switch: id/port api 2025-05-09 04:05:02 +03:00
Maxim Devaev
32425c1903 switch: server-side IDs 2025-05-07 18:23:13 +03:00
Maxim Devaev
6005ed38b9 meta: auto fqdn 2025-05-07 18:07:09 +03:00
Maxim Devaev
bb0656c0cb vnc: additional auth check 2025-05-07 12:46:45 +03:00
Maxim Devaev
8d7f89e8f1 switch: next/prev api 2025-05-07 05:03:10 +03:00
Maxim Devaev
a65cd7feb5 vnc: removed allow_cut_after for a future hotkey paste 2025-05-07 04:41:32 +03:00
Maxim Devaev
d630e24aa0 note about pid==0 in get_request_unix_credentials() 2025-05-06 21:08:32 +03:00
Maxim Devaev
46ef5fd46b vnc: using usc auth 2025-05-06 20:51:34 +03:00
Maxim Devaev
c8cf06ee8c ipmi: usinc usc auth 2025-05-06 14:49:20 +03:00
Maxim Devaev
79d4d99f37 usc allowed for docker 2025-05-04 06:05:30 +03:00
Maxim Devaev
0437f487b5 refactoring 2025-05-04 03:29:13 +03:00
Maxim Devaev
59eff99dcc refactoring 2025-05-03 23:14:51 +03:00
Maxim Devaev
334b9f7d7b nginx: configurable listen ip addresses
Based by idea of pikvm/pikvm#189
2025-05-03 18:50:14 +03:00
Maxim Devaev
6dea594380 pikvm/pikvm#1500: web: Paste hotkey 2025-05-03 05:03:48 +03:00
Maxim Devaev
fd5196a2ce udev: Disabled USB autosuspend for PiKVM devices 2025-05-03 04:29:06 +03:00
Maxim Devaev
b7715b731e lint fixes 2025-05-03 04:27:21 +03:00
Maxim Devaev
7d7edb1c03 pikvm/pikvm#1501: Switch: Option to disable HDMI dummy plug 2025-05-03 03:54:05 +03:00
Maxim Devaev
69d254d80e lint fix 2025-05-02 07:18:01 +03:00
Maxim Devaev
e011a98288 patched pico sdk for cmake 2025-05-01 06:15:35 +03:00
Maxim Devaev
63a1933342 audio keys 2025-05-01 06:09:45 +03:00
Maxim Devaev
ebbd55ee17 using evdev instead of string constants 2025-05-01 03:03:25 +03:00
mofeng-git
a92a6f2811 更新百度网盘链接的密码 2025-04-17 03:24:46 +00:00
mofeng-git
3d58f6dd21 更新 README.md 文件 2025-04-17 03:22:50 +00:00
mofeng-git
50022e7353 重构镜像构建脚本,增强了灵活性和可维护性。主要改动包括:
1. 允许通过环境变量覆盖默认路径。
2. 添加清理函数以确保资源释放。
3. 优化了挂载和卸载逻辑,确保在脚本退出时自动清理。
4. 增强了错误处理和日志输出,提供更清晰的信息反馈。
5. 重新组织了函数结构,提升代码可读性。
2025-04-17 03:12:47 +00:00
Maxim Devaev
1624b0cbf8 added KvmdClientWs.send_mouse_relative_event() 2025-04-15 22:02:10 +03:00
Maxim Devaev
fa2630250c refactoring 2025-04-14 02:05:21 +03:00
Maxim Devaev
7e185d2ad9 unix socket auth 2025-04-13 17:45:01 +03:00
Maxim Devaev
16a1dbd9ed Bump version: 4.70 → 4.71 2025-04-06 12:28:11 +03:00
Maxim Devaev
e66edd45e2 pikvm/pikvm#1460: Added scroll_rate param for VNC 2025-04-06 12:27:21 +03:00
Maxim Devaev
86774dfa4e Bump version: 4.69 → 4.70 2025-04-06 00:08:07 +03:00
Maxim Devaev
866eb2a2c6 pikvm/pikvm#1489: Added en-us-colemak keymap 2025-04-06 00:06:40 +03:00
Maxim Devaev
1984a245e9 Bump version: 4.68 → 4.69 2025-04-05 12:37:27 +03:00
Maxim Devaev
04209e2a6b increased ocr timeout and allow offline 2025-04-05 12:36:35 +03:00
Maxim Devaev
71617cc62a fixed /api/hid/print timeout with slow typing 2025-04-04 14:38:00 +03:00
Maxim Devaev
45ff6cb7c7 Bump version: 4.67 → 4.68 2025-04-03 13:10:07 +03:00
mofeng-git
ff4f04d936 更新说明 2025-03-28 04:30:18 +00:00
Yao Wei
49695247a5
hid: fix flashing hid using avrdude 8.0 (#188)
Closes: pikvm/pikvm#1482
2025-03-25 06:40:40 +02:00
Maxim Devaev
87f78990a5 Bump version: 4.66 → 4.67 2025-03-19 22:12:17 +02:00
Maxim Devaev
b86f4cd437 allow short edids, import full edid on with kvmd-edidconf 2025-03-19 03:51:31 +02:00
mofeng-git
413fce72ec 更新镜像构建脚本 2025-03-15 12:51:23 +00:00
mofeng-git
842238009e 将默认采集帧率由 30fps 修改为 60fps 2025-03-15 11:47:46 +00:00
Maxim Devaev
2c4f7f1458 Bump version: 4.65 → 4.66 2025-03-12 23:01:20 +02:00
Maxim Devaev
ba5df47c97 edidconf: Allow 128-byte edids for --import-display-ids 2025-03-12 23:00:33 +02:00
Raphael Ochsenbein
20a7206b0f
web: disable autocomplete for 2fa (#187) 2025-03-12 01:05:04 +02:00
Maxim Devaev
70d134a2ff Bump version: 4.64 → 4.65 2025-03-11 16:13:12 +02:00
Maxim Devaev
8391b7a467 web: reconfigure webcodec if needed 2025-03-11 16:07:32 +02:00
mofeng-git
7116da2511 添加 otg 文件夹创建判断,避免文件夹存在导致程序退出 2025-03-11 12:52:19 +00:00
mofeng-git
a59fe84e26 修改 supervisord 配置文件为默认路径 2025-03-11 12:50:10 +00:00
Maxim Devaev
2bdd349fbf kvmd-edidconf: monitor ID clonning option for V4 2025-03-11 05:31:25 +02:00
mofeng-git
48fe790897 大幅优化镜像体积
1. 使用 docker 预构建的必需文件,而不是安装全量依赖
2. 使用 zerofree 清除镜像无效数据
将 janus 日志级别调整为2
2025-03-10 10:56:16 +00:00
mofeng-git
e375e41fb6 更新赞助信息 2025-03-06 14:41:59 +00:00
Maxim Devaev
5014e82177 Bump version: 4.63 → 4.64 2025-03-03 03:23:55 +02:00
Maxim Devaev
1566f026de pikvm/pikvm#1254: kvmd-bootconfig: Added option WIFI_WPA23=1 2025-03-03 03:11:43 +02:00
Maxim Devaev
878bc03a80 refactoring 2025-03-03 03:05:34 +02:00
Maxim Devaev
41e6502904 Bump version: 4.62 → 4.63 2025-03-01 18:57:18 +02:00
Maxim Devaev
ec9c12ffcc enabled relative mouse by default on all v2+ configurations 2025-03-01 18:55:55 +02:00
Maxim Devaev
9fdb861048 web: removed blue border in fullscreen mode 2025-03-01 18:38:53 +02:00
Maxim Devaev
97dbc17771 refactoring 2025-02-27 23:01:31 +02:00
Maxim Devaev
e7d4f7fe8c Bump version: 4.61 → 4.62 2025-02-24 19:52:52 +02:00
Maxim Devaev
1cb5c11239 pikvm/pikvm#1339: Pass ICE servers to the Web UI 2025-02-24 19:51:43 +02:00
Maxim Devaev
72ef037959 Bump version: 4.60 → 4.61 2025-02-22 19:50:35 +02:00
Maxim Devaev
182aa0e374 otg: renamed product, removed configuration name 2025-02-22 19:33:56 +02:00
Maxim Devaev
876ff22bd8 Bump version: 4.59 → 4.60 2025-02-20 02:41:22 +02:00
Maxim Devaev
a01ef562a1 fixed sed 2025-02-20 02:40:33 +02:00
Maxim Devaev
362b88e92c Bump version: 4.58 → 4.59 2025-02-20 02:27:08 +02:00
Maxim Devaev
7f6b0a814d v4plus: Increaset memory for 4k OUT2 display 2025-02-20 02:26:19 +02:00
Maxim Devaev
b3d1291039 Bump version: 4.57 → 4.58 2025-02-16 01:01:54 +02:00
Maxim Devaev
6a08fab818 switch: added ignore_hpd quirk for bad csi boards 2025-02-16 01:00:38 +02:00
Maxim Devaev
02740aef37 Bump version: 4.56 → 4.57 2025-02-13 14:28:07 +02:00
Maxim Devaev
dd3f4c16e3 htpasswd: raise error on del if user is not exist 2025-02-13 14:20:33 +02:00
Maxim Devaev
30a82efea4 htpasswd: split add and set commands 2025-02-13 13:40:02 +02:00
Maxim Devaev
ccbe455ada refactoring 2025-02-13 12:07:26 +02:00
Maxim Devaev
1d0f441cc4 Bump version: 4.55 → 4.56 2025-02-13 00:56:50 +02:00
Maxim Devaev
8c7f86ac83 switch firmware version == 6 2025-02-13 00:56:11 +02:00
Maxim Devaev
4b67208cab Bump version: 4.54 → 4.55 2025-02-12 13:02:22 +02:00
Maxim Devaev
a3e398a1d5 web: added doc link about session expiration 2025-02-12 12:57:08 +02:00
Maxim Devaev
c66c97afd4 improved auth logging 2025-02-12 12:51:15 +02:00
Maxim Devaev
83c352a900 Bump version: 4.53 → 4.54 2025-02-11 17:02:24 +02:00
Maxim Devaev
de4f1903aa using salted sha512 for htpasswd by default 2025-02-11 16:55:45 +02:00
Maxim Devaev
800d2724b8 Bump version: 4.52 → 4.53 2025-02-10 20:27:48 +02:00
Maxim Devaev
dc1c6c0fcf nginx: disabled cache for the /login location 2025-02-10 20:21:16 +02:00
Maxim Devaev
4c9c98c6ab refactoring 2025-02-10 00:55:33 +02:00
Maxim Devaev
6ffaa8d6bd refactoring 2025-02-10 00:06:49 +02:00
Maxim Devaev
97b405297b refactoring 2025-02-09 23:20:28 +02:00
Maxim Devaev
302e7c2877 web: placed pikvm logo on login page 2025-02-09 20:32:38 +02:00
Maxim Devaev
75a4aa0736 improved auth logging 2025-02-09 19:44:42 +02:00
Maxim Devaev
c3dc5b9553 test_auth: improved expiration test 2025-02-09 14:31:51 +02:00
Maxim Devaev
79b7788480 web: fixed switch edids collection selector width 2025-02-09 03:05:32 +02:00
Maxim Devaev
05519f403f commented hidrelay from testenv 2025-02-09 02:46:41 +02:00
Maxim Devaev
c49d712f17 pikvm/pikvm#1204: 12h instead of 24h 2025-02-09 01:07:19 +02:00
Maxim Devaev
375a345820 pikvm/pikvm#1204: Configurable global expiration policy 2025-02-09 00:40:48 +02:00
Maxim Devaev
a7c3cdc1ea pikvm/pikvm#1204: Expire user session 2025-02-08 23:30:52 +02:00
Maxim Devaev
abbd65a9a0 lint fix 2025-02-08 20:01:35 +02:00
Maxim Devaev
ba28f03575 refactoring 2025-02-08 19:22:56 +02:00
Maxim Devaev
ad019f8476 web: cleanup session/info code 2025-02-08 19:11:42 +02:00
Maxim Devaev
0afc81f56c ustreamer >= 6.31 2025-02-08 18:36:27 +02:00
Maxim Devaev
84ec99b332 health event instead of hw 2025-02-07 01:10:57 +02:00
Maxim Devaev
54f6d93f63 kvmd: binary ping/pong 2025-02-07 01:08:53 +02:00
Maxim Devaev
94fe2226f1 js cleanup 2025-02-06 17:04:18 +02:00
Maxim Devaev
beb5d541b0 Bump version: 4.51 → 4.52 2025-02-03 09:52:21 +02:00
Maxim Devaev
1c179da857 web: orientation changing for media 2025-02-03 09:51:25 +02:00
mofeng-git
8c8bf35d0b fix 2025-02-02 07:08:40 +00:00
Maxim Devaev
c8df621172 Bump version: 4.50 → 4.51 2025-02-02 07:16:21 +02:00
Maxim Devaev
1899902860 bunch of js === and !== fixes 2025-02-02 07:15:08 +02:00
Maxim Devaev
4800f9e486 nginx: removed legacy limit_rate 2025-02-02 07:10:11 +02:00
Maxim Devaev
73238e18e9 pikvm/pikvm#1462: relative root location 2025-02-02 07:09:21 +02:00
mofeng-git
f032b8c798 fix 2025-02-01 14:40:28 +00:00
mofeng-git
c711683c63 som fix 2025-02-01 12:19:02 +00:00
mofeng-git
06a64725be 修改源文件版权声明 2025-02-01 10:55:41 +00:00
mofeng-git
94897ab8c9 Merge branch 'main' of https://github.com/mofeng-git/One-KVM 2025-02-01 09:13:29 +00:00
mofeng-git
c6a5ffa0cf Merge remote-tracking branch 'upstream/master' 2025-02-01 09:12:03 +00:00
mofeng-git
9da06d3f58 修复 HTTP H.264 模式
其他修改
2025-02-01 08:31:27 +00:00
Maxim Devaev
b51ea5e374 web: relative html 2025-02-01 08:58:04 +02:00
Maxim Devaev
13fff8a88c web: preparing to relative paths 2025-02-01 08:29:36 +02:00
Maxim Devaev
9436bb029d web: removed gop link 2025-02-01 08:16:25 +02:00
mofeng-git
7b3335ea94 Add support for PiKVM Switch and related features
This commit introduces several new components and improvements:
- Added Switch module with firmware update and configuration support
- Implemented new media streaming capabilities
- Updated various UI elements and CSS styles
- Enhanced keyboard and mouse event handling
- Added new validators and configuration options
- Updated Python version support to 3.13
- Improved error handling and logging
2025-02-01 01:08:36 +00:00
Maxim Devaev
430a3848f7 web: commented invalid css 2025-01-31 00:25:59 +02:00
Maxim Devaev
3b5e539012 web fixes, verbose video modes name 2025-01-30 20:12:36 +02:00
Maxim Devaev
d1a12f1f6a web: fixed slider height on firefox 2025-01-30 20:07:33 +02:00
Maxim Devaev
697ef549b9 refactoring 2025-01-30 10:34:36 +02:00
Maxim Devaev
4039ae0483 Bump version: 4.49 → 4.50 2025-01-28 16:00:27 +02:00
Maxim Devaev
06812231c1 fixed missing python-bcrypt 2025-01-28 15:57:48 +02:00
Warfront1
15dbe6265f
feat: fix cause on switch pop-up (#185) 2025-01-25 08:40:48 +02:00
Maxim Devaev
b2c8ed6818 Bump version: 4.48 → 4.49 2025-01-24 05:33:43 +02:00
Maxim Devaev
2acd613a38 dvd support 2025-01-24 05:24:40 +02:00
Maxim Devaev
0202a3c2d1 Bump version: 4.47 → 4.48 2025-01-21 05:41:57 +02:00
Maxim Devaev
be3e97178d moved to python-3.13 2025-01-21 05:41:25 +02:00
Maxim Devaev
dafc8e3941 Bump version: 4.46 → 4.47 2025-01-20 16:45:49 +02:00
Maxim Devaev
6dcc41601e janus: check file for aplay 2025-01-20 16:43:32 +02:00
Maxim Devaev
b9af5f8825 Bump version: 4.45 → 4.46 2025-01-20 02:17:26 +02:00
Maxim Devaev
00ed5197b0 add some otg info 2025-01-20 02:16:55 +02:00
Maxim Devaev
b2c5305564 Bump version: 4.44 → 4.45 2025-01-19 18:24:21 +02:00
Maxim Devaev
e9443119ec required ustreamer 6.24 at least 2025-01-19 18:20:39 +02:00
Maxim Devaev
ab5608e3e0 Bump version: 4.43 → 4.44 2025-01-18 22:01:39 +02:00
Maxim Devaev
78557b0c47 Merge branch 'mic' 2025-01-18 21:57:48 +02:00
Maxim Devaev
f042ed38e0 usb microphone 2025-01-18 20:28:24 +02:00
Maxim Devaev
e1e3605630 Bump version: 4.42 → 4.43 2025-01-16 15:01:01 +02:00
Maxim Devaev
3f3a834c0c pikvm/pikvm#1459: Extended TOTP window with a single step (+30sec) 2025-01-16 14:57:05 +02:00
Maxim Devaev
8631ee8555 web: fixed gray icon on http/h264 2025-01-16 14:40:04 +02:00
Maxim Devaev
da4da975ef Revert "pikvm/pikvm#1459: TOTP valid_window=5"
This reverts commit b6c73aceb74cf6f3a376fc1223e9cc701a8f6ee5.
2025-01-15 02:49:10 +02:00
Maxim Devaev
b6c73aceb7 pikvm/pikvm#1459: TOTP valid_window=5 2025-01-15 02:06:01 +02:00
Maxim Devaev
d3549ab52b Bump version: 4.41 → 4.42 2025-01-11 22:21:39 +02:00
Maxim Devaev
965e649f8c switch update notification 2025-01-11 22:20:56 +02:00
Maxim Devaev
b49107ff6c Bump version: 4.40 → 4.41 2025-01-11 21:27:08 +02:00
Maxim Devaev
e9cbf04ba5 kvmd-otgmsd: allow to connect all file types 2025-01-11 21:26:26 +02:00
Maxim Devaev
3cf543a13e switch binary 2025-01-11 21:22:17 +02:00
Maxim Devaev
4d89d6b222 Bump version: 4.39 → 4.40 2025-01-10 23:23:49 +02:00
Maxim Devaev
e7c06643b4 refactoring 2025-01-10 23:04:12 +02:00
Maxim Devaev
72c9ae3aa0 improved jiggler logic 2025-01-10 22:56:28 +02:00
Maxim Devaev
05bced1461 Bump version: 4.38 → 4.39 2025-01-10 14:26:11 +02:00
Maxim Devaev
464672d1a0 enabled jiggler by default 2025-01-10 14:24:43 +02:00
SilentWind
1061a6ba01
Merge pull request #70 from soulteary/fix/license-conflict
fix: license conflict
2025-01-06 10:37:15 +08:00
Maxim Devaev
be6843a486 Bump version: 4.37 → 4.38 2025-01-05 20:49:38 +02:00
Maxim Devaev
f5de6a0f2e moving to janus 1.x 2025-01-05 20:48:35 +02:00
Su Yang
21b7429ffe
fix: license conflict 2025-01-05 22:19:31 +08:00
Maxim Devaev
9ef1a3665a Bump version: 4.36 → 4.37 2025-01-05 15:24:20 +02:00
Maxim Devaev
10a7ca978b Bump version: 4.35 → 4.36 2025-01-05 15:19:54 +02:00
Maxim Devaev
4488365dfb removed _state suffix from all ws events 2025-01-05 15:19:04 +02:00
Maxim Devaev
5a61ddecd3 Removed ws legacy mode and some msd legacy 2025-01-05 15:11:27 +02:00
Maxim Devaev
a12163a797 kvmd-media: renamed kind to type 2025-01-05 14:43:20 +02:00
Maxim Devaev
43e6cd3e26 usb: kvmd-otgconf now calculates endpoints before operation 2025-01-05 14:17:52 +02:00
Maxim Devaev
57518468ad usb: max endpoints is 9 2025-01-05 14:14:17 +02:00
Maxim Devaev
5973b9e773 kvmd-otgconf: Ignore some errors 2025-01-05 02:34:11 +02:00
Maxim Devaev
e120b50f50 usb: max endpoints is 10, not 8 2025-01-05 02:02:21 +02:00
Maxim Devaev
f1256ee74a Bump version: 4.34 → 4.35 2025-01-04 22:36:13 +02:00
Maxim Devaev
9aef70c43f lint fixes 2025-01-04 22:17:55 +02:00
Maxim Devaev
f9584929e3 usb: endpoints calculation 2025-01-04 18:27:17 +02:00
Maxim Devaev
7aa963330c Bump version: 4.33 → 4.34 2025-01-02 19:18:28 +02:00
Maxim Devaev
5d8633556e fixed missing modifiers mapping 2025-01-02 19:17:50 +02:00
Maxim Devaev
ebda7ea03d Bump version: 4.32 → 4.33 2024-12-30 18:56:38 +02:00
Maxim Devaev
fed3bf1efd pikvm/pikvm#1334: Bad link mode for keyboard events 2024-12-30 18:55:59 +02:00
Maxim Devaev
d52bb34bb9 Bump version: 4.31 → 4.32 2024-12-27 05:44:44 +02:00
Maxim Devaev
6c5f0bf09f janus: use symbolic soundcard name 2024-12-27 05:44:01 +02:00
Maxim Devaev
aae529f40b split otg mouse start options 2024-12-27 05:42:23 +02:00
Maxim Devaev
253231adac enabled remote wakeup by default 2024-12-27 03:01:18 +02:00
Maxim Devaev
e491057891 Bump version: 4.30 → 4.31 2024-12-26 16:57:40 +02:00
Maxim Devaev
3b5d62dd98 enable kvmd-media when kvmd-janus or kvmd-janus-static enabled 2024-12-26 16:57:05 +02:00
Maxim Devaev
38346bece1 improved media js 2024-12-26 16:56:30 +02:00
Maxim Devaev
647d3f3961 Bump version: 4.29 → 4.30 2024-12-26 05:05:41 +02:00
Maxim Devaev
287244d376 kvmd: disabled legacy API by default 2024-12-26 05:05:03 +02:00
Maxim Devaev
56438a372e Bump version: 4.28 → 4.29 2024-12-25 09:17:40 +02:00
Maxim Devaev
ab08d823c4 pikvm/pikvm#1440: Websocket-based transport and decoding for H.264 2024-12-25 09:16:59 +02:00
mofeng-git
5db37797ea 适配章鱼星球 2024-12-19 15:24:07 +00:00
Maxim Devaev
eda7ab3a49 Bump version: 4.27 → 4.28 2024-12-18 06:42:17 +02:00
Maxim Devaev
af2ee26a2f kvmd-media server 2024-12-18 06:39:18 +02:00
Maxim Devaev
596334735e removed legacy generic configs 2024-12-18 06:00:13 +02:00
Maxim Devaev
c8385213cc Bump version: 4.26 → 4.27 2024-12-17 18:28:52 +02:00
Maxim Devaev
c009985247 build fix 2024-12-17 18:28:17 +02:00
Maxim Devaev
7caa695d79 Bump version: 4.25 → 4.26 2024-12-17 18:21:13 +02:00
Maxim Devaev
630610bc53 switch 2024-12-17 18:20:04 +02:00
Maxim Devaev
e0bbf6968e testenv: Use memsink for VNC 2024-12-16 19:19:31 +02:00
Maxim Devaev
ada1c39eef Bump version: 4.24 → 4.25 2024-12-11 21:10:24 +02:00
Maxim Devaev
e014cbcedf pikvm/pikvm#858, pikvm/pikvm#1249: Added slow typing mode for /api/hid/print 2024-12-11 21:09:49 +02:00
Maxim Devaev
adbd4f242b pikvm/pikvm#1437: Don't reset absolute mouse position on clear 2024-12-11 17:56:54 +02:00
Maxim Devaev
2649a2fa01 web: Enabled secure paste text for Firefox 2024-12-11 17:56:54 +02:00
No0ne
8cca5a8cc7
Bump version: ps2x2pico-2.0 (#184) 2024-12-05 13:41:54 +02:00
Maxim Devaev
70452f048b Bump version: 4.23 → 4.24 2024-12-03 19:25:50 +02:00
Maxim Devaev
be21a420a0 fix 2024-12-03 19:25:13 +02:00
Maxim Devaev
e337e8d45c switch: Added udev rule for /dev/kvmd-switch 2024-12-03 19:23:38 +02:00
Maxim Devaev
8a09505baf pikvm/pikvm#1432: web: Fixed OCR region 2024-12-03 19:15:00 +02:00
Maxim Devaev
870af902a1 fix 2024-12-03 19:08:18 +02:00
mofeng-git
0dd117711d 一些样式修改 2024-11-29 05:29:28 +00:00
mofeng-git
ed68449274 修复相对模式鼠标不可用
此问题有合并上游更新冲突所致
2024-11-27 13:33:49 +00:00
Maxim Devaev
85a2f2367d Bump version: 4.22 → 4.23 2024-11-26 19:12:40 +02:00
Maxim Devaev
7fd4dae3c6 pikvm/pikvm#1408: Additional colors for GPIO 2024-11-26 19:11:59 +02:00
mofeng-git
a32dcd2e00 修复前端 wol 类名错误
修复前端 meta 块错误
2024-11-26 05:42:18 +00:00
Maxim Devaev
0cf5f8de9e Bump version: 4.21 → 4.22 2024-11-25 05:29:49 +02:00
Maxim Devaev
7394588279 fixed prometheus metrics 2024-11-25 05:26:03 +02:00
mofeng-git
666f0b694a a fix 2024-11-23 02:45:01 +00:00
mofeng-git
b8ddf7c2da 增加快速文件互传功能(基于 MSD)
为 MSD 路径添加配置选项
为 文件镜像名称添加配置选项
修复 make 测试环境
2024-11-22 17:40:55 +00:00
Maxim Devaev
1b9b27660a Bump version: 4.20 → 4.21 2024-11-22 16:32:05 +02:00
Maxim Devaev
7c453b8b49 new sponsors 2024-11-22 16:29:59 +02:00
mofeng-git
5450d7297c 为 docker 版本添加 nano 文本编辑器 2024-11-21 04:52:46 +00:00
mofeng-git
506d8a4a64 为 Docker 容器添加 kvmd 系列管理命令 2024-11-21 02:52:36 +00:00
Maxim Devaev
8929d0f311 pikvm/pikvm#1415: kvmd-bootconfig: Supported open wifi network 2024-11-20 21:35:25 +02:00
Maxim Devaev
d25e43c934 pikvm/pikvm#1415: Allow autoconnecting to open wifi 2024-11-20 18:53:10 +02:00
Maxim Devaev
3cbeabe2e8 VNC: Supported ExtendedMouseButtons 2024-11-20 17:50:27 +02:00
mofeng-git
eec64ef57c Merge remote-tracking branch 'upstream/master' 2024-11-20 15:18:34 +00:00
mofeng-git
baa0f7e226 取消中文日志
中文日志没有必要,减低代码耦合
2024-11-20 12:44:59 +00:00
mofeng-git
3ec872878e 修复 make run-nogpi 测试环境 2024-11-20 12:44:59 +00:00
mofeng-git
6928fab16c Revert "初步的 kvmd 国际化(汉化)支持"
This reverts commit 20927c7226b1860f94299bd157f73c08ba950dda.
2024-11-20 12:44:59 +00:00
mofeng-git
8fdb7d7cd6 Revert "修复 kvmd 国际化带来的错误"
This reverts commit 4fc188dbc891dacb54268c08cef5a4051772104e.
2024-11-20 12:44:59 +00:00
mofeng-git
433232c845 Revert "进一步的 kvmd 国际化(汉化)支持"
This reverts commit 5b25b3661f28deda2d836f970387465ebfd6896a.
2024-11-20 12:44:59 +00:00
mofeng-git
b419641251 Revert "进一步的 kvmd 国际化(汉化)支持,添加配置入口"
This reverts commit 35397c54143f3dba89bebc4e30bc09b0804b5798.
2024-11-20 12:44:59 +00:00
mofeng-git
50819d0a35 更换logo,增大默认分辨率和 h.264 比特率 2024-11-18 14:53:26 +00:00
mofeng-git
a37b818039 更新脚本 2024-11-16 08:42:08 +00:00
mofeng-git
8a81996e52 更新说明 2024-11-16 06:09:48 +00:00
mofeng-git
69cb9ac950 更新一键脚本 2024-11-16 05:50:08 +00:00
mofeng-git
30378211b5 更新说明 2024-11-12 11:12:41 +00:00
Maxim Devaev
e9e7f9bd05 pikvm/pikvm#1341: Web: Switch to maximize tab by default 2024-11-07 00:34:24 +02:00
mofeng-git
72dce4de89 #46 修复 CD-ROM 与 FLASH 模式网页无法切换
挂载 MSD 时重启 UDC 确保模式生效
2024-11-05 17:54:38 +00:00
Maxim Devaev
f1503d69e0 pikvm/pikvm#1207: Draw UI tips via meta.yaml 2024-11-05 18:17:04 +02:00
mofeng-git
de5cb73b93 更新说明 2024-11-05 02:15:03 +00:00
mofeng-git
0751b519c2 #44 添加 docker 网页音频支持
使用作者修改版 ustreamer
H.264/WebRTC 模式下音频可用
H.264/WebRTC 模式下网页录制视频包含音频
2024-11-05 02:12:00 +00:00
Maxim Devaev
0010dd1d11 pikvm/pikvm#1420: VNC: Ignore CUT event 3 seconds after connection 2024-11-04 18:59:50 +02:00
Maxim Devaev
7ef2e16b51 minor partial state fixes 2024-11-04 18:06:16 +02:00
mofeng-git
1a13760df0 #44 添加视频录制支持
使用浏览器前端 API
支持 mjpeg 和 h.264 模式下的视频录制
录制格式为wbem(vp8)
2024-11-04 13:25:18 +00:00
Maxim Devaev
d93639ba8d hid with granularity prototype 2024-11-03 18:28:28 +02:00
Maxim Devaev
1e277c0f06 lint fix 2024-11-02 21:04:57 +02:00
Maxim Devaev
95597b15e4 fix 2024-11-02 20:03:00 +02:00
mofeng-git
6fbfc2b343 43 修复 docker 相对鼠标设备模式无法使用的问题 2024-11-02 17:11:18 +00:00
mofeng-git
b893f27285 #43 修复 docker 相对鼠标设备模式无法使用的问题 2024-11-02 17:02:37 +00:00
Maxim Devaev
28167c4b45 fixed ocr null event handling 2024-11-02 18:48:14 +02:00
Maxim Devaev
5aef0a2193 refactoring 2024-11-02 18:47:59 +02:00
Maxim Devaev
0fd1174bc5 granularity info and minor fixes 2024-11-02 18:06:52 +02:00
Maxim Devaev
d4fb640418 refactoring 2024-11-02 14:46:48 +02:00
Maxim Devaev
d6b61cb407 refactoring 2024-11-02 14:26:39 +02:00
Maxim Devaev
8192b1fa95 simplified stream js logic 2024-11-02 10:39:43 +02:00
Maxim Devaev
deba110cdf partial msd events 2024-11-02 10:39:15 +02:00
Maxim Devaev
936cc21c40 Using disablePictureInPicture="true" 2024-10-30 11:30:45 +02:00
Maxim Devaev
47778bc48c msd: ftruncate() for uploading 2024-10-29 19:50:27 +02:00
Maxim Devaev
c02bc53bc4 msd: reload parts from inotify loop 2024-10-29 13:35:39 +02:00
Maxim Devaev
546ac24b93 msd reset now leads to inotify restart 2024-10-29 11:01:18 +02:00
Maxim Devaev
2195acf2ff Don't watch inotify modify events because they fires on every write() 2024-10-28 17:20:13 +02:00
Maxim Devaev
60f413c1f4 refactoring 2024-10-28 10:46:12 +02:00
Maxim Devaev
a84242c9bc AioExclusiveRegion API is sync now 2024-10-26 15:51:33 +03:00
mofeng-git
efa865ec9c 更新说明 2024-10-25 05:41:24 +00:00
Maxim Devaev
399712c684 refactoring 2024-10-24 03:05:46 +03:00
Maxim Devaev
1ebc08eae8 fix 2024-10-23 23:12:34 +03:00
Maxim Devaev
684b9f629e send kvmd version to ws 2024-10-23 23:02:25 +03:00
Maxim Devaev
76d70d0838 new ocr event format 2024-10-23 22:14:47 +03:00
Maxim Devaev
a26aee3543 partial streamer events 2024-10-23 19:31:39 +03:00
Maxim Devaev
0e4a70e7b9 refactoring 2024-10-22 05:39:18 +03:00
Maxim Devaev
cda32a083f new events model 2024-10-21 17:46:59 +03:00
mofeng-git
11d8f26874 更新说明 2024-10-20 13:08:49 +00:00
mofeng-git
2929a925a2 为玩客云替换网络服务程序为 systemd-networkd
修复修改 mac 地址无法自动获取 ip 问题
2024-10-20 11:01:22 +00:00
Maxim Devaev
b67a232584 copy some msd dicts to avoid changing 2024-10-19 09:25:20 +03:00
Maxim Devaev
90d8e745e3 gpio diff events mode 2024-10-19 08:59:52 +03:00
Maxim Devaev
3852d0a456 refactoring 2024-10-18 13:25:03 +03:00
mofeng-git
f5bebbc43f 整合包适配我家云、虚拟机和中兴 B863AV3.2M 2024-10-13 22:54:51 +00:00
mofeng-git
6707cb9932 为整合包补全 python3-hid 依赖
修复 amd64 架构 docker 错误启用 MSD功能
2024-10-11 11:49:07 +00:00
mofeng-git
87c887a62b 深度适配私家云二代 2024-10-11 11:28:36 +00:00
mofeng-git
40505e7e00 添加私家云二代整合包制作脚本 2024-10-07 08:57:25 +00:00
Maxim Devaev
c1f408ea1a Bump version: 4.19 → 4.20 2024-10-06 21:04:17 +03:00
Maxim Devaev
5b0ca351d7 fixed platform gpio again 2024-10-06 21:03:39 +03:00
Maxim Devaev
b6869cfbec Bump version: 4.18 → 4.19 2024-10-06 20:14:42 +03:00
Maxim Devaev
1e11678260 fixed gpio platform-specific switches 2024-10-06 20:14:07 +03:00
Maxim Devaev
8c0953aafc Bump version: 4.17 → 4.18 2024-10-02 22:17:56 +03:00
Maxim Devaev
073f67ca1b pikvm/pikvm#1410: Fixed EDID file loader 2024-10-02 22:17:15 +03:00
Maxim Devaev
cb5c1e9e6d Bump version: 4.16 → 4.17 2024-10-02 03:37:29 +03:00
Maxim Devaev
8ce27dca3f pikvm/pikvm#1405: Fixed behaviour on duplicating gpio leds 2024-10-02 03:35:57 +03:00
Maxim Devaev
f4ba4210e1 fixed post params 2024-10-02 03:32:54 +03:00
Maxim Devaev
4e1d9815cd pikvm/pikvm#1407: Save keymap on macro recording 2024-10-02 02:45:59 +03:00
Maxim Devaev
8209ee2eb0 improved wm dialogs 2024-09-23 02:32:38 +03:00
Maxim Devaev
5ed368769c refactoring 2024-09-23 02:32:23 +03:00
Maxim Devaev
1217144ecd refactoring + some tools 2024-09-22 05:20:01 +03:00
Maxim Devaev
842ddc91a1 refactoring 2024-09-20 01:11:22 +03:00
Maxim Devaev
7a53f14456 refactoring 2024-09-18 04:37:43 +03:00
Maxim Devaev
45270a09d7 Bump version: 4.15 → 4.16 2024-09-17 17:59:19 +03:00
Maxim Devaev
f03ac695bd refactoring 2024-09-17 17:58:31 +03:00
Maxim Devaev
b3e836e553 pikvm/pikvm#1386: Setup STUN by IP 2024-09-17 17:53:55 +03:00
Maxim Devaev
c57334f214 refactoring 2024-09-16 23:07:38 +03:00
Maxim Devaev
b779c18530 Bump version: 4.14 → 4.15 2024-09-13 22:08:43 +03:00
Maxim Devaev
6ccd91a8d1 removed print() 2024-09-13 22:07:59 +03:00
Maxim Devaev
bd127c3fd3 Bump version: 4.13 → 4.14 2024-09-13 19:34:39 +03:00
Maxim Devaev
4bc2ca3c90 refactoring 2024-09-13 19:33:49 +03:00
Maxim Devaev
445e2e04e2 oled: sensors class 2024-09-12 17:05:35 +03:00
Maxim Devaev
489601bb96 Bump version: 4.12 → 4.13 2024-09-11 20:23:24 +03:00
Maxim Devaev
56da910ebe moved kvmd-oled to this repo 2024-09-11 20:22:49 +03:00
Maxim Devaev
40393acf67 Bump version: 4.11 → 4.12 2024-09-11 01:16:16 +03:00
Maxim Devaev
2123799e51 required ustreamer 6.16 2024-09-11 01:14:43 +03:00
Maxim Devaev
0bb35806ff Janus: Fixed OPUS mono audio in Chrome 2024-09-11 00:48:47 +03:00
Maxim Devaev
bbbc908af1 Bump version: 4.10 → 4.11 2024-09-08 01:59:50 +03:00
Maxim Devaev
8113c5748b new sponsors 2024-09-08 01:57:30 +03:00
Maxim Devaev
aa1ca3b329 Serial number to uppercase, more info in Avahi 2024-09-08 01:35:11 +03:00
Maxim Devaev
508d5fe606 Bump version: 4.9 → 4.10 2024-09-04 21:53:01 +03:00
Maxim Devaev
bc22a28022 removed avahi from deps 2024-09-04 21:52:20 +03:00
Maxim Devaev
80aa9de4cc Bump version: 4.8 → 4.9 2024-09-04 18:49:21 +03:00
Maxim Devaev
572a75d27b kvmd-gencert: US is a new default 2024-09-04 14:08:00 +03:00
Maxim Devaev
864a2af45e kvmd-bootconfig: ensure avahi service on ENABLE_AVAHI 2024-09-04 04:47:43 +03:00
Maxim Devaev
5f26fa4072 added avahi to deps 2024-09-04 04:42:17 +03:00
Maxim Devaev
af9023e8aa kvmd-bootconfig: provide ENABLE_AVAHI 2024-09-04 04:39:56 +03:00
Maxim Devaev
5c3ac4c9c1 pikvm/kvmd#170: alternative implementation 2024-09-04 03:03:48 +03:00
Maxim Devaev
fb9d860cf2 pikvm/kvmd#182: improved dbus_next fix 2024-08-30 19:52:11 +03:00
czo
5045d8b3d7
silence the systemd/dbus exception if there are no matching services (#182) 2024-08-30 19:30:31 +03:00
Maxim Devaev
cc66fbf1df Bump version: 4.7 → 4.8 2024-08-27 15:51:43 +03:00
Maxim Devaev
9dc2af0356 kvmd-edidconf: removed --fix-edid-checksums 2024-08-27 15:51:07 +03:00
Maxim Devaev
99fcbdda05 lint fix 2024-08-27 01:49:17 +03:00
Maxim Devaev
308911191a testenv: restored eslint 2024-08-27 01:48:52 +03:00
Maxim Devaev
0c213add4a pst: changed data root to /var/lib/kvmd/pst 2024-08-27 01:48:30 +03:00
Maxim Devaev
3837e1a1c8 Simplified inotify API 2024-08-25 01:24:12 +03:00
Maxim Devaev
8569ed406a Bump version: 4.6 → 4.7 2024-08-24 23:07:05 +03:00
Maxim Devaev
4772c2b6c3 Since 1.28.1, v4l2-ctl deprecated --fix-edid-checksums and made thid behaviour default 2024-08-24 23:05:49 +03:00
Maxim Devaev
e6b775089f Bump version: 4.5 → 4.6 2024-08-20 07:15:03 +03:00
Maxim Devaev
721a80ef03 fixed pst chgrp and chmod 2024-08-20 07:14:28 +03:00
Maxim Devaev
a55948bf8e Bump version: 4.4 → 4.5 2024-08-20 05:45:00 +03:00
Maxim Devaev
39422f37ac sticky pst 2024-08-20 05:43:47 +03:00
Maxim Devaev
06b69d3dde Bump version: 4.3 → 4.4 2024-08-19 01:06:34 +03:00
Maxim Devaev
c9405efa05 lint fix 2024-08-19 01:06:00 +03:00
Maxim Devaev
abedace4b3 enable v4p by default 2024-08-19 00:43:32 +03:00
376 changed files with 25845 additions and 12794 deletions

View File

@ -1,7 +1,7 @@
[bumpversion] [bumpversion]
commit = True commit = True
tag = True tag = True
current_version = 4.3 current_version = 4.94
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)? parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize = serialize =
{major}.{minor} {major}.{minor}

36
.github/ISSUE_TEMPLATE/bug-反馈.md vendored Normal file
View File

@ -0,0 +1,36 @@
---
name: BUG 反馈
about: 反馈你所遇到的软件 BUG 或其他错误
title: "[BUG]"
labels: BUG
assignees: ''
---
### **Bug 反馈**
**问题描述**
请清晰描述您遇到的问题。例如:软件无法启动、特定功能报错或表现异常等。
**复现步骤**
请提供可复现此问题的详细步骤:
1. 前往 '...'
2. 点击 '....'
3. 滚动到 '....'
4. 发现错误
**日志信息**
如果程序崩溃或报错,请在此处粘贴相关的日志。
- **整合包镜像**: `systemctl status kvmd``journalctl -xeu kvmd`
- **Docker 镜像**: `docker logs kvmd`
**系统环境**
- **运行方式**: (例如:整合包镜像 / Docker)
- **镜像版本**: (Docker 镜像请提供版本号)
- **操作系统**: (例如Debian 12)
**尝试过的解决方法**
请简要描述您为解决此问题已尝试过的方法及其结果。如果未尝试,可留空。
**补充信息**
可以附加截图、录屏或其他有助于理解问题的信息。

View File

@ -0,0 +1,25 @@
---
name: 功能请求与设备适配
about: 请求新的功能或适配新的平台
title: "[功能/适配]"
labels: 特性
assignees: ''
---
**功能描述**
请详细描述您期望的新功能应该是什么样子。
- **对于新功能**:它应该如何工作?有哪些关键特性?
- **对于新平台适配**:请提供该平台的具体信息(如设备型号、系统版本、相关链接等)。
**期望的效果**
当该功能实现或平台适配完成后,您期望达到怎样的理想效果?可以像下面这样列出关键点:
- [ ] 用户可以...
- [ ] 系统能够...
- [ ] 解决了之前的...问题
**我能提供的帮助**
为了让这个想法更快成为现实,您可以提供哪些帮助?没有则填写无。
- [ ] 我可以参与后续的功能测试
- [ ] 我可以提供(临时的)远程调试环境(如 SSH、远程桌面
- [ ] 其他:...

View File

@ -1,23 +0,0 @@
name: Arduino HID CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
container:
image: python
steps:
- uses: actions/checkout@v3
- name: Prepare platformio
run: pip install platformio
- name: Build all
run: make -C hid/arduino _build_all

210
.github/workflows/build_img.yaml vendored Normal file
View File

@ -0,0 +1,210 @@
name: Build One-KVM Image
on:
workflow_dispatch:
inputs:
device_target:
description: 'Target device to build'
required: true
type: choice
options:
- onecloud
- onecloud-pro
- cumebox2
- chainedbox
- vm
- e900v22c
- octopus-flanet
- orangepi-zero
- oec-turbo
- all
create_release:
description: 'Create GitHub Release'
required: false
default: true
type: boolean
release_name:
description: 'Custom release name (optional)'
required: false
type: string
env:
BUILD_DATE: ""
GIT_SHA: ""
RELEASE_TAG: ""
jobs:
build:
runs-on: ubuntu-22.04
container:
image: node:18
options: --user root --privileged
env:
TZ: Asia/Shanghai
volumes:
- /dev:/dev
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Inject TURN config (optional)
if: ${{ env.TURN_HOST != '' }}
run: |
mkdir -p configs/kvmd/override.d
cat > configs/kvmd/override.d/turn.yaml <<EOF
janus:
stun:
host: ${TURN_HOST}
port: ${TURN_PORT}
local_ice_servers:
- urls:
- "stun:${TURN_HOST}:${TURN_PORT}"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
username: "${TURN_USER}"
credential: "${TURN_PASS}"
EOF
env:
TURN_HOST: ${{ secrets.TURN_HOST }}
TURN_PORT: ${{ secrets.TURN_PORT }}
TURN_USER: ${{ secrets.TURN_USER }}
TURN_PASS: ${{ secrets.TURN_PASS }}
- name: Set build environment
id: build_env
shell: bash
run: |
BUILD_DATE=$(date +%y%m%d-%H%M)
# 使用 GitHub 提供的环境变量避免 Git 权限问题
GIT_SHA="${GITHUB_SHA:0:7}"
GIT_BRANCH="${GITHUB_REF_NAME}"
echo "BUILD_DATE=$BUILD_DATE" >> $GITHUB_ENV
echo "GIT_SHA=$GIT_SHA" >> $GITHUB_ENV
echo "GIT_BRANCH=$GIT_BRANCH" >> $GITHUB_ENV
# 生成唯一但不创建新分支的标识符
RELEASE_TAG="build-$BUILD_DATE-${{ github.event.inputs.device_target }}-$GIT_SHA"
echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV
echo "Build environment:"
echo "- Date: $BUILD_DATE"
echo "- Git SHA: $GIT_SHA"
echo "- Git Branch: $GIT_BRANCH"
echo "- Release Tag: $RELEASE_TAG"
- name: Install dependencies
run: |
apt-get update
export DEBIAN_FRONTEND=noninteractive
apt-get install -y --no-install-recommends \
sudo tzdata docker.io qemu-utils qemu-user-static binfmt-support parted e2fsprogs \
curl tar python3 python3-pip rsync git android-sdk-libsparse-utils coreutils zerofree wget \
file tree
apt-get clean
rm -rf /var/lib/apt/lists/*
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime
echo $TZ > /etc/timezone
update-binfmts --enable
env:
DEBIAN_FRONTEND: noninteractive
- name: Build image
id: build
shell: bash
run: |
set -eo pipefail
echo "=== Build Configuration ==="
echo "Target: ${{ github.event.inputs.device_target }}"
echo "Build Date: $BUILD_DATE"
echo "Git SHA: $GIT_SHA"
echo "Git Branch: $GIT_BRANCH"
echo "Output Directory: ${{ github.workspace }}/output"
echo "=========================="
mkdir -p "${{ github.workspace }}/output"
chmod +x build/build_img.sh
echo "Starting build process..."
if bash build/build_img.sh ${{ github.event.inputs.device_target }}; then
echo "BUILD_SUCCESS=true" >> $GITHUB_OUTPUT
echo "Build completed successfully!"
else
echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT
echo "Build failed!" >&2
exit 1
fi
env:
CI_PROJECT_DIR: ${{ github.workspace }}
GITHUB_ACTIONS: true
OUTPUTDIR: ${{ github.workspace }}/output
- name: Collect build artifacts
id: artifacts
run: |
cd "${{ github.workspace }}/output"
echo "=== Build Artifacts ==="
if [ -d "${{ github.workspace }}/output" ]; then
find . -name "*.xz" | head -20
# 统计xz文件信息
ARTIFACT_COUNT=$(find . -name "*.xz" | wc -l)
TOTAL_SIZE=$(du -sh . | cut -f1)
echo "ARTIFACT_COUNT=$ARTIFACT_COUNT" >> $GITHUB_OUTPUT
echo "TOTAL_SIZE=$TOTAL_SIZE" >> $GITHUB_OUTPUT
else
echo "No output directory found!"
echo "ARTIFACT_COUNT=0" >> $GITHUB_OUTPUT
echo "TOTAL_SIZE=0" >> $GITHUB_OUTPUT
fi
echo "======================"
- name: Create GitHub Release
if: steps.build.outputs.BUILD_SUCCESS == 'true' && github.event.inputs.create_release == 'true'
id: release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.RELEASE_TAG }}
name: ${{ github.event.inputs.release_name || format('One-KVM {0} 构建镜像 ({1})', github.event.inputs.device_target, env.BUILD_DATE) }}
body: |
## 📦 GitHub Actions 镜像构建
### 构建信息
- **目标设备**: `${{ github.event.inputs.device_target }}`
- **构建时间**: `${{ env.BUILD_DATE }}`
- **Git 提交**: `${{ env.GIT_SHA }}` (分支: `${{ env.GIT_BRANCH }}`)
- **构建环境**: GitHub Actions (Ubuntu 22.04)
- **工作流ID**: `${{ github.run_id }}`
files: ${{ github.workspace }}/output/*.xz
prerelease: true
make_latest: false
generate_release_notes: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build summary
if: always()
run: |
echo "## 📋 构建摘要" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| 项目 | 值 |" >> $GITHUB_STEP_SUMMARY
echo "|------|-----|" >> $GITHUB_STEP_SUMMARY
echo "| **目标设备** | \`${{ github.event.inputs.device_target }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **构建时间** | \`${{ env.BUILD_DATE }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **Git SHA** | \`${{ env.GIT_SHA }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **Git 分支** | \`${{ env.GIT_BRANCH }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **构建状态** | ${{ steps.build.outputs.BUILD_SUCCESS == 'true' && '✅ 成功' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.build.outputs.BUILD_SUCCESS }}" = "true" ]; then
echo "| **构建产物** | ${{ steps.artifacts.outputs.ARTIFACT_COUNT || '0' }} 个文件 (${{ steps.artifacts.outputs.TOTAL_SIZE || '0' }}) |" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event.inputs.create_release }}" = "true" ]; then
echo "| **Release** | [${{ env.RELEASE_TAG }}](${{ steps.release.outputs.url }}) |" >> $GITHUB_STEP_SUMMARY
fi
fi

240
.github/workflows/docker-build.yaml vendored Normal file
View File

@ -0,0 +1,240 @@
name: Build and Push Docker Image
on:
workflow_dispatch:
inputs:
build_type:
description: 'Build type'
required: true
type: choice
options:
- stage-0
- dev
- release
version:
description: 'Version tag (for main image)'
required: false
default: 'latest'
type: string
platforms:
description: 'Target platforms'
required: false
default: 'linux/amd64,linux/arm64,linux/arm/v7'
type: string
enable_aliyun:
description: 'Push to Aliyun Registry'
required: false
default: true
type: boolean
env:
DOCKERHUB_REGISTRY: docker.io
ALIYUN_REGISTRY: registry.cn-hangzhou.aliyuncs.com
STAGE0_IMAGE: kvmd-stage-0
MAIN_IMAGE: kvmd
jobs:
build-stage-0:
runs-on: ubuntu-22.04
if: github.event.inputs.build_type == 'stage-0'
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Inject TURN config (optional)
if: ${{ env.TURN_HOST != '' }}
run: |
mkdir -p configs/kvmd/override.d
cat > configs/kvmd/override.d/turn.yaml <<EOF
janus:
stun:
host: ${TURN_HOST}
port: ${TURN_PORT}
local_ice_servers:
- urls:
- "stun:${TURN_HOST}:${TURN_PORT}"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
username: "${TURN_USER}"
credential: "${TURN_PASS}"
EOF
env:
TURN_HOST: ${{ secrets.TURN_HOST }}
TURN_PORT: ${{ secrets.TURN_PORT }}
TURN_USER: ${{ secrets.TURN_USER }}
TURN_PASS: ${{ secrets.TURN_PASS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
platforms: ${{ github.event.inputs.platforms }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Aliyun Registry
if: github.event.inputs.enable_aliyun == 'true'
uses: docker/login-action@v3
with:
registry: ${{ env.ALIYUN_REGISTRY }}
username: ${{ secrets.ALIYUN_USERNAME }}
password: ${{ secrets.ALIYUN_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
silentwind0/${{ env.STAGE0_IMAGE }}
${{ github.event.inputs.enable_aliyun == 'true' && format('{0}/silentwind/{1}', env.ALIYUN_REGISTRY, env.STAGE0_IMAGE) || '' }}
tags: |
type=raw,value=latest
type=raw,value=latest-{{date 'YYYYMMDD-HHmmss'}}
type=sha,prefix={{branch}}-
labels: |
org.opencontainers.image.title=One-KVM Stage-0 Base Image
org.opencontainers.image.description=Base image for One-KVM build environment
org.opencontainers.image.vendor=One-KVM Project
- name: Build and push stage-0 image
uses: docker/build-push-action@v5
with:
context: .
file: ./build/Dockerfile-stage-0
platforms: ${{ github.event.inputs.platforms }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=stage-0
cache-to: type=gha,mode=max,scope=stage-0
provenance: false
sbom: false
allow: security.insecure
build-main:
runs-on: ubuntu-22.04
if: github.event.inputs.build_type != 'stage-0'
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Inject TURN config (optional)
if: ${{ env.TURN_HOST != '' }}
run: |
mkdir -p configs/kvmd/override.d
cat > configs/kvmd/override.d/turn.yaml <<EOF
janus:
stun:
host: ${TURN_HOST}
port: ${TURN_PORT}
local_ice_servers:
- urls:
- "stun:${TURN_HOST}:${TURN_PORT}"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
username: "${TURN_USER}"
credential: "${TURN_PASS}"
EOF
env:
TURN_HOST: ${{ secrets.TURN_HOST }}
TURN_PORT: ${{ secrets.TURN_PORT }}
TURN_USER: ${{ secrets.TURN_USER }}
TURN_PASS: ${{ secrets.TURN_PASS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
platforms: ${{ github.event.inputs.platforms }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Aliyun Registry
if: github.event.inputs.enable_aliyun == 'true'
uses: docker/login-action@v3
with:
registry: ${{ env.ALIYUN_REGISTRY }}
username: ${{ secrets.ALIYUN_USERNAME }}
password: ${{ secrets.ALIYUN_PASSWORD }}
- name: Set version tag
id: version
run: |
if [[ "${{ github.event.inputs.build_type }}" == "dev" ]]; then
echo "tag=dev" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.inputs.build_type }}" == "release" ]]; then
echo "tag=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
fi
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
silentwind0/${{ env.MAIN_IMAGE }}
${{ github.event.inputs.enable_aliyun == 'true' && format('{0}/silentwind/{1}', env.ALIYUN_REGISTRY, env.MAIN_IMAGE) || '' }}
tags: |
type=raw,value=${{ steps.version.outputs.tag }}
type=raw,value=${{ steps.version.outputs.tag }}-{{date 'YYYYMMDD-HHmmss'}}
type=sha,prefix={{branch}}-
labels: |
org.opencontainers.image.title=One-KVM
org.opencontainers.image.description=DIY IP-KVM solution based on PiKVM
org.opencontainers.image.vendor=One-KVM Project
org.opencontainers.image.version=${{ steps.version.outputs.tag }}
- name: Build and push main image
uses: docker/build-push-action@v5
with:
context: .
file: ./build/Dockerfile
platforms: ${{ github.event.inputs.platforms }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=main
cache-to: type=gha,mode=max,scope=main
provenance: false
sbom: false
- name: Build summary
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- **Build Type**: ${{ github.event.inputs.build_type }}" >> $GITHUB_STEP_SUMMARY
echo "- **Version Tag**: ${{ steps.version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Platforms**: ${{ github.event.inputs.platforms }}" >> $GITHUB_STEP_SUMMARY
echo "- **Aliyun Enabled**: ${{ github.event.inputs.enable_aliyun }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' >> $GITHUB_STEP_SUMMARY

View File

@ -1,41 +0,0 @@
name: Pico HID Release
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Installing deps ...
run: sudo apt-get install cmake gcc-arm-none-eabi build-essential
- name: Building ...
run: make -C hid/pico all
- name: Releasing ...
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Uploading firmware ...
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./hid/pico/hid.uf2
asset_name: pico-hid.uf2
asset_content_type: application/octet-stream

View File

@ -1,20 +0,0 @@
name: Pico HID CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Installing deps ...
run: sudo apt-get install cmake gcc-arm-none-eabi build-essential
- name: Running tests ...
run: make -C hid/pico all

View File

@ -1,20 +0,0 @@
name: TOX CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Building testenv ...
run: make testenv
- name: Running tests ...
run: make tox CMD="tox -c testenv/tox.ini"

4
.gitignore vendored
View File

@ -1,5 +1,6 @@
/pkg/ /pkg/
/src/ /src/**/*.img
/src/tmp
/site/ /site/
/dist/ /dist/
/kvmd.egg-info/ /kvmd.egg-info/
@ -20,3 +21,4 @@
/venv/ /venv/
.vscode/settings.j/son .vscode/settings.j/son
kvmd_config/ kvmd_config/
CLAUDE.md

16
LICENSE
View File

@ -1,11 +1,7 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
<<<<<<< HEAD
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
=======
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
>>>>>>> origin/dev
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -649,11 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
<<<<<<< HEAD
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
=======
along with this program. If not, see <http://www.gnu.org/licenses/>.
>>>>>>> origin/dev
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
@ -672,19 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU GPL, see
<<<<<<< HEAD
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
=======
<http://www.gnu.org/licenses/>.
>>>>>>> origin/dev
The GNU General Public License does not permit incorporating your program The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<<<<<<< HEAD
<https://www.gnu.org/licenses/why-not-lgpl.html>. <https://www.gnu.org/licenses/why-not-lgpl.html>.
=======
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
>>>>>>> origin/dev

View File

@ -4,7 +4,8 @@ TESTENV_IMAGE ?= kvmd-testenv
TESTENV_HID ?= /dev/ttyS10 TESTENV_HID ?= /dev/ttyS10
TESTENV_VIDEO ?= /dev/video0 TESTENV_VIDEO ?= /dev/video0
TESTENV_GPIO ?= /dev/gpiochip0 TESTENV_GPIO ?= /dev/gpiochip0
TESTENV_RELAY ?= $(if $(shell ls /dev/hidraw0 2>/dev/null || true),/dev/hidraw0,) TESTENV_RELAY ?=
#TESTENV_RELAY ?= $(if $(shell ls /dev/hidraw0 2>/dev/null || true),/dev/hidraw0,)
LIBGPIOD_VERSION ?= 1.6.3 LIBGPIOD_VERSION ?= 1.6.3
@ -28,6 +29,8 @@ all:
@ echo " make testenv # Build test environment" @ echo " make testenv # Build test environment"
@ echo " make tox # Run tests and linters" @ echo " make tox # Run tests and linters"
@ echo " make tox E=pytest # Run selected test environment" @ echo " make tox E=pytest # Run selected test environment"
@ echo " make tox-local # Run tests and linters locally (no Docker)"
@ echo " make tox-local E=flake8 # Run selected test locally"
@ echo " make gpio # Create gpio mockup" @ echo " make gpio # Create gpio mockup"
@ echo " make run # Run kvmd" @ echo " make run # Run kvmd"
@ echo " make run CMD=... # Run specified command inside kvmd environment" @ echo " make run CMD=... # Run specified command inside kvmd environment"
@ -86,7 +89,9 @@ tox: testenv
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ && cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
&& mkdir -p /etc/kvmd/override.d \ && mkdir -p /etc/kvmd/override.d \
&& cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ && cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
&& cd /src \ && cd /src \
@ -94,14 +99,19 @@ tox: testenv
" "
tox-local:
@./check-code.sh $(if $(E),$(E),all)
$(TESTENV_GPIO): $(TESTENV_GPIO):
test ! -e $(TESTENV_GPIO) test ! -e $(TESTENV_GPIO)
sudo modprobe gpio-mockup gpio_mockup_ranges=0,40 sudo modprobe gpio_mockup gpio_mockup_ranges=0,40
test -c $(TESTENV_GPIO) test -c $(TESTENV_GPIO)
run: testenv $(TESTENV_GPIO) run: testenv $(TESTENV_GPIO)
- $(DOCKER) run --rm --name kvmd \ - $(DOCKER) run --rm --name kvmd \
--ipc=shareable \
--privileged \ --privileged \
--volume `pwd`/testenv/run:/run/kvmd:rw \ --volume `pwd`/testenv/run:/run/kvmd:rw \
--volume `pwd`/testenv:/testenv:ro \ --volume `pwd`/testenv:/testenv:ro \
@ -128,6 +138,7 @@ run: testenv $(TESTENV_GPIO)
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ && cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
&& ln -s /testenv/web.css /etc/kvmd/web.css \ && ln -s /testenv/web.css /etc/kvmd/web.css \
&& mkdir -p /etc/kvmd/override.d \ && mkdir -p /etc/kvmd/override.d \
@ -155,7 +166,9 @@ run-cfg: testenv
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ && cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
&& mkdir -p /etc/kvmd/override.d \ && mkdir -p /etc/kvmd/override.d \
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
&& $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \ && $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \
@ -178,6 +191,7 @@ run-ipmi: testenv
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ && cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
&& mkdir -p /etc/kvmd/override.d \ && mkdir -p /etc/kvmd/override.d \
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
@ -187,6 +201,7 @@ run-ipmi: testenv
run-vnc: testenv run-vnc: testenv
- $(DOCKER) run --rm --name kvmd-vnc \ - $(DOCKER) run --rm --name kvmd-vnc \
--ipc=container:kvmd \
--volume `pwd`/testenv/run:/run/kvmd:rw \ --volume `pwd`/testenv/run:/run/kvmd:rw \
--volume `pwd`/testenv:/testenv:ro \ --volume `pwd`/testenv:/testenv:ro \
--volume `pwd`/kvmd:/kvmd:ro \ --volume `pwd`/kvmd:/kvmd:ro \
@ -201,6 +216,7 @@ run-vnc: testenv
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ && cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
&& mkdir -p /etc/kvmd/override.d \ && mkdir -p /etc/kvmd/override.d \
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
@ -271,36 +287,24 @@ clean-all: testenv clean
.PHONY: testenv .PHONY: testenv
run-stage-0: run-stage-0:
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 \ $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 -t silentwind0/kvmd-stage-0 \
--allow security.insecure --progress plain \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-f build/Dockerfile-stage-0 . \
--push
$(DOCKER) buildx build -t silentwind0/kvmd-stage-0 \
--allow security.insecure --progress plain \ --allow security.insecure --progress plain \
--platform linux/amd64,linux/arm64,linux/arm/v7 \ --platform linux/amd64,linux/arm64,linux/arm/v7 \
-f build/Dockerfile-stage-0 . \ -f build/Dockerfile-stage-0 . \
--push --push
run-build-dev: run-build-dev:
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd:dev \ $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd:dev -t silentwind0/kvmd:dev \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-f build/Dockerfile . \
--push
$(DOCKER) buildx build -t silentwind0/kvmd:dev \
--platform linux/amd64,linux/arm64,linux/arm/v7 \ --platform linux/amd64,linux/arm64,linux/arm/v7 \
--build-arg CACHEBUST=$(date +%s) \
-f build/Dockerfile . \ -f build/Dockerfile . \
--push --push
run-build-release: run-build-release:
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd \ $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd -t silentwind0/kvmd \
--progress plain \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-f build/Dockerfile . \
--push
$(DOCKER) buildx build -t silentwind0/kvmd \
--progress plain \ --progress plain \
--platform linux/amd64,linux/arm64,linux/arm/v7 \ --platform linux/amd64,linux/arm64,linux/arm/v7 \
--build-arg CACHEBUST=$(date +%s) \
-f build/Dockerfile . \ -f build/Dockerfile . \
--push --push
@ -331,7 +335,7 @@ run-nogpio: testenv
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ && cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
&& ln -s /testenv/web.css /etc/kvmd/web.css \ && ln -s /testenv/web.css /etc/kvmd/web.css \
&& mkdir -p /etc/kvmd/override.d \ && mkdir -p /etc/kvmd/override.d \
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \

View File

@ -39,20 +39,22 @@ for _variant in "${_variants[@]}"; do
pkgname+=(kvmd-platform-$_platform-$_board) pkgname+=(kvmd-platform-$_platform-$_board)
done done
pkgbase=kvmd pkgbase=kvmd
pkgver=4.3 pkgver=4.94
pkgrel=1 pkgrel=1
pkgdesc="The main PiKVM daemon" pkgdesc="The main PiKVM daemon"
url="https://github.com/pikvm/kvmd" url="https://github.com/pikvm/kvmd"
license=(GPL) license=(GPL)
arch=(any) arch=(any)
depends=( depends=(
"python>=3.12" "python>=3.13"
"python<3.13" "python<3.14"
python-yaml python-yaml
python-aiohttp python-aiohttp
python-aiofiles python-aiofiles
python-async-lru python-async-lru
python-passlib python-passlib
# python-bcrypt is needed for passlib
python-bcrypt
python-pyotp python-pyotp
python-qrcode python-qrcode
python-periphery python-periphery
@ -66,7 +68,7 @@ depends=(
python-dbus python-dbus
python-dbus-next python-dbus-next
python-pygments python-pygments
python-pyghmi "python-pyghmi>=1.6.0-2"
python-pam python-pam
python-pillow python-pillow
python-xlib python-xlib
@ -77,6 +79,10 @@ depends=(
python-ldap python-ldap
python-zstandard python-zstandard
python-mako python-mako
python-luma-oled
python-pyusb
python-pyudev
python-evdev
"libgpiod>=2.1" "libgpiod>=2.1"
freetype2 freetype2
"v4l-utils>=1.22.1-1" "v4l-utils>=1.22.1-1"
@ -87,11 +93,11 @@ depends=(
iproute2 iproute2
dnsmasq dnsmasq
ipmitool ipmitool
"janus-gateway-pikvm>=0.14.2-3" "janus-gateway-pikvm>=1.3.0"
certbot certbot
platform-io-access platform-io-access
raspberrypi-utils raspberrypi-utils
"ustreamer>=6.11" "ustreamer>=6.37"
# Systemd UDEV bug # Systemd UDEV bug
"systemd>=248.3-2" "systemd>=248.3-2"
@ -117,7 +123,7 @@ depends=(
# fsck for /boot # fsck for /boot
dosfstools dosfstools
# pgrep for kvmd-udev-restart-pass # pgrep for kvmd-udev-restart-pass, sysctl for kvmd-otgnet
procps-ng procps-ng
# Misc # Misc
@ -131,6 +137,7 @@ conflicts=(
python-aiohttp-pikvm python-aiohttp-pikvm
platformio platformio
avrdude-pikvm avrdude-pikvm
kvmd-oled
) )
makedepends=( makedepends=(
python-setuptools python-setuptools
@ -159,12 +166,14 @@ package_kvmd() {
install -Dm755 -t "$pkgdir/usr/bin" scripts/kvmd-{bootconfig,gencert,certbot} install -Dm755 -t "$pkgdir/usr/bin" scripts/kvmd-{bootconfig,gencert,certbot}
install -Dm644 -t "$pkgdir/usr/lib/systemd/system" configs/os/services/* install -dm755 "$pkgdir/usr/lib/systemd/system"
cp -rd configs/os/services -T "$pkgdir/usr/lib/systemd/system"
install -DTm644 configs/os/sysusers.conf "$pkgdir/usr/lib/sysusers.d/kvmd.conf" install -DTm644 configs/os/sysusers.conf "$pkgdir/usr/lib/sysusers.d/kvmd.conf"
install -DTm644 configs/os/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/kvmd.conf" install -DTm644 configs/os/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/kvmd.conf"
mkdir -p "$pkgdir/usr/share/kvmd" mkdir -p "$pkgdir/usr/share/kvmd"
cp -r {hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd" cp -r {switch,hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd"
find "$pkgdir/usr/share/kvmd/web" -name '*.pug' -exec rm -f '{}' \; find "$pkgdir/usr/share/kvmd/web" -name '*.pug' -exec rm -f '{}' \;
local _cfg_default="$pkgdir/usr/share/kvmd/configs.default" local _cfg_default="$pkgdir/usr/share/kvmd/configs.default"
@ -194,6 +203,7 @@ package_kvmd() {
mkdir -p "$pkgdir/etc/kvmd/override.d" mkdir -p "$pkgdir/etc/kvmd/override.d"
mkdir -p "$pkgdir/var/lib/kvmd/"{msd,pst} mkdir -p "$pkgdir/var/lib/kvmd/"{msd,pst}
chmod 1775 "$pkgdir/var/lib/kvmd/pst"
} }
@ -206,7 +216,7 @@ for _variant in "${_variants[@]}"; do
cd \"kvmd-\$pkgver\" cd \"kvmd-\$pkgver\"
pkgdesc=\"PiKVM platform configs - $_platform for $_board\" pkgdesc=\"PiKVM platform configs - $_platform for $_board\"
depends=(kvmd=$pkgver-$pkgrel \"linux-rpi-pikvm>=6.6.21-3\") depends=(kvmd=$pkgver-$pkgrel \"linux-rpi-pikvm>=6.6.45-13\" \"raspberrypi-bootloader-pikvm>=20240818-1\")
backup=( backup=(
etc/sysctl.d/99-kvmd.conf etc/sysctl.d/99-kvmd.conf
@ -250,8 +260,12 @@ for _variant in "${_variants[@]}"; do
fi fi
if [[ $_platform =~ ^.*-hdmi$ ]]; then if [[ $_platform =~ ^.*-hdmi$ ]]; then
backup=(\"\${backup[@]}\" etc/kvmd/tc358743-edid.hex) backup=(\"\${backup[@]}\" etc/kvmd/tc358743-edid.hex etc/kvmd/switch-edid.hex)
install -DTm444 configs/kvmd/edid/$_base.hex \"\$pkgdir/etc/kvmd/tc358743-edid.hex\" install -DTm444 configs/kvmd/edid/$_base.hex \"\$pkgdir/etc/kvmd/tc358743-edid.hex\"
ln -s tc358743-edid.hex \"\$pkgdir/etc/kvmd/switch-edid.hex\"
else
backup=(\"\${backup[@]}\" etc/kvmd/switch-edid.hex)
install -DTm444 configs/kvmd/edid/_no-1920x1200.hex \"\$pkgdir/etc/kvmd/switch-edid.hex\"
fi fi
mkdir -p \"\$pkgdir/usr/share/kvmd\" mkdir -p \"\$pkgdir/usr/share/kvmd\"

307
README.en.md Normal file
View File

@ -0,0 +1,307 @@
<div align="center">
<img src="https://github.com/mofeng-git/Build-Armbian/assets/62919083/add9743a-0987-4e8a-b2cb-62121f236582" alt="One-KVM Logo" width="300">
<h1>One-KVM</h1>
<p><strong>DIY IP-KVM solution based on PiKVM</strong></p>
[![GitHub stars](https://img.shields.io/github/stars/mofeng-git/One-KVM?style=social)](https://github.com/mofeng-git/One-KVM/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/mofeng-git/One-KVM?style=social)](https://github.com/mofeng-git/One-KVM/network/members)
[![GitHub issues](https://img.shields.io/github/issues/mofeng-git/One-KVM)](https://github.com/mofeng-git/One-KVM/issues)
[![GitHub license](https://img.shields.io/github/license/mofeng-git/One-KVM)](https://github.com/mofeng-git/One-KVM/blob/master/LICENSE)
<p>
<a href="https://one-kvm.mofeng.run">📖 Documentation</a>
<a href="https://kvmd-demo.mofeng.run">🚀 Live Demo</a>
<a href="#quick-start">⚡ Quick Start</a>
<a href="#features">📊 Features</a>
</p>
</div>
[简体中文](README.md) | English
---
## 📋 Table of Contents
- [Overview](#project-overview)
- [Features](#features)
- [Quick Start](#quick-start)
- [Contributing](#contributing)
- [Others](#others)
## 📖 Project Overview
**One-KVM** is a DIY IP-KVM solution built upon the open-source [PiKVM](https://github.com/pikvm/pikvm) project. It uses cost-effective hardware to provide BIOS-level remote management for servers and workstations.
### Use Cases
- **Home lab management** Remotely manage servers and development devices
- **Server maintenance** Perform system maintenance without physical access
- **System recovery** Troubleshoot boot and BIOS/UEFI issues remotely
![One-KVM UI Screenshot](https://github.com/user-attachments/assets/a7848bca-e43c-434e-b812-27a45fad7910)
## 📊 Features
### Core Capabilities
| Feature | Description | Benefit |
|------|------|------|
| **Non-intrusive** | No software/driver required on the target machine | OS-agnostic; access BIOS/UEFI |
| **Cost-effective** | Leverages affordable hardware (TV boxes, dev boards) | Lower cost for KVM-over-IP |
| **Extendable** | Added utilities on top of PiKVM | Docker, recording, Chinese UI |
| **Deployment** | Supports Docker and prebuilt images | Preconfigured images for specific devices |
### Limitations
This project is maintained by an individual with limited resources and no commercial plan.
- No built-in free NAT punching/tunneling service
- No 24×7 technical support
- No guarantee on stability/compliance; use at your own risk
- User experience is optimized, but basic technical skills are still required
### Feature Comparison
> 💡 **Note:** The table below compares One-KVM with other PiKVM-based projects for reference only. If there are omissions or inaccuracies, please open an issue to help improve it.
| Feature | One-KVM | PiKVM | ArmKVM | BLIKVM |
|:--------:|:-------:|:-----:|:------:|:------:|
| Simplified Chinese WebUI | ✅ | ❌ | ✅ | ✅ |
| Remote video stream | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 |
| H.264 encoding | CPU | GPU | Unknown | GPU |
| Remote audio | ✅ | ✅ | ✅ | ✅ |
| Remote mouse/keyboard | OTG/CH9329 | OTG/CH9329/Pico/Bluetooth | OTG | OTG |
| VNC control | ✅ | ✅ | ✅ | ✅ |
| ATX power control | GPIO/USB relay | GPIO | GPIO | GPIO |
| Virtual drive mounting | ✅ | ✅ | ✅ | ✅ |
| Web terminal | ✅ | ✅ | ✅ | ✅ |
| Docker deployment | ✅ | ❌ | ❌ | ❌ |
| Commercial offering | ❌ | ✅ | ✅ | ✅ |
## ⚡ Quick Start
### Method 1: Docker (Recommended)
The Docker variant supports OTG or CH9329 as virtual HID and runs on Linux for amd64/arm64/armv7.
#### One-liner Script
```bash
curl -sSL https://one-kvm.mofeng.run/quick_start.sh -o quick_start.sh && bash quick_start.sh
```
#### Manual Deployment
It is recommended to use the `--net=host` network mode for better WOL functionality and WebRTC communication support.
Docker host network mode:
Port 8080: HTTP Web service
Port 4430: HTTPS Web service
Port 5900: VNC service
Port 623: IPMI service
Ports 20000-40000: WebRTC communication port range for low-latency video
Port 9 (UDP): Wake-on-LAN (WOL)
Docker host mode:
**Using OTG as virtual HID:**
```bash
sudo docker run --name kvmd -itd --privileged=true \
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
-v /sys/kernel/config:/sys/kernel/config -e OTG=1 \
--net=host \
silentwind0/kvmd
```
**Using CH9329 as virtual HID:**
```bash
sudo docker run --name kvmd -itd \
--device /dev/video0:/dev/video0 \
--device /dev/ttyUSB0:/dev/ttyUSB0 \
--net=host \
silentwind0/kvmd
```
Docker bridge mode:
**Using OTG as virtual HID:**
```bash
sudo docker run --name kvmd -itd --privileged=true \
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
-v /sys/kernel/config:/sys/kernel/config -e OTG=1 \
-p 8080:8080 -p 4430:4430 -p 5900:5900 -p 623:623 \
silentwind0/kvmd
```
**Using CH9329 as virtual HID:**
```bash
sudo docker run --name kvmd -itd \
--device /dev/video0:/dev/video0 \
--device /dev/ttyUSB0:/dev/ttyUSB0 \
-p 8080:8080 -p 4430:4430 -p 5900:5900 -p 623:623 \
silentwind0/kvmd
```
### Method 2: Flash Prebuilt One-KVM Images
Preconfigured images are provided for specific hardware platforms to simplify deployment and enable out-of-the-box experience.
#### Download
**GitHub:**
- **GitHub Releases:** [https://github.com/mofeng-git/One-KVM/releases](https://github.com/mofeng-git/One-KVM/releases)
**Other mirrors:**
- **No-login mirror:** [https://pan.huang1111.cn/s/mxkx3T1](https://pan.huang1111.cn/s/mxkx3T1)
- **Baidu Netdisk:** [https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj](https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj) (code: o9aj)
#### Supported Hardware Platforms
| Firmware | Codename | Hardware | Latest | Status |
|:--------:|:--------:|:--------:|:------:|:----:|
| OneCloud | Onecloud | USB capture card, OTG | 241018 | ✅ |
| CumeBox 2 | Cumebox2 | USB capture card, OTG | 241004 | ✅ |
| Vmare | Vmare-uefi | USB capture card, CH9329 | 241004 | ✅ |
| VirtualBox | Virtualbox-uefi | USB capture card, CH9329 | 241004 | ✅ |
| s905l3a Generic | E900v22c | USB capture card, OTG | 241004 | ✅ |
| Chainedbox | Chainedbox | USB capture card, OTG | 241004 | ✅ |
| Loongson 2K0300 | 2k0300 | USB capture card, CH9329 | 241025 | ✅ |
## 🤝 Contributing
Contributions of all kinds are welcome!
### How to Contribute
1. **Fork this repo**
2. **Create a feature branch:** `git checkout -b feature/AmazingFeature`
3. **Commit your changes:** `git commit -m 'Add some AmazingFeature'`
4. **Push to the branch:** `git push origin feature/AmazingFeature`
5. **Open a Pull Request**
### Report Issues
If you find bugs or have suggestions:
1. Open an issue via [GitHub Issues](https://github.com/mofeng-git/One-KVM/issues)
2. Provide detailed error logs and reproduction steps
3. Include your hardware and system information
### Sponsorship
This project builds upon many great open-source projects and requires considerable time for testing and maintenance. If you find it helpful, consider supporting via **[Afdian](https://afdian.com/a/silentwind)**.
#### Thanks
<details>
<summary><strong>Click to view the thank-you list</strong></summary>
- 浩龙的电子嵌入式之路
- Tsuki
- H_xiaoming
- 0蓝蓝0
- fairybl
- Will
- 浩龙的电子嵌入式之路
- 自.知
- 观棋不语٩ ི۶
- 爱发电用户_a57a4
- 爱发电用户_2c769
- 霜序
- 远方(闲鱼用户名:小远技术店铺)
- 爱发电用户_399fc
- 斐斐の
- 爱发电用户_09451
- 超高校级的錆鱼
- 爱发电用户_08cff
- guoke
- mgt
- 姜沢掵
- ui_beam
- 爱发电用户_c0dd7
- 爱发电用户_dnjK
- 忍者胖猪
- 永遠の願い
- 爱发电用户_GBrF
- 爱发电用户_fd65c
- 爱发电用户_vhNa
- 爱发电用户_Xu6S
- moss
- woshididi
- 爱发电用户_a0fd1
- 爱发电用户_f6bH
- 码农
- 爱发电用户_6639f
- jeron
- 爱发电用户_CN7y
- 爱发电用户_Up6w
- 爱发电用户_e3202
- ......
</details>
#### Sponsors
This project is supported by the following sponsors:
**CDN & Security:**
- **[Tencent EdgeOne](https://edgeone.ai/zh?from=github)** CDN acceleration and security protection
![Tencent EdgeOne](https://edgeone.ai/media/34fe3a45-492d-4ea4-ae5d-ea1087ca7b4b.png)
**File Storage:**
- **[Huang1111公益计划](https://pan.huang1111.cn/s/mxkx3T1)** No-login download service
## 📚 Others
### Open-source Projects Used
This project is built upon the following excellent open-source projects:
- [PiKVM](https://github.com/pikvm/pikvm) Open-source DIY IP-KVM solution

310
README.md
View File

@ -1,24 +1,136 @@
<h3 align=center><img src="https://github.com/mofeng-git/Build-Armbian/assets/62919083/add9743a-0987-4e8a-b2cb-62121f236582" alt="logo" width="300"><br></h3> <div align="center">
<h3 align=center><a href="https://github.com/mofeng-git/One-KVM/blob/master/README.md">简体中文</a> </h3> <img src="https://github.com/mofeng-git/Build-Armbian/assets/62919083/add9743a-0987-4e8a-b2cb-62121f236582" alt="One-KVM Logo" width="300">
<p align=right>&nbsp;</p> <h1>One-KVM</h1>
<p><strong>基于 PiKVM 的 DIY IP-KVM 解决方案</strong></p>
### 介绍 <p><a href="README.md">简体中文</a> | <a href="README.en.md">English</a></p>
One-KVM 是基于廉价计算机硬件和 [PiKVM]((https://github.com/pikvm/pikvm)) 软件二次开发的 BIOS 级远程控制项目。可以实现远程管理服务器或工作站,无需在被控机安装软件调整设置,实现无侵入式控制,适用范围广泛。 [![GitHub stars](https://img.shields.io/github/stars/mofeng-git/One-KVM?style=social)](https://github.com/mofeng-git/One-KVM/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/mofeng-git/One-KVM?style=social)](https://github.com/mofeng-git/One-KVM/network/members)
[![GitHub issues](https://img.shields.io/github/issues/mofeng-git/One-KVM)](https://github.com/mofeng-git/One-KVM/issues)
[![GitHub license](https://img.shields.io/github/license/mofeng-git/One-KVM)](https://github.com/mofeng-git/One-KVM/blob/master/LICENSE)
演示网站:[https://kvmd-demo.mofeng.run](https://kvmd-demo.mofeng.run) <p>
<a href="https://docs.one-kvm.cn">📖 详细文档</a>
<a href="https://demo.one-kvm.cn/">🚀 在线演示</a>
<a href="#快速开始">⚡ 快速开始</a>
<a href="#功能介绍">📊 功能介绍</a>
</p>
</div>
![image-20240926220156381](https://github.com/user-attachments/assets/a7848bca-e43c-434e-b812-27a45fad7910) ---
## 📋 目录
### 快速开始 - [项目概述](#项目概述)
- [功能介绍](#功能介绍)
- [快速开始](#快速开始)
- [贡献指南](#贡献指南)
- [其他](#其他)
**方式一Docker 镜像部署(推荐)** ## 📖 项目概述
Docker 版本可以使用 OTG 或 CH9329 作为虚拟 HID ,支持 amd64、arm64、armv7 架构的 Linux 系统安装。 **One-KVM** 是基于开源 [PiKVM](https://github.com/pikvm/pikvm) 项目进行二次开发的 DIY IP-KVM 解决方案。该方案利用成本较低的硬件设备,实现 BIOS 级别的远程服务器或工作站管理功能
> 本项目目前并无适配树莓派的计划。这是因为树莓派平台本质上属于 PiKVM 官方硬件生态和盈利的一部分。我们非常尊重和感谢上游项目 PiKVM ,因此 One-KVM 的设备适配主要聚焦于补充性场景,尽量避免与 PiKVM 官方产品产生重叠,以支持其可持续发展。
### 应用场景
- **家庭实验室主机管理** - 远程管理服务器和开发设备
- **服务器远程维护** - 无需物理接触即可进行系统维护
- **系统故障处理** - 远程解决系统启动和 BIOS 相关问题
![One-KVM 界面截图](https://github.com/user-attachments/assets/a7848bca-e43c-434e-b812-27a45fad7910)
## 📊 功能介绍
### 核心特性
| 特性 | 描述 | 优势 |
|------|------|------|
| **无侵入性** | 无需在目标机器上安装软件或驱动 | 不依赖操作系统,可访问 BIOS/UEFI 设置 |
| **成本效益** | 利用常见硬件设备(如电视盒子、开发板等) | 降低 KVM over IP 的实现成本 |
| **功能扩展** | 在 PiKVM 基础上增加实用功能 | Docker 部署、视频录制、中文界面 |
| **部署方式** | 支持 Docker 部署和硬件整合包 | 为特定硬件平台提供预配置方案 |
### 项目限制
本项目为个人维护的开源项目,资源有限,无商业运营计划
- 不提供内置免费内网穿透服务,相关问题请自行解决
- 不提供24×7小时技术支持服务
- 不承诺系统稳定性和合规性,使用风险需自行承担
- 尽力优化用户体验,但仍需要一定的技术基础
### 功能对比
> 💡 **说明:** 以下表格展示了 One-KVM 与其他基于 PiKVM 项目的功能对比,仅供参考。如有遗漏或错误,欢迎联系更正。
| 功能特性 | One-KVM | PiKVM | ArmKVM | BLIKVM |
|:--------:|:-------:|:-----:|:------:|:------:|
| 简体中文 WebUI | ✅ | ❌ | ✅ | ✅ |
| 远程视频流 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 |
| H.264 视频编码 | CPU/GPU | GPU | 未知 | GPU |
| 远程音频流 | ✅ | ✅ | ✅ | ✅ |
| 远程鼠键控制 | OTG/CH9329 | OTG/CH9329/Pico/Bluetooth | OTG | OTG |
| VNC 控制 | ✅ | ✅ | ✅ | ✅ |
| ATX 电源控制 | GPIO/USB 继电器 | GPIO | GPIO | GPIO |
| 虚拟存储驱动器挂载 | ✅ | ✅ | ✅ | ✅ |
| 网页终端 | ✅ | ✅ | ✅ | ✅ |
| Docker 部署 | ✅ | ❌ | ❌ | ❌ |
| 商业化运营 | ❌ | ✅ | ✅ | ✅ |
## ⚡ 快速开始
### 方式一Docker 镜像部署(推荐)
Docker 版本支持 OTG 或 CH9329 作为虚拟 HID兼容 amd64、arm64、armv7 架构的 Linux 系统。
#### 一键脚本部署
```bash
curl -sSL https://docs.one-kvm.cn/quick_start.sh -o quick_start.sh && bash quick_start.sh
```
#### 手动部署
推荐使用 --net=host 网络模式以获得更好的 wol 功能和 webrtc 通信支持。
docker host 网络模式:
端口 8080HTTP Web 服务
端口 4430HTTPS Web 服务
端口 5900VNC 服务
端口 623IPMI 服务
端口 20000-40000WebRTC 通信端口范围,用于低延迟视频传输
端口 9UDPWake-on-LANWOL唤醒功能
docker host 模式:
**使用 OTG 作为虚拟 HID**
```bash
sudo docker run --name kvmd -itd --privileged=true \
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
-v /sys/kernel/config:/sys/kernel/config -e OTG=1 \
--net=host \
silentwind0/kvmd
```
**使用 CH9329 作为虚拟 HID**
```bash
sudo docker run --name kvmd -itd \
--device /dev/video0:/dev/video0 \
--device /dev/ttyUSB0:/dev/ttyUSB0 \
--net=host \
silentwind0/kvmd
```
docker bridge 模式:
**使用 OTG 作为虚拟 HID**
如果使用 OTG 作为虚拟 HID可以使用如下部署命令
```bash ```bash
sudo docker run --name kvmd -itd --privileged=true \ sudo docker run --name kvmd -itd --privileged=true \
-v /lib/modules:/lib/modules:ro -v /dev:/dev \ -v /lib/modules:/lib/modules:ro -v /dev:/dev \
@ -27,7 +139,8 @@ sudo docker run --name kvmd -itd --privileged=true \
silentwind0/kvmd silentwind0/kvmd
``` ```
如果使用 CH9329可以使用如下部署命令 **使用 CH9329 作为虚拟 HID**
```bash ```bash
sudo docker run --name kvmd -itd \ sudo docker run --name kvmd -itd \
--device /dev/video0:/dev/video0 \ --device /dev/video0:/dev/video0 \
@ -36,65 +149,180 @@ sudo docker run --name kvmd -itd \
silentwind0/kvmd silentwind0/kvmd
``` ```
部署完成访问 https://IP:4430 ,点击信任自签证书即可开始使用默认账号密码admin/admin。 ### 方式二:直刷 One-KVM 整合包
如无法访问可以使用 `sudo docker logs kvmd` 命令查看日志尝试修复、提交 issue 或在 QQ 群内寻求帮助 针对特定硬件平台,提供了预配置的 One-KVM 打包镜像,简化部署流程,实现开箱即用
详细内容可以查阅 [One-KVM文档](https://one-kvm.mofeng.run/)。 #### 固件下载
**方式二:直刷 One-KVM 镜像** **GitHub 下载:**
- **GitHub Releases** [https://github.com/mofeng-git/One-KVM/releases](https://github.com/mofeng-git/One-KVM/releases)
对于玩客云设备,本项目 Releases 页可以找到适配玩客云的 One-KVM 预编译镜像。镜像名称带 One-KVM 前缀、burn 后缀的为线刷镜像,可使用 USB_Burning_Tool 软件线刷至玩客云。预编译线刷镜像为开箱即用,刷好后启动设备就可以开始使用 One-KVM。 **其他下载方式:**
- **免登录高速下载:** [http://sd1.files.one-kvm.cn/](http://sd1.files.one-kvm.cn/)(由群友赞助,支持直链,接入 EdgeOne CDN建议使用多线程下载工具下载获取最高速度
- **免登录下载:** [https://pan.huang1111.cn/s/mxkx3T1](https://pan.huang1111.cn/s/mxkx3T1) (由 Huang1111公益计划 提供)
- **百度网盘:** [https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj](https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj) 提取码o9aj
#### 支持的硬件平台
**赞助** | 固件型号 | 固件代号 | 硬件配置 | 最新版本 | 状态 |
|:--------:|:--------:|:--------:|:--------:|:----:|
| 玩客云 | Onecloud | USB 采集卡、OTG | 241018 | ✅ |
| 私家云二代 | Cumebox2 | USB 采集卡、OTG | 241004 | ✅ |
| Vmare | Vmare-uefi | USB 采集卡、CH9329 | 241004 | ✅ |
| Virtualbox | Virtualbox-uefi | USB 采集卡、CH9329 | 241004 | ✅ |
| s905l3a 通用包 | E900v22c | USB 采集卡、OTG | 241004 | ✅ |
| 我家云 | Chainedbox | USB 采集卡、OTG | 241004 | ✅ |
| 龙芯久久派 | 2k0300 | USB 采集卡、CH9329 | 241025 | ❌ |
这个项目基于众多开源项目二次开发,作者为此花费了大量的时间和精力进行测试和维护。若此项目对您有用,您可以考虑通过 [为爱发电](https://afdian.com/a/silentwind) 赞助一笔小钱支持作者。作者将能够购买新的硬件(玩客云和周边设备)来测试和维护 One-KVM 的各种配置,并在项目上投入更多的时间。 ### 报告问题
**感谢名单** 如果您发现了问题,请:
1. 使用 [GitHub Issues](https://github.com/mofeng-git/One-KVM/issues) 报告
2. 提供详细的错误信息和复现步骤
3. 包含您的硬件配置和系统信息
### 赞助支持
本项目基于多个优秀开源项目进行二次开发,作者投入了大量时间进行测试和维护。如果您觉得这个项目有价值,欢迎通过 **[为爱发电](https://afdian.com/a/silentwind)** 支持项目发展。
#### 感谢名单
<details> <details>
<summary><strong>点击查看感谢名单</strong></summary>
浩龙的电子嵌入式之路(赞助) - 浩龙的电子嵌入式之路
Tsuki赞助 - Tsuki
H_xiaoming - H_xiaoming
0蓝蓝0 - 0蓝蓝0
fairybl - fairybl
Will - Will
浩龙的电子嵌入式之路 - 浩龙的电子嵌入式之路
自.知 - 自.知
观棋不语٩ ི۶ - 观棋不语٩ ི۶
爱发电用户_a57a4 - 爱发电用户_a57a4
爱发电用户_2c769 - 爱发电用户_2c769
霜序 - 霜序
[远方](https://runyf.cn/) - 远方(闲鱼用户名:小远技术店铺)
爱发电用户_399fc - 爱发电用户_399fc
[斐斐の](https://www.mmuaa.com/) - 斐斐の
- 爱发电用户_09451
- 超高校级的錆鱼
- 爱发电用户_08cff
- guoke
- mgt
- 姜沢掵
- ui_beam
- 爱发电用户_c0dd7
- 爱发电用户_dnjK
- 忍者胖猪
- 永遠の願い
- 爱发电用户_GBrF
- 爱发电用户_fd65c
- 爱发电用户_vhNa
- 爱发电用户_Xu6S
- moss
- woshididi
- 爱发电用户_a0fd1
- 爱发电用户_f6bH
- 码农
- 爱发电用户_6639f
- jeron
- 爱发电用户_CN7y
- 爱发电用户_Up6w
- 爱发电用户_e3202
- 一语念白
- 云边
- 爱发电用户_5a711
- 爱发电用户_9a706
- T0m9ir1SUKI
- 爱发电用户_56d52
- 爱发电用户_3N6F
- DUSK
- 飘零
- .
- 饭太稀
- 葱
- ......
......
</details> </details>
本项目使用了下列开源项目: #### 赞助商
1. [pikvm/pikvm: Open and inexpensive DIY IP-KVM based on Raspberry Pi (github.com)](https://github.com/pikvm/pikvm)
**状态** 本项目得到以下赞助商的支持:
[![Star History Chart](https://api.star-history.com/svg?repos=mofeng-git/One-KVM&type=Date)](https://star-history.com/#mofeng-git/One-KVM&Date) **CDN 加速及安全防护:**
- **[Tencent EdgeOne](https://edgeone.ai/zh?from=github)** - 提供 CDN 加速及安全防护服务
![Github](https://repobeats.axiom.co/api/embed/7cfaab47e31073107771a7179078aa2a6c3f1108.svg "Repobeats analytics image") ![Tencent EdgeOne](https://edgeone.ai/media/34fe3a45-492d-4ea4-ae5d-ea1087ca7b4b.png)
**文件存储服务:**
- **[Huang1111公益计划](https://pan.huang1111.cn/s/mxkx3T1)** - 提供免登录下载服务
**云服务商**
- **[林枫云](https://www.dkdun.cn)** - 赞助了本项目宁波大带宽服务器
![林枫云](./img/36076FEFF0898A80EBD5756D28F4076C.png)
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
## 📚 其他
### 使用的开源项目
本项目基于以下优秀开源项目进行二次开发:
- [PiKVM](https://github.com/pikvm/pikvm) - 开源的 DIY IP-KVM 解决方案

View File

@ -1 +0,0 @@
[python: kvmd/**.py]

View File

@ -1,41 +1,123 @@
FROM silentwind0/kvmd-stage-0 AS builder FROM registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 AS builder
FROM python:3.12.0rc2-slim-bookworm FROM python:3.11.11-slim-bookworm
LABEL maintainer="mofeng654321@hotmail.com" LABEL maintainer="mofeng654321@hotmail.com"
ARG TARGETARCH
COPY --from=builder /tmp/lib/* /tmp/lib/ COPY --from=builder /tmp/lib/* /tmp/lib/
COPY --from=builder /tmp/ustreamer/ustreamer /tmp/ustreamer/ustreamer-dump /usr/bin/janus /usr/bin/ COPY --from=builder /tmp/ustreamer/ustreamer /tmp/ustreamer/ustreamer-dump /usr/bin/janus /usr/bin/
COPY --from=builder /tmp/wheel/*.whl /tmp/wheel/ COPY --from=builder /tmp/wheel/*.whl /tmp/wheel/
COPY --from=builder /tmp/ustreamer/libjanus_ustreamer.so /usr/lib/ustreamer/janus/ COPY --from=builder /tmp/ustreamer/libjanus_ustreamer.so /usr/lib/ustreamer/janus/
COPY --from=builder /usr/lib/janus/transports/* /usr/lib/janus/transports/ COPY --from=builder /usr/lib/janus/transports/* /usr/lib/janus/transports/
COPY --from=builder /tmp/arm64-libs.tar.gz* /tmp/
RUN if [ ${TARGETARCH} = arm64 ] && [ -f /tmp/arm64-libs.tar.gz ]; then \
cd / && tar -xzf /tmp/arm64-libs.tar.gz && rm -f /tmp/arm64-libs.tar.gz; \
fi
ARG TARGETARCH ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
ENV PYTHONDONTWRITEBYTECODE=1 TZ=Asia/Shanghai
ENV PYTHONUNBUFFERED=1
ENV TZ=Asia/Shanghai
RUN cp /tmp/lib/* /lib/*-linux-*/ \
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check /tmp/wheel/*.whl \
&& rm -rf /tmp/lib /tmp/wheel
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \ && apt-get update \
&& apt-get install -y --no-install-recommends libxkbcommon-x11-0 nginx tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim iptables sudo curl kmod \ && apt-get install -y --no-install-recommends \
libmicrohttpd12 libjansson4 libssl3 libsofia-sip-ua0 libglib2.0-0 libopus0 libogg0 libcurl4 libconfig9 libusrsctp2 libwebsockets17 libnss3 libasound2 \ libxkbcommon-x11-0 \
&& rm -rf /var/lib/apt/lists/* nginx \
tesseract-ocr \
RUN if [ ${TARGETARCH} = arm ]; then ARCH=armhf; elif [ ${TARGETARCH} = arm64 ]; then ARCH=aarch64; elif [ ${TARGETARCH} = amd64 ]; then ARCH=x86_64; fi \ tesseract-ocr-eng \
tesseract-ocr-chi-sim \
iptables \
sudo \
curl \
kmod \
libmicrohttpd12 \
libjansson4 \
libssl3 \
libsofia-sip-ua0 \
libglib2.0-0 \
libopus0 \
libogg0 \
libcurl4 \
libconfig9 \
libusrsctp2 \
libwebsockets17 \
libnss3 \
libasound2 \
libdrm2 \
libx264-164 \
libyuv0 \
nano \
unzip \
&& case ${TARGETARCH} in \
amd64) \
apt-get install -y --no-install-recommends \
libavcodec59 libavformat59 libavutil57 \
libswscale6 libavfilter8 libavdevice59 \
ffmpeg vainfo \
libva2 libva-drm2 libva-x11-2 \
mesa-va-drivers mesa-vdpau-drivers \
intel-media-va-driver i965-va-driver \
;; \
arm) \
apt-get install -y --no-install-recommends \
libavcodec59 libavformat59 libavutil57 \
libswscale6 libavfilter8 libavdevice59 \
v4l-utils libv4l-0 \
;; \
arm64) \
apt-get install -y --no-install-recommends \
v4l-utils libv4l-0 libavutil57 \
libstdc++6 libavcodec59 libavformat59 \
libswscale6 libavfilter8 libavdevice59 \
libva2 libva-drm2 libva-x11-2 \
libvdpau1 ocl-icd-libopencl1 \
;; \
*) \
echo "Unsupported architecture: ${TARGETARCH}" && exit 1 \
;; \
esac \
&& cp /tmp/lib/* /lib/*-linux-*/ \
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check /tmp/wheel/*.whl \
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check pyfatfs \
&& if [ ${TARGETARCH} = arm ]; then ARCH=armhf; \
elif [ ${TARGETARCH} = arm64 ]; then ARCH=aarch64; \
elif [ ${TARGETARCH} = amd64 ]; then ARCH=x86_64; \
fi \
&& curl https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.$ARCH -L -o /usr/local/bin/ttyd \ && curl https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.$ARCH -L -o /usr/local/bin/ttyd \
&& chmod +x /usr/local/bin/ttyd \ && chmod +x /usr/local/bin/ttyd \
&& mkdir -p /tmp/gostc && cd /tmp/gostc \
&& case ${TARGETARCH} in \
amd64) GOSTC_ARCH=amd64_v1 ;; \
arm) GOSTC_ARCH=arm_7 ;; \
arm64) GOSTC_ARCH=arm64_v8.0 ;; \
*) echo "Unsupported architecture for gostc: ${TARGETARCH}" && exit 1 ;; \
esac \
&& curl -L https://github.com/mofeng-git/gostc-open/releases/download/v2.0.8-beta.2/gostc_linux_${GOSTC_ARCH}.tar.gz -o gostc.tar.gz \
&& tar -xzf gostc.tar.gz \
&& mv gostc /usr/bin/ \
&& chmod +x /usr/bin/gostc \
&& cd / && rm -rf /tmp/gostc \
&& adduser kvmd --gecos "" --disabled-password \ && adduser kvmd --gecos "" --disabled-password \
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \ && ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
&& mkdir -p /etc/kvmd_backup/override.d /var/lib/kvmd/msd/images /var/lib/kvmd/msd/meta /var/lib/kvmd/pst/data /opt/vc/bin /run/kvmd /tmp/kvmd-nginx \ && mkdir -p /etc/kvmd_backup/override.d \
&& touch /run/kvmd/ustreamer.sock /var/lib/kvmd/msd/images \
/var/lib/kvmd/msd/meta \
/var/lib/kvmd/pst/data \
/var/lib/kvmd/msd/NormalFiles \
/opt/vc/bin \
/run/kvmd \
/tmp/kvmd-nginx \
&& touch /run/kvmd/ustreamer.sock \
&& groupadd kvmd-selfauth \
&& usermod -a -G kvmd-selfauth root \
&& apt clean \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /tmp/lib /tmp/wheel \
&& ustreamer -v
COPY testenv/fakes/vcgencmd scripts/kvmd* /usr/bin/
COPY testenv/fakes/vcgencmd /usr/bin/
COPY extras/ /usr/share/kvmd/extras/ COPY extras/ /usr/share/kvmd/extras/
COPY web/ /usr/share/kvmd/web/ COPY web/ /usr/share/kvmd/web/
COPY scripts/kvmd-gencert /usr/share/kvmd/ COPY scripts/kvmd-gencert /usr/share/kvmd/

View File

@ -1,70 +1,202 @@
# syntax = docker/dockerfile:experimental # syntax = docker/dockerfile:experimental
FROM python:3.12.0rc2-slim-bookworm AS builder FROM debian:bookworm-slim AS builder
ARG TARGETARCH ARG TARGETARCH
# 设置环境变量
ENV DEBIAN_FRONTEND=noninteractive \
PIP_NO_CACHE_DIR=1 \
RUSTUP_DIST_SERVER="https://mirrors.tuna.tsinghua.edu.cn/rustup"
# 更新源并安装依赖
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \ && apt-get update \
&& apt-get install -y --no-install-recommends build-essential libssl-dev libffi-dev python3-dev libevent-dev libjpeg-dev \ && apt-get install -y --no-install-recommends \
libbsd-dev libudev-dev git pkg-config wget curl libmicrohttpd-dev libjansson-dev libssl-dev libsofia-sip-ua-dev libglib2.0-dev \ python3-full \
libopus-dev libogg-dev libcurl4-openssl-dev liblua5.3-dev libconfig-dev libopus-dev libtool automake autoconf meson cmake \ python3-pip \
libx264-dev libyuv-dev libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev \ python3-dev \
build-essential \
libssl-dev \
libffi-dev \
python3-dev \
libevent-dev \
libjpeg-dev \
libbsd-dev \
libudev-dev \
git \
pkg-config \
wget \
curl \
libmicrohttpd-dev \
libjansson-dev \
libsofia-sip-ua-dev \
libglib2.0-dev \
libopus-dev \
libogg-dev \
libcurl4-openssl-dev \
liblua5.3-dev \
libconfig-dev \
libtool \
automake \
autoconf \
meson \
cmake \
libx264-dev \
libyuv-dev \
libasound2-dev \
libspeex-dev \
libspeexdsp-dev \
libusb-1.0-0-dev \
libldap2-dev \
libsasl2-dev \
libdrm-dev \
mesa-va-drivers \
mesa-vdpau-drivers \
v4l-utils \
libv4l-dev \
ffmpeg \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
libswscale-dev \
libavfilter-dev \
libavdevice-dev \
&& if [ ${TARGETARCH} != arm ] && [ ${TARGETARCH} != arm64 ]; then \
apt-get install -y --no-install-recommends \
vainfo \
libva-dev \
libva-drm2 \
libva-x11-2 \
intel-media-va-driver \
i965-va-driver; \
fi \
&& if [ ${TARGETARCH} = arm64 ]; then \
apt-get install -y --no-install-recommends \
ninja-build \
zlib1g-dev \
libswresample-dev; \
fi \
&& apt clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY build/cargo_config /tmp/config COPY build/cargo_config /tmp/config
# 配置 pip 源并安装 Python 依赖
RUN --security=insecure pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \ RUN --security=insecure pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \
&& if [ ${TARGETARCH} = arm ]; then \ && if [ ${TARGETARCH} = arm ]; then \
mkdir -p /root/.cargo \ mkdir -p /root/.cargo \
&& chmod 777 /root/.cargo && mount -t tmpfs none /root/.cargo \ && chmod 777 /root/.cargo && mount -t tmpfs none /root/.cargo \
&& export RUSTUP_DIST_SERVER="https://mirrors.tuna.tsinghua.edu.cn/rustup" \
#&& export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" \
&& wget https://sh.rustup.rs -O /root/rustup-init.sh \ && wget https://sh.rustup.rs -O /root/rustup-init.sh \
&& sh /root/rustup-init.sh -y \ && sh /root/rustup-init.sh -y \
&& export PATH=$PATH:/root/.cargo/bin \ && export PATH=$PATH:/root/.cargo/bin \
&& cp /tmp/config /root/.cargo/config.toml; \ && cp /tmp/config /root/.cargo/config.toml; \
fi \ fi \
&& pip wheel --wheel-dir=/tmp/wheel/ cryptography && pip install --root-user-action=ignore --disable-pip-version-check --upgrade --break-system-packages build setuptools pip \
&& pip wheel --wheel-dir=/tmp/wheel/ cryptography \
&& pip wheel --wheel-dir=/tmp/wheel/ \
aiofiles aiohttp appdirs asn1crypto async_lru async-timeout bottle cffi \
chardet click colorama dbus_next gpiod hidapi idna mako marshmallow \
more-itertools multidict netifaces packaging passlib pillow ply psutil \
pycparser pyelftools pyghmi pygments pyparsing pyotp qrcode requests \
semantic-version setproctitle six spidev tabulate urllib3 wrapt xlib \
yarl pyserial pyyaml zstandard supervisor pyfatfs pyserial python-periphery \
python-ldap python-pam pyrad pyudev pyusb luma.oled pyserial-asyncio \
&& rm -rf /root/.cache/pip/* /tmp/pip-* \
&& if [ ${TARGETARCH} = arm ]; then \
umount /root/.cargo 2>/dev/null || true \
&& rm -rf /root/.cargo /root/rustup-init.sh; \
fi
RUN pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check build \ # 编译 python evdev库
&& pip wheel --wheel-dir=/tmp/wheel/ aiofiles aiohttp appdirs asn1crypto async_lru async-timeout bottle cffi chardet click colorama \ RUN git clone --depth=1 https://github.com/gvalkov/python-evdev.git /tmp/python-evdev \
dbus_next gpiod hidapi idna mako marshmallow more-itertools multidict netifaces packaging passlib pillow ply psutil pycparser \ && cd /tmp/python-evdev \
pyelftools pyghmi pygments pyparsing pyotp qrcode requests semantic-version setproctitle setuptools six spidev \ && python3 setup.py bdist_wheel --dist-dir /tmp/wheel/ \
tabulate urllib3 wrapt xlib yarl pyserial pyyaml zstandard supervisor && rm -rf /tmp/python-evdev
RUN git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \ # 编译安装 libnice、libsrtp、libwebsockets 和 janus-gateway显式 Release 与按架构优化)
RUN export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
&& if [ "${TARGETARCH}" = arm64 ]; then export CFLAGS="$COMMON_CFLAGS -march=armv8-a"; \
elif [ "${TARGETARCH}" = arm ]; then export CFLAGS="$COMMON_CFLAGS -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -mtune=cortex-a7"; \
else export CFLAGS="$COMMON_CFLAGS -march=x86-64 -mtune=generic"; fi \
&& export CXXFLAGS="$CFLAGS" LDFLAGS="-Wl,-O1 -Wl,--as-needed" \
&& git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \
&& cd /tmp/libnice \ && cd /tmp/libnice \
&& meson --prefix=/usr build && ninja -C build && ninja -C build install && meson setup build --prefix=/usr --buildtype=release -Doptimization=2 -Dc_args="$CFLAGS" -Dcpp_args="$CXXFLAGS" \
&& ninja -C build && ninja -C build install \
RUN curl https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -L -o /tmp/libsrtp-2.2.0.tar.gz \ && rm -rf /tmp/libnice \
&& curl https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -L -o /tmp/libsrtp-2.2.0.tar.gz \
&& cd /tmp \ && cd /tmp \
&& tar xfv libsrtp-2.2.0.tar.gz \ && tar xf libsrtp-2.2.0.tar.gz \
&& cd libsrtp-2.2.0 \ && cd libsrtp-2.2.0 \
&& ./configure --prefix=/usr --enable-openssl \ && CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure --prefix=/usr --enable-openssl \
&& make shared_library && make install && make shared_library -j$(nproc) && make install \
&& cd /tmp \
RUN git clone --depth=1 https://libwebsockets.org/repo/libwebsockets /tmp/libwebsockets \ && rm -rf /tmp/libsrtp* \
&& git clone --depth=1 https://github.com/warmcat/libwebsockets /tmp/libwebsockets \
&& cd /tmp/libwebsockets \ && cd /tmp/libwebsockets \
&& mkdir build && cd build \ && mkdir build && cd build \
&& cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_C_FLAGS="-fpic" .. \ && cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr \
&& make && make install -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="$CFLAGS -fPIC" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS -fPIC" .. \
&& make -j$(nproc) && make install \
RUN git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \ && cd /tmp \
&& rm -rf /tmp/libwebsockets \
&& git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \
&& cd /tmp/janus-gateway \ && cd /tmp/janus-gateway \
&& sh autogen.sh \ && sh autogen.sh \
&& ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \ && CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \
--disable-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins --disable-all-loggers \ --disable-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins \
--prefix=/usr \ --disable-all-loggers --prefix=/usr \
&& make && make install && make -j$(nproc) && make install \
&& cd /tmp \
&& rm -rf /tmp/janus-gateway
# 编译 Rockchip MPP、RGA仅 arm64显式 Release 与按架构优化)
RUN if [ ${TARGETARCH} = arm64 ]; then \
export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
&& export CFLAGS="$COMMON_CFLAGS -march=armv8-a" \
&& export CXXFLAGS="$CFLAGS" \
&& git clone --depth=1 https://github.com/rockchip-linux/mpp.git /tmp/rkmpp \
&& mkdir -p /tmp/rkmpp/rkmpp_build && cd /tmp/rkmpp/rkmpp_build \
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DBUILD_TEST=OFF \
-DCMAKE_C_FLAGS_RELEASE="$CFLAGS" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS" .. \
&& make -j$(nproc) \
&& make install \
&& git clone -b jellyfin-rga --depth=1 https://github.com/nyanmisaka/rk-mirrors.git /tmp/rkrga \
&& cd /tmp/ \
&& meson setup rkrga rkrga_build --prefix=/usr --libdir=lib --buildtype=release -Doptimization=2 \
-Dc_args="$CFLAGS" -Dcpp_args="$CXXFLAGS -fpermissive" -Dlibdrm=false -Dlibrga_demo=false \
&& meson configure rkrga_build > /dev/null \
&& ninja -C rkrga_build install \
&& rm -rf /tmp/rkmpp /tmp/rkrga; \
fi
# 编译 ustreamer按架构优化
RUN sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h \ RUN sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h \
&& git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \ && git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \
&& make -j WITH_PYTHON=1 WITH_JANUS=1 WITH_LIBX264=1 -C /tmp/ustreamer \ && export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
&& /tmp/ustreamer/ustreamer -v && if [ "${TARGETARCH}" = arm64 ]; then export CFLAGS="$COMMON_CFLAGS -march=armv8-a"; \
elif [ "${TARGETARCH}" = arm ]; then export CFLAGS="$COMMON_CFLAGS -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -mtune=cortex-a7"; \
else export CFLAGS="$COMMON_CFLAGS -march=x86-64 -mtune=generic"; fi \
&& export CXXFLAGS="$CFLAGS" \
&& if [ ${TARGETARCH} = arm64 ]; then \
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_MPP=1 WITH_DRM=1 -C /tmp/ustreamer; \
else \
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_DRM=1 -C /tmp/ustreamer; \
fi \
&& /tmp/ustreamer/ustreamer -v \
&& /tmp/ustreamer/ustreamer-dump -v \
&& cp /tmp/ustreamer/python/dist/*.whl /tmp/wheel/
# 复制必要的库文件
RUN mkdir /tmp/lib \ RUN mkdir /tmp/lib \
&& cd /lib/*-linux-*/ \ && cd /lib/*-linux-*/ \
&& cp libevent_core-*.so.7 libbsd.so.0 libevent_pthreads-*.so.7 libspeexdsp.so.1 libevent-*.so.7 libjpeg.so.62 libx264.so.164 libyuv.so.0 \ && cp libevent_core-*.so.* libbsd.so.* libevent_pthreads-*.so.* libspeexdsp.so.* \
libnice.so.10 /usr/lib/libsrtp2.so.1 /usr/lib/libwebsockets.so.19 \ libevent-*.so.* libjpeg.so.* libyuv.so.* libnice.so.* \
/tmp/lib/ \ /tmp/lib/ \
&& cp /tmp/ustreamer/python/dist/*.whl /tmp/wheel/ && find /usr/lib -name "libsrtp2.so.*" -exec cp {} /tmp/lib/ \; \
&& find /usr/lib -name "libwebsockets.so.*" -exec cp {} /tmp/lib/ \; \
&& [ "${TARGETARCH}" = "arm64" ] && \
find /usr/lib -name "libsw*.so.*" -exec cp {} /tmp/lib/ \; && \
find /usr/lib -name "libpostproc.so.*" -exec cp {} /tmp/lib/ \; && \
find /usr/lib -name "librockchip*" -exec cp {} /tmp/lib/ \; && \
find /usr/lib -name "librga.so.*" -exec cp {} /tmp/lib/ \; || true

303
build/build_img.sh Normal file → Executable file
View File

@ -1,129 +1,208 @@
#!/bin/bash #!/bin/bash
#File List # --- 配置 ---
#src # 允许通过环境变量覆盖默认路径
#└── image SRCPATH="${SRCPATH:-/mnt/src}"
# ├── cumebox2 BOOTFS="${BOOTFS:-/tmp/bootfs}"
# │ └── Armbian_24.8.1_Khadas-vim1_bookworm_current_6.6.47_minimal.img ROOTFS="${ROOTFS:-/tmp/rootfs}"
# └── onecloud OUTPUTDIR="${OUTPUTDIR:-/mnt/output}"
# ├── AmlImg_v0.3.1_linux_amd64 TMPDIR="${TMPDIR:-$SRCPATH/tmp}"
# ├── Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal.burn.img
# └── rc.local
#预处理镜像文件 # 远程文件下载配置
SRCPATH=../src REMOTE_PREFIX="${REMOTE_PREFIX:-https://files.mofeng.run/src}"
ROOTFS=/tmp/rootfs
$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64 unpack $SRCPATH/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal.burn.img $SRCPATH/tmp
simg2img $SRCPATH/tmp/7.rootfs.PARTITION.sparse $SRCPATH/tmp/rootfs.img
dd if=/dev/zero of=/tmp/add.img bs=1M count=800 && cat /tmp/add.img >> $SRCPATH/tmp/rootfs.img && rm /tmp/add.img
e2fsck -f $SRCPATH/tmp/rootfs.img && resize2fs $SRCPATH/tmp/rootfs.img
#挂载镜像文件 export LC_ALL=C
mkdir $ROOTFS
sudo mount $SRCPATH/tmp/rootfs.img $ROOTFS || exit -1
sudo mount -t proc proc $ROOTFS/proc || exit -1
sudo mount -t sysfs sys $ROOTFS/sys || exit -1
sudo mount -o bind /dev $ROOTFS/dev || exit -1
#准备文件 # 全局变量
sudo mkdir -p $ROOTFS/etc/kvmd/override.d $ROOTFS/etc/kvmd/vnc $ROOTFS/var/lib/kvmd/msd $ROOTFS/opt/vc/bin $ROOTFS/usr/share/kvmd \ LOOPDEV=""
$ROOTFS/usr/share/janus/javascript $ROOTFS/usr/lib/ustreamer/janus $ROOTFS/run/kvmd $ROOTFS/var/lib/kvmd/msd/images $ROOTFS/var/lib/kvmd/msd/meta ROOTFS_MOUNTED=0
sudo cp -r ../One-KVM $ROOTFS/ BOOTFS_MOUNTED=0
sudo cp $SRCPATH/image/onecloud/rc.local $ROOTFS/etc/ PROC_MOUNTED=0
sudo cp -r $ROOTFS/One-KVM/configs/kvmd/* $ROOTFS/One-KVM/configs/nginx $ROOTFS/One-KVM/configs/janus \ SYS_MOUNTED=0
$ROOTFS/etc/kvmd DEV_MOUNTED=0
sudo cp -r $ROOTFS/One-KVM/web $ROOTFS/One-KVM/extras $ROOTFS/One-KVM/contrib/keymaps $ROOTFS/usr/share/kvmd DOCKER_CONTAINER_NAME="to_build_rootfs_$$"
sudo cp $ROOTFS/One-KVM/build/platform/onecloud $ROOTFS/usr/share/kvmd/platform PREBUILT_DIR="/tmp/prebuilt_binaries"
sudo cp $ROOTFS/One-KVM/testenv/fakes/vcgencmd $ROOTFS/usr/bin/
sudo cp -r $ROOTFS/One-KVM/testenv/js/* $ROOTFS/usr/share/janus/javascript/
#安装依赖 # --- 引入模块化脚本 ---
sudo chroot --userspec "root:root" $ROOTFS bash -c " \ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
apt update \ source "$SCRIPT_DIR/functions/common.sh"
&& apt install -y python3-aiofiles python3-aiohttp python3-appdirs python3-asn1crypto python3-async-timeout \ source "$SCRIPT_DIR/functions/devices.sh"
python3-bottle python3-cffi python3-chardet python3-click python3-colorama python3-cryptography python3-dateutil \ source "$SCRIPT_DIR/functions/install.sh"
python3-dbus python3-dev python3-hidapi python3-idna python3-libgpiod python3-mako python3-marshmallow python3-more-itertools \ source "$SCRIPT_DIR/functions/packaging.sh"
python3-multidict python3-netifaces python3-packaging python3-passlib python3-pillow python3-ply python3-psutil \
python3-pycparser python3-pyelftools python3-pyghmi python3-pygments python3-pyparsing python3-requests \
python3-semantic-version python3-setproctitle python3-setuptools python3-six python3-spidev python3-systemd \
python3-tabulate python3-urllib3 python3-wrapt python3-xlib python3-yaml python3-yarl python3-pyotp python3-qrcode \
python3-serial python3-zstandard python3-dbus-next \
&& apt install -y nginx python3-pip python3-dev python3-build net-tools tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim \
git gpiod libxkbcommon0 build-essential janus-dev libssl-dev libffi-dev libevent-dev libjpeg-dev libbsd-dev libudev-dev \
pkg-config libx264-dev libyuv-dev libasound2-dev libsndfile-dev libspeexdsp-dev cpufrequtils iptables\
&& apt clean "
sudo chroot --userspec "root:root" $ROOTFS bash -c " \ # 获取日期与Git版本
pip3 config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple \ GIT_COMMIT_ID=$(get_git_commit_id)
&& pip3 install --target=/usr/lib/python3/dist-packages --break-system-packages async-lru gpiod \ DATE=$(date +%y%m%d)
&& pip3 cache purge " if [ -n "$GIT_COMMIT_ID" ]; then
DATE="${DATE}-${GIT_COMMIT_ID}"
fi
sudo chroot --userspec "root:root" $ROOTFS sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h # --- 注册清理函数 ---
# 在脚本退出、收到错误信号、中断信号、终止信号时执行 cleanup
trap cleanup EXIT ERR INT TERM
sudo chroot --userspec "root:root" $ROOTFS bash -c " \ # --- 构建流程函数 ---
git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \
&& make -j WITH_PYTHON=1 WITH_JANUS=1 WITH_LIBX264=1 -C /tmp/ustreamer \
&& mv /tmp/ustreamer/src/ustreamer.bin /usr/bin/ustreamer \
&& mv /tmp/ustreamer/src/ustreamer-dump.bin /usr/bin/ustreamer-dump \
&& chmod +x /usr/bin/ustreamer /usr/bin/ustreamer-dump \
&& mv /tmp/ustreamer/janus/libjanus_ustreamer.so /usr/lib/ustreamer/janus \
&& pip3 install --target=/usr/lib/python3/dist-packages --break-system-packages /tmp/ustreamer/python/dist/*.whl "
#安装 kvmd 主程序 build_target() {
sudo chroot --userspec "root:root" $ROOTFS bash -c " \ local target="$1"
cd /One-KVM \ local build_time=$(date "+%Y-%m-%d %H:%M:%S")
&& python3 setup.py install \ echo "=================================================="
&& bash scripts/kvmd-gencert --do-the-thing \ echo "信息:构建目标: $target"
&& bash scripts/kvmd-gencert --do-the-thing --vnc \ echo "信息:构建时间: $build_time"
&& kvmd-nginx-mkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf \ echo "=================================================="
&& kvmd -m "
sudo chroot --userspec "root:root" $ROOTFS bash -c " \ # 设置全局变量,供后续函数使用
curl https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.armhf -L -o /usr/bin/ttyd \ TARGET_DEVICE_NAME="$target"
&& chmod +x /usr/bin/ttyd \ NEED_PREPARE_DNS=false # 默认不需要准备 DNS
&& systemd-sysusers /One-KVM/configs/os/kvmd-webterm.conf \
&& mkdir -p /home/kvmd-webterm \
&& chown kvmd-webterm /home/kvmd-webterm "
case "$target" in
onecloud)
onecloud_rootfs
local arch="armhf"
local device_type="gpio-onecloud"
local network_type="systemd-networkd"
;;
cumebox2)
cumebox2_rootfs
local arch="aarch64"
local device_type="video1"
local network_type="" # 默认 NetworkManager
NEED_PREPARE_DNS=true
;;
chainedbox)
chainedbox_rootfs_and_fix_dtb
local arch="aarch64"
local device_type="video1"
local network_type=""
NEED_PREPARE_DNS=true
;;
vm)
vm_rootfs
local arch="amd64"
local device_type=""
local network_type=""
NEED_PREPARE_DNS=true
;;
e900v22c)
e900v22c_rootfs
local arch="aarch64"
local device_type="video1"
local network_type=""
NEED_PREPARE_DNS=true
;;
octopus-flanet)
octopus_flanet_rootfs
local arch="aarch64"
local device_type="video1"
local network_type=""
NEED_PREPARE_DNS=true
;;
onecloud-pro)
onecloud_pro_rootfs
local arch="aarch64"
local device_type="gpio-onecloud-pro video1"
local network_type="systemd-networkd"
NEED_PREPARE_DNS=true
;;
orangepi-zero)
orangepizero_rootfs
local arch="armhf"
local device_type=""
local network_type=""
NEED_PREPARE_DNS=true
;;
oec-turbo)
oec_turbo_rootfs
local arch="aarch64"
local device_type="vpu"
local network_type=""
NEED_PREPARE_DNS=true
;;
*)
echo "错误:未知或不支持的目标 '$target'" >&2
exit 1
;;
esac
#服务自启 mount_rootfs
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
cat /One-KVM/configs/os/sudoers/v2-hdmiusb >> /etc/sudoers \
&& cat /One-KVM/configs/os/udev/v2-hdmiusb-generic.rules > /etc/udev/rules.d/99-kvmd.rules \
&& echo 'libcomposite' >> /etc/modules \
&& mv /usr/local/bin/kvmd* /usr/bin \
&& cp /One-KVM/configs/os/services/* /etc/systemd/system/ \
&& cp /One-KVM/configs/os/tmpfiles.conf /usr/lib/tmpfiles.d/ \
&& chmod +x /etc/update-motd.d/* \
&& echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/gpio.sh' >> /etc/sudoers \
&& echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/usbrelay_hid.sh' >> /etc/sudoers \
&& systemd-sysusers /One-KVM/configs/os/sysusers.conf \
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
&& sed -i 's/ch9329/otg/g' /etc/kvmd/override.yaml \
&& sed -i 's/device: \/dev\/ttyUSB0//g' /etc/kvmd/override.yaml \
&& sed -i 's/8080/80/g' /etc/kvmd/override.yaml \
&& sed -i 's/4430/443/g' /etc/kvmd/override.yaml \
&& sed -i 's/#type: otg/type: otg/g' /etc/kvmd/override.yaml \
&& chown kvmd -R /var/lib/kvmd/msd/ \
&& sed -i 's/localhost.localdomain/onecloud/g' /etc/kvmd/meta.yaml \
&& systemctl enable kvmd kvmd-otg kvmd-nginx kvmd-vnc kvmd-ipmi kvmd-webterm kvmd-janus \
&& systemctl disable nginx janus \
&& rm -r /One-KVM "
install_and_configure_kvmd "$arch" "$device_type" "$network_type"
sudo chroot --userspec "root:root" $ROOTFS bash -c " \ write_meta "$target"
sed -i '2c ATX=GPIO' /etc/kvmd/atx.sh \
&& sed -i 's/SHUTDOWNPIN/gpiochip1 7/g' /etc/kvmd/custom_atx/gpio.sh \
&& sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh "
#卸载镜像 unmount_all
sudo umount $ROOTFS/sys
sudo umount $ROOTFS/dev
sudo umount $ROOTFS/proc
sudo umount $ROOTFS
#打包镜像 case "$target" in
sudo rm $SRCPATH/tmp/7.rootfs.PARTITION.sparse onecloud)
sudo img2simg $SRCPATH/tmp/rootfs.img $SRCPATH/tmp/7.rootfs.PARTITION.sparse pack_img_onecloud
sudo $SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64 pack $SRCPATH/output/One-KVM_by-SilentWind_Onecloud_241004.burn.img $SRCPATH/tmp/ ;;
sudo rm $SRCPATH/tmp/* vm)
pack_img "Vm"
;;
cumebox2)
pack_img "Cumebox2"
;;
chainedbox)
pack_img "Chainedbox"
;;
e900v22c)
pack_img "E900v22c"
;;
octopus-flanet)
pack_img "Octopus-Flanet"
;;
onecloud-pro)
pack_img "Onecloud-Pro"
;;
orangepi-zero)
pack_img "Orangepi-Zero"
;;
oec-turbo)
pack_img "OEC-Turbo"
;;
*)
echo "错误:未知的打包类型 for '$target'" >&2
;;
esac
# 在 GitHub Actions 环境中清理下载的文件
cleanup_downloaded_files
echo "=================================================="
echo "信息:目标 $target 构建完成!"
echo "=================================================="
}
# --- 主逻辑 ---
# 检查是否提供了目标参数
if [ -z "$1" ]; then
echo "用法: $0 <target|all>"
echo "可用目标: onecloud, cumebox2, chainedbox, vm, e900v22c, octopus-flanet, onecloud-pro, orangepi-zero, oec-turbo"
exit 1
fi
# 设置脚本立即退出模式
set -eo pipefail
# 检查必要的外部工具
check_required_tools "$1"
# 执行构建
if [ "$1" = "all" ]; then
echo "信息:开始构建所有目标..."
build_target "onecloud"
build_target "cumebox2"
build_target "chainedbox"
build_target "vm"
build_target "e900v22c"
build_target "octopus-flanet"
build_target "onecloud-pro"
build_target "orangepi-zero"
build_target "oec-turbo"
echo "信息:所有目标构建完成。"
else
build_target "$1"
fi
exit 0

313
build/functions/common.sh Executable file
View File

@ -0,0 +1,313 @@
#!/bin/bash
# --- 辅助函数 ---
# 获取 Git 提交 ID
get_git_commit_id() {
if git rev-parse --is-inside-work-tree &>/dev/null; then
git rev-parse --short HEAD 2>/dev/null || echo ""
else
echo ""
fi
}
# 查找并设置一个可用的 loop 设备
find_loop_device() {
echo "信息:查找可用的 loop 设备..."
# 只使用 --find 来获取设备名
LOOPDEV=$(sudo losetup --find)
if [[ -z "$LOOPDEV" || ! -e "$LOOPDEV" ]]; then
echo "错误:再次尝试后仍无法找到可用的 loop 设备。" >&2
exit 1
fi
echo "信息:找到可用 loop 设备名:$LOOPDEV"
}
# 检查并创建目录
ensure_dir() {
if [[ ! -d "$1" ]]; then
echo "信息:创建目录 $1 ..."
sudo mkdir -p "$1" || { echo "错误:创建目录 $1 失败" >&2; exit 1; }
fi
}
# 执行 chroot 命令
run_in_chroot() {
echo "信息:在 chroot 环境 ($ROOTFS) 中执行命令..."
sudo chroot --userspec "root:root" "$ROOTFS" bash -ec "$1" || { echo "错误:在 chroot 环境中执行命令失败" >&2; exit 1; }
echo "信息chroot 命令执行完成。"
}
# --- 清理函数 ---
cleanup() {
echo "信息:执行清理操作..."
# 尝试卸载 chroot 环境下的挂载点
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/dev ..."
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
DEV_MOUNTED=0
fi
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/sys ..."
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
SYS_MOUNTED=0
fi
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/proc ..."
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
PROC_MOUNTED=0
fi
# 尝试卸载主根文件系统
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
echo "信息:卸载 $ROOTFS ..."
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
ROOTFS_MOUNTED=0
fi
# 尝试卸载引导文件系统 (如果使用)
if [[ "$BOOTFS_MOUNTED" -eq 1 && -d "$BOOTFS" ]]; then
echo "信息:卸载 $BOOTFS ..."
sudo umount "$BOOTFS" || sudo umount -l "$BOOTFS" || echo "警告:卸载 $BOOTFS 失败"
BOOTFS_MOUNTED=0
fi
# 尝试分离 loop 设备
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "信息:尝试 zerofree $LOOPDEV ..."
sudo zerofree "$LOOPDEV" || echo "警告zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
echo "信息:分离 loop 设备 $LOOPDEV ..."
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
LOOPDEV=""
fi
# 尝试删除 Docker 容器
echo "信息:检查并删除 Docker 容器 $DOCKER_CONTAINER_NAME ..."
if sudo docker ps -a --format '{{.Names}}' | grep -q "^${DOCKER_CONTAINER_NAME}$"; then
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || echo "警告:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败"
else
echo "信息Docker 容器 $DOCKER_CONTAINER_NAME 不存在或已被删除。"
fi
# 清理临时目录和挂载点目录
echo "信息:清理临时文件和目录..."
sudo rm -rf "$PREBUILT_DIR"
# 只删除挂载点目录本身
if [[ -d "$ROOTFS" ]]; then
sudo rmdir "$ROOTFS" || echo "警告:删除目录 $ROOTFS 失败,可能非空"
fi
if [[ -d "$BOOTFS" ]]; then
sudo rmdir "$BOOTFS" || echo "警告:删除目录 $BOOTFS 失败,可能非空"
fi
echo "信息:清理完成。"
}
# 在打包镜像前调用此函数确保干净卸载所有挂载点和loop设备
unmount_all() {
echo "信息:执行卸载操作,准备打包..."
# 卸载 chroot 环境下的挂载点
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/dev ..."
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
DEV_MOUNTED=0
fi
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/sys ..."
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
SYS_MOUNTED=0
fi
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/proc ..."
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
PROC_MOUNTED=0
fi
# 卸载主根文件系统
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
echo "信息:卸载 $ROOTFS ..."
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
ROOTFS_MOUNTED=0
fi
# 尝试分离 loop 设备前执行 zerofree如果文件系统支持
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "信息:尝试 zerofree $LOOPDEV ..."
sudo zerofree "$LOOPDEV" || echo "警告zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
echo "信息:分离 loop 设备 $LOOPDEV ..."
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
LOOPDEV=""
fi
sudo rm -rf "$PREBUILT_DIR"
echo "信息:卸载操作完成,可以安全打包镜像。"
}
# 挂载根文件系统
mount_rootfs() {
echo "信息:挂载根文件系统到 $ROOTFS ..."
ensure_dir "$ROOTFS"
sudo mount "$LOOPDEV" "$ROOTFS" || { echo "错误:挂载 $LOOPDEV$ROOTFS 失败" >&2; exit 1; }
ROOTFS_MOUNTED=1
echo "信息:挂载 proc, sys, dev 到 chroot 环境..."
ensure_dir "$ROOTFS/proc"
sudo mount -t proc proc "$ROOTFS/proc" || { echo "错误:挂载 proc 到 $ROOTFS/proc 失败" >&2; exit 1; }
PROC_MOUNTED=1
ensure_dir "$ROOTFS/sys"
sudo mount -t sysfs sys "$ROOTFS/sys" || { echo "错误:挂载 sys 到 $ROOTFS/sys 失败" >&2; exit 1; }
SYS_MOUNTED=1
ensure_dir "$ROOTFS/dev"
sudo mount -o bind /dev "$ROOTFS/dev" || { echo "错误:绑定挂载 /dev 到 $ROOTFS/dev 失败" >&2; exit 1; }
DEV_MOUNTED=1
echo "信息:根文件系统及虚拟文件系统挂载完成。"
}
# 设置元数据
write_meta() {
local hostname="$1"
echo "信息:在 chroot 环境中设置主机名/元数据为 $hostname ..."
run_in_chroot "sed -i 's/localhost.localdomain/$hostname/g' /etc/kvmd/meta.yaml"
}
# 检测是否在 GitHub Actions 环境中
is_github_actions() {
[[ -n "$GITHUB_ACTIONS" ]]
}
# 记录下载的文件列表(仅在 GitHub Actions 环境中)
DOWNLOADED_FILES_LIST="/tmp/downloaded_files.txt"
# 自动下载文件函数
download_file_if_missing() {
local file_path="$1"
local relative_path=""
# 如果文件已存在,直接返回
if [[ -f "$file_path" ]]; then
echo "信息:文件已存在: $file_path"
return 0
fi
# 计算相对于 SRCPATH 的路径
if [[ "$file_path" == "$SRCPATH"/* ]]; then
relative_path="${file_path#$SRCPATH/}"
else
echo "错误:文件路径 $file_path 不在 SRCPATH ($SRCPATH) 下" >&2
return 1
fi
echo "信息:文件不存在,尝试下载: $file_path"
echo "信息:相对路径: $relative_path"
# 确保目标目录存在
local target_dir="$(dirname "$file_path")"
ensure_dir "$target_dir"
# 首先尝试直接下载
local remote_url="${REMOTE_PREFIX}/${relative_path}"
echo "信息:尝试下载: $remote_url"
if curl -f -L -o "$file_path" "$remote_url" 2>/dev/null; then
echo "信息:下载成功: $file_path"
# 在 GitHub Actions 环境中记录下载的文件
if is_github_actions; then
echo "$file_path" >> "$DOWNLOADED_FILES_LIST"
fi
return 0
fi
# 如果直接下载失败,尝试添加 .xz 后缀
echo "信息:直接下载失败,尝试 .xz 压缩版本..."
local xz_url="${remote_url}.xz"
local xz_file="${file_path}.xz"
if curl -f -L -o "$xz_file" "$xz_url" 2>/dev/null; then
echo "信息:下载 .xz 文件成功,正在解压..."
if xz -d "$xz_file"; then
echo "信息:解压成功: $file_path"
# 在 GitHub Actions 环境中记录下载的文件
if is_github_actions; then
echo "$file_path" >> "$DOWNLOADED_FILES_LIST"
fi
return 0
else
echo "错误:解压 .xz 文件失败" >&2
rm -f "$xz_file"
return 1
fi
fi
echo "错误:无法下载文件 $file_path (尝试了原始版本和 .xz 版本)" >&2
return 1
}
# 下载 rc.local 文件
download_rc_local() {
local platform_id="$1"
local rc_local_path="$SRCPATH/image/$platform_id/rc.local"
local relative_path="image/$platform_id/rc.local"
local remote_url="$REMOTE_PREFIX/$relative_path"
echo "信息:检查是否需要下载 rc.local 文件 ($platform_id)..."
# 如果本地文件不存在,尝试下载
if [ ! -f "$rc_local_path" ]; then
echo "信息:本地 rc.local 文件不存在,尝试从远程下载..."
ensure_dir "$(dirname "$rc_local_path")"
if curl -sSL --fail "$remote_url" -o "$rc_local_path"; then
echo "信息:成功下载 rc.local 文件:$remote_url"
# 在 GitHub Actions 环境中记录下载的文件
if is_github_actions; then
echo "$rc_local_path" >> "$DOWNLOADED_FILES_LIST"
fi
return 0
else
echo "信息:远程 rc.local 文件不存在或下载失败:$remote_url"
return 1
fi
else
echo "信息:使用本地 rc.local 文件:$rc_local_path"
return 0
fi
}
# 清理下载的文件(仅在 GitHub Actions 环境中)
cleanup_downloaded_files() {
if is_github_actions && [[ -f "$DOWNLOADED_FILES_LIST" ]]; then
echo "信息:清理 GitHub Actions 环境中下载的文件..."
while IFS= read -r file_path; do
if [[ -f "$file_path" ]]; then
echo "信息:删除下载的文件: $file_path"
rm -f "$file_path"
fi
done < "$DOWNLOADED_FILES_LIST"
rm -f "$DOWNLOADED_FILES_LIST"
echo "信息:下载文件清理完成"
fi
}
# 检查必要的外部工具
check_required_tools() {
local required_tools="sudo docker losetup mount umount parted e2fsck resize2fs qemu-img curl tar python3 pip3 rsync git simg2img img2simg dd cat rm mkdir mv cp sed chmod chown ln grep printf id xz"
for cmd in $required_tools; do
if ! command -v "$cmd" &> /dev/null; then
echo "错误:必需的命令 '$cmd' 未找到。请安装相应软件包。" >&2
exit 1
fi
done
# 检查特定工具 (如果脚本中使用了)
if ! command -v "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" &> /dev/null && [[ "$1" == "onecloud" || "$1" == "all" ]]; then
if [ -f "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" ]; then
echo "信息:找到 AmlImg 工具,尝试设置执行权限..."
sudo chmod +x "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" || echo "警告:设置 AmlImg 执行权限失败"
else
echo "错误:构建 onecloud 需要 '$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64',但未找到。" >&2
fi
fi
}

453
build/functions/devices.sh Executable file
View File

@ -0,0 +1,453 @@
#!/bin/bash
# --- 设备特定的 Rootfs 准备函数 ---
onecloud_rootfs() {
local unpacker="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
local source_image="$SRCPATH/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal_support-dvd-emulation.burn.img"
local bootfs_img="$TMPDIR/bootfs.img"
local rootfs_img="$TMPDIR/rootfs.img"
local bootfs_sparse="$TMPDIR/6.boot.PARTITION.sparse"
local rootfs_sparse="$TMPDIR/7.rootfs.PARTITION.sparse"
local bootfs_loopdev="" # 存储 bootfs 使用的 loop 设备
local add_size_mb=600
echo "信息:准备 Onecloud Rootfs..."
ensure_dir "$TMPDIR"
ensure_dir "$BOOTFS"
# 自动下载 AmlImg 工具(如果不存在)
download_file_if_missing "$unpacker" || { echo "错误:下载 AmlImg 工具失败" >&2; exit 1; }
sudo chmod +x "$unpacker" || { echo "错误:设置 AmlImg 工具执行权限失败" >&2; exit 1; }
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 Onecloud 原始镜像失败" >&2; exit 1; }
echo "信息:解包 Onecloud burn 镜像..."
sudo "$unpacker" unpack "$source_image" "$TMPDIR" || { echo "错误:解包失败" >&2; exit 1; }
echo "信息:转换 bootfs 和 rootfs sparse 镜像到 raw 格式..."
sudo simg2img "$bootfs_sparse" "$bootfs_img" || { echo "错误:转换 bootfs sparse 镜像失败" >&2; exit 1; }
sudo simg2img "$rootfs_sparse" "$rootfs_img" || { echo "错误:转换 rootfs sparse 镜像失败" >&2; exit 1; }
echo "信息:挂载 bootfs 并修复 DTB..."
find_loop_device # 查找一个 loop 设备给 bootfs
bootfs_loopdev="$LOOPDEV" # 保存这个设备名
echo "信息:将 $bootfs_img 关联到 $bootfs_loopdev..."
sudo losetup "$bootfs_loopdev" "$bootfs_img" || { echo "错误:关联 bootfs 镜像到 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 bootfs ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
# 自动下载 DTB 文件(如果不存在)
local dtb_file="$SRCPATH/image/onecloud/meson8b-onecloud-fix.dtb"
download_file_if_missing "$dtb_file" || { echo "错误:下载 Onecloud DTB 文件失败" >&2; exit 1; }
sudo cp "$dtb_file" "$BOOTFS/dtb/meson8b-onecloud.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 bootfs ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; } # 卸载失败不应中断流程
BOOTFS_MOUNTED=0
echo "信息:分离 bootfs loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 bootfs loop 设备 $bootfs_loopdev 失败" >&2; }
# bootfs_loopdev 对应的设备现在是空闲的
echo "信息:扩展 rootfs 镜像 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$rootfs_img" || { echo "错误:扩展 rootfs 镜像失败" >&2; exit 1; }
echo "信息:检查并调整 rootfs 文件系统大小 (在文件上)..."
# 注意e2fsck/resize2fs 现在直接操作镜像文件,而不是 loop 设备
sudo e2fsck -f -y "$rootfs_img" || { echo "警告e2fsck 检查 rootfs 镜像文件失败" >&2; exit 1; }
sudo resize2fs "$rootfs_img" || { echo "错误resize2fs 调整 rootfs 镜像文件大小失败" >&2; exit 1; }
echo "信息:设置 rootfs loop 设备..."
find_loop_device # 重新查找一个可用的 loop 设备 (可能是刚才释放的那个)
echo "信息:将 $rootfs_img 关联到 $LOOPDEV..."
sudo losetup "$LOOPDEV" "$rootfs_img" || { echo "错误:关联 rootfs 镜像到 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Onecloud Rootfs 准备完成。 Loop 设备 $LOOPDEV 已关联 $rootfs_img"
}
cumebox2_rootfs() {
local source_image="$SRCPATH/image/cumebox2/Armbian_24.8.1_Khadas-vim1_bookworm_current_6.6.47_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((8192 * 512))
local add_size_mb=900
echo "信息:准备 Cumebox2 Rootfs..."
ensure_dir "$TMPDIR"
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 Cumebox2 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 Cumebox2 原始镜像失败" >&2; exit 1; }
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
echo "信息:调整镜像分区大小..."
sudo parted -s "$target_image" resizepart 1 100% || { echo "错误:使用 parted 调整分区大小失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Cumebox2 Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
chainedbox_rootfs_and_fix_dtb() {
local source_image="$SRCPATH/image/chainedbox/Armbian_24.11.0_rockchip_chainedbox_bookworm_6.1.112_server_2024.10.02_add800m.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((32768 * 512))
local rootfs_offset=$((1081344 * 512))
local bootfs_loopdev=""
echo "信息:准备 Chainedbox Rootfs 并修复 DTB..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 Chainedbox 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 Chainedbox 原始镜像失败" >&2; exit 1; }
echo "信息:挂载 boot 分区并修复 DTB..."
find_loop_device # 找 loop 给 boot
bootfs_loopdev="$LOOPDEV"
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
# 自动下载 DTB 文件(如果不存在)
local dtb_file="$SRCPATH/image/chainedbox/rk3328-l1pro-1296mhz-fix.dtb"
download_file_if_missing "$dtb_file" || { echo "错误:下载 Chainedbox DTB 文件失败" >&2; exit 1; }
sudo cp "$dtb_file" "$BOOTFS/dtb/rockchip/rk3328-l1pro-1296mhz.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
BOOTFS_MOUNTED=0
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Chainedbox Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
vm_rootfs() {
local source_image="$SRCPATH/image/vm/Armbian_25.2.1_Uefi-x86_bookworm_current_6.12.13_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((540672 * 512))
echo "信息:准备 Vm Rootfs..."
ensure_dir "$TMPDIR"
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 Vm 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 Vm 原始镜像失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Vm Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
e900v22c_rootfs() {
local source_image="$SRCPATH/image/e900v22c/Armbian_23.08.0_amlogic_s905l3a_bookworm_5.15.123_server_2023.08.01.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((532480 * 512))
local add_size_mb=600
echo "信息:准备 E900V22C Rootfs..."
ensure_dir "$TMPDIR"
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 E900V22C 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 E900V22C 原始镜像失败" >&2; exit 1; }
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息E900V22C Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
octopus_flanet_rootfs() {
local source_image="$SRCPATH/image/octopus-flanet/Armbian_25.05.0_amlogic_s912_bookworm_6.1.129_server_2025.03.02.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((8192 * 512))
local rootfs_offset=$((1056768 * 512))
local add_size_mb=600
local bootfs_loopdev=""
echo "信息:准备 Octopus-Planet Rootfs..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 Octopus-Planet 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 Octopus-Planet 原始镜像失败" >&2; exit 1; }
echo "信息:挂载 boot 分区并修改 uEnv.txt (使用 VIM2 DTB)..."
find_loop_device # 找 loop 给 boot
bootfs_loopdev="$LOOPDEV"
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
# 自动下载 Octopus-Planet 相关文件
local dtb_file="$SRCPATH/image/octopus-flanet/meson-gxm-octopus-planet.dtb"
download_file_if_missing "$dtb_file" || echo "警告:下载 Octopus-Planet DTB 失败"
sudo cp "$dtb_file" "$BOOTFS/dtb/amlogic/meson-gxm-octopus-planet.dtb" || echo "警告:复制 Octopus-Planet DTB 失败"
sudo sed -i "s/meson-gxm-octopus-planet.dtb/meson-gxm-khadas-vim2.dtb/g" "$BOOTFS/uEnv.txt" || { echo "错误:修改 uEnv.txt 失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
BOOTFS_MOUNTED=0
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Octopus-Planet Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
onecloud_pro_rootfs() {
local source_image="$SRCPATH/image/onecloud-pro/Armbian-by-SilentWind_24.5.0_amlogic_Onecloud-Pro_jammy_6.6.28_server.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((8192 * 512))
local rootfs_offset=$((1056768 * 512))
local add_size_mb=600
local bootfs_loopdev=""
echo "信息:准备 Octopus-Planet Rootfs..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
# 自动下载源镜像文件(如果不存在)
download_file_if_missing "$source_image" || { echo "错误:下载 Octopus-Planet 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 Octopus-Planet 原始镜像失败" >&2; exit 1; }
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Octopus-Planet Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
orangepizero_rootfs() {
local source_image="$SRCPATH/image/orangepi-zero/Armbian_community_25.11.0-trunk.208_Orangepizero_bookworm_current_6.12.47_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((8192 * 512))
local add_size_mb=600
echo "信息:准备 Orange Pi Zero Rootfs..."
ensure_dir "$TMPDIR"
echo "信息:下载或使用本地 Orange Pi Zero 原始镜像..."
download_file_if_missing "$source_image" || { echo "错误:下载 Orange Pi Zero 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 Orange Pi Zero 原始镜像失败" >&2; exit 1; }
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
echo "信息:调整镜像分区大小..."
sudo parted -s "$target_image" resizepart 1 100% || { echo "错误:使用 parted 调整分区大小失败" >&2; exit 1; }
find_loop_device
sudo losetup -P "$LOOPDEV" "$target_image" || { echo "错误:设置 loop 设备失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小..."
sudo e2fsck -y -f "${LOOPDEV}p1" || { echo "错误:文件系统检查失败" >&2; exit 1; }
sudo resize2fs "${LOOPDEV}p1" || { echo "错误:调整文件系统大小失败" >&2; exit 1; }
# 重新设置 LOOPDEV 为分区
sudo losetup -d "$LOOPDEV"
sudo losetup "$LOOPDEV" "$target_image" -o "$offset" || { echo "错误:重新设置 loop 设备失败" >&2; exit 1; }
echo "信息Orange Pi Zero Rootfs 准备完成。"
}
# --- 特定设备的文件配置函数 ---
config_cumebox2_files() {
echo "信息:为 Cumebox2 配置特定文件 (OLED, DTB)..."
ensure_dir "$ROOTFS/etc/oled"
# 自动下载 Cumebox2 相关文件(如果不存在)
local dtb_file="$SRCPATH/image/cumebox2/v-fix.dtb"
local ssd_file="$SRCPATH/image/cumebox2/ssd"
local config_file="$SRCPATH/image/cumebox2/config.json"
download_file_if_missing "$dtb_file" || echo "警告:下载 Cumebox2 DTB 失败"
download_file_if_missing "$ssd_file" || echo "警告:下载 Cumebox2 ssd 脚本失败"
download_file_if_missing "$config_file" || echo "警告:下载 Cumebox2 配置文件失败"
sudo cp "$dtb_file" "$ROOTFS/boot/dtb/amlogic/meson-gxl-s905x-khadas-vim.dtb" || echo "警告:复制 Cumebox2 DTB 失败"
sudo cp "$ssd_file" "$ROOTFS/usr/bin/" || echo "警告:复制 Cumebox2 ssd 脚本失败"
sudo chmod +x "$ROOTFS/usr/bin/ssd" || echo "警告:设置 ssd 脚本执行权限失败"
sudo cp "$config_file" "$ROOTFS/etc/oled/config.json" || echo "警告:复制 OLED 配置文件失败"
}
config_octopus_flanet_files() {
echo "信息:为 Octopus-Planet 配置特定文件 (model_database.conf)..."
# 自动下载 Octopus-Planet 相关文件(如果不存在)
local config_file="$SRCPATH/image/octopus-flanet/model_database.conf"
download_file_if_missing "$config_file" || echo "警告:下载 Octopus-Planet 配置文件失败"
sudo cp "$config_file" "$ROOTFS/etc/model_database.conf" || echo "警告:复制 model_database.conf 失败"
echo "信息:为 Octopus-Planet 添加 DRM 设备支持..."
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card0\\\"\" /etc/kvmd/override.yaml"
}
config_orangepi_zero_files() {
echo "信息:配置 Orange Pi Zero 特定文件..."
# 清空 modules.conf 文件,避免加载不必要的模块
run_in_chroot "echo 'libcomposite' > /etc/modules-load.d/modules.conf"
echo "信息Orange Pi Zero 特定配置完成。"
}
config_onecloud_pro_files() {
echo "信息:配置 Onecloud Pro 特定文件..."
echo "信息:为 Onecloud Pro 添加 DRM 设备支持..."
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card0\\\"\" /etc/kvmd/override.yaml"
}
config_onecloud_files() {
echo "信息:配置 Onecloud 特定文件..."
echo "信息:为 Onecloud 添加 DRM 设备支持..."
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card1\\\"\" /etc/kvmd/override.yaml"
echo "信息Onecloud 特定配置完成。"
}
oec_turbo_rootfs() {
local source_image="$SRCPATH/image/oec-turbo/Flash_Armbian_25.05.0_rockchip_efused-wxy-oec_bookworm_6.1.99_server_2025.03.20.img"
local target_image="$TMPDIR/rootfs.img"
local rootfs_offset=$((1409024 * 512)) # 根据分区7的起始扇区计算
echo "信息:准备 OEC-Turbo Rootfs (Debian 12)..."
ensure_dir "$TMPDIR"
echo "信息:下载或使用本地 OEC-Turbo 原始镜像..."
download_file_if_missing "$source_image" || { echo "错误:下载 OEC-Turbo 原始镜像失败" >&2; exit 1; }
cp "$source_image" "$target_image" || { echo "错误:复制 OEC-Turbo 原始镜像失败" >&2; exit 1; }
find_loop_device
# 设置 loop 设备指向 rootfs 分区 (分区7)
sudo losetup "$LOOPDEV" "$target_image" -o "$rootfs_offset" || { echo "错误:设置 loop 设备失败" >&2; exit 1; }
echo "信息OEC-Turbo Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
config_oec_turbo_files() {
echo "信息:配置 OEC-Turbo 特定文件..."
# 替换 override.yaml 中的硬件编码配置,启用 RK MPP 硬件编码
echo "信息:配置 VPU 硬件编码支持..."
run_in_chroot "sed -i 's/--h264-hwenc=disabled/--h264-hwenc=rkmpp/g' /etc/kvmd/override.yaml"
echo "信息:配置 udev 规则以授权 kvmd 组访问硬件设备..."
run_in_chroot "cat > /etc/udev/rules.d/99-kvmd-hw-access.rules <<'EOF'
# Generic hardware access for kvmd
# Safe on all platforms — rules only apply if device exists
# Rockchip MPP (rkmpp)
KERNEL==\"mpp_service\", GROUP=\"kvmd\", MODE=\"0660\"
# DMA-Heap (used by modern MPP)
SUBSYSTEM==\"dma_heap\", KERNEL==\"system\", GROUP=\"kvmd\", MODE=\"0660\"
SUBSYSTEM==\"dma_heap\", KERNEL==\"system-uncached\", GROUP=\"kvmd\", MODE=\"0660\"
SUBSYSTEM==\"dma_heap\", KERNEL==\"reserved\", GROUP=\"kvmd\", MODE=\"0660\"
# Optional legacy Rockchip devices
KERNEL==\"rkvdec\", GROUP=\"kvmd\", MODE=\"0660\"
KERNEL==\"rkvenc\", GROUP=\"kvmd\", MODE=\"0660\"
KERNEL==\"rga\", GROUP=\"kvmd\", MODE=\"0660\"
EOF"
# 替换 DTB 文件
replace_oec_turbo_dtb
echo "信息OEC-Turbo 特定配置完成。"
}
replace_oec_turbo_dtb() {
local dtb_source="$SRCPATH/image/oec-turbo/rk3566-onething-oec-box.dtb"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((360448 * 512)) # boot 分区6的偏移
local boot_mount="$TMPDIR/oec_boot_mount"
local dtb_target_path="dtb/rockchip/rk3566-onething-oec-box.dtb"
local boot_loopdev=""
echo "信息:替换 OEC-Turbo DTB 文件..."
if [ ! -f "$dtb_source" ]; then
echo "信息:尝试下载 DTB 文件..."
download_file_if_missing "$dtb_source"
fi
echo "信息:为 boot 分区查找独立的 loop 设备..."
# 查找一个新的loop设备用于boot分区
boot_loopdev=$(losetup -f)
ensure_dir "$boot_mount"
losetup -o "$boot_offset" "$boot_loopdev" "$target_image"
mount "$boot_loopdev" "$boot_mount"
# 确保目标目录存在并复制 DTB 文件
mkdir -p "$boot_mount/$(dirname "$dtb_target_path")"
cp "$dtb_source" "$boot_mount/$dtb_target_path"
echo "信息DTB 文件替换成功: $dtb_target_path"
umount "$boot_mount"
losetup -d "$boot_loopdev"
rmdir "$boot_mount"
}

386
build/functions/install.sh Executable file
View File

@ -0,0 +1,386 @@
#!/bin/bash
# --- 预准备 ---
prepare_dns_and_mirrors() {
echo "信息:在 chroot 环境中准备 DNS 和更换软件源..."
run_in_chroot "
mkdir -p /run/systemd/resolve/ \\
&& touch /run/systemd/resolve/stub-resolv.conf \\
&& printf '%s\\n' 'nameserver 1.1.1.1' 'nameserver 1.0.0.1' > /etc/resolv.conf \\
&& echo '信息:尝试更换镜像源...' \\
&& bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh) \\
--source mirrors.ustc.edu.cn --upgrade-software false --web-protocol http || echo '警告:更换镜像源脚本执行失败,可能网络不通或脚本已更改'
"
}
delete_armbian_verify(){
echo "信息:在 chroot 环境中修改 Armbian 软件源..."
run_in_chroot "echo 'deb http://mirrors.ustc.edu.cn/armbian bullseye main bullseye-utils bullseye-desktop' > /etc/apt/sources.list.d/armbian.list"
}
prepare_external_binaries() {
local platform="$1" # linux/armhf or linux/amd64 or linux/aarch64
# 如果在 GitHub Actions 环境下,使用 silentwind0/kvmd-stage-0否则用阿里云镜像
if is_github_actions; then
local docker_image="silentwind0/kvmd-stage-0"
else
local docker_image="registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0"
fi
echo "信息:准备外部预编译二进制文件 (平台: $platform)..."
ensure_dir "$PREBUILT_DIR"
echo "信息:拉取 Docker 镜像 $docker_image (平台: $platform)..."
sudo docker pull --platform "$platform" "$docker_image" || { echo "错误:拉取 Docker 镜像 $docker_image 失败" >&2; exit 1; }
echo "信息:创建 Docker 容器 $DOCKER_CONTAINER_NAME ..."
sudo docker create --name "$DOCKER_CONTAINER_NAME" "$docker_image" || { echo "错误:创建 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
echo "信息:从 Docker 容器导出文件到 $PREBUILT_DIR ..."
sudo docker export "$DOCKER_CONTAINER_NAME" | sudo tar -xf - -C "$PREBUILT_DIR" || { echo "错误:导出并解压 Docker 容器内容失败" >&2; exit 1; }
echo "信息:预编译二进制文件准备完成,存放于 $PREBUILT_DIR"
# 删除 Docker 容器
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || { echo "错误:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
}
config_base_files() {
local platform_id="$1" # e.g., "onecloud", "cumebox2"
echo "信息:配置基础文件和目录结构 ($platform_id)..."
echo "信息:创建 KVMD 相关目录..."
ensure_dir "$ROOTFS/etc/kvmd/override.d"
ensure_dir "$ROOTFS/etc/kvmd/vnc"
ensure_dir "$ROOTFS/var/lib/kvmd/msd/images"
ensure_dir "$ROOTFS/var/lib/kvmd/msd/meta"
ensure_dir "$ROOTFS/opt/vc/bin"
ensure_dir "$ROOTFS/usr/share/kvmd"
ensure_dir "$ROOTFS/One-KVM"
ensure_dir "$ROOTFS/usr/share/janus/javascript"
ensure_dir "$ROOTFS/usr/lib/ustreamer/janus"
ensure_dir "$ROOTFS/run/kvmd"
ensure_dir "$ROOTFS/tmp/wheel/"
ensure_dir "$ROOTFS/usr/lib/janus/transports/"
ensure_dir "$ROOTFS/usr/lib/janus/loggers"
echo "信息:复制 One-KVM 源码..."
sudo rsync -a --exclude={.git,.github,output,tmp} . "$ROOTFS/One-KVM/" || { echo "错误:复制 One-KVM 源码失败" >&2; exit 1; }
echo "信息:复制配置文件..."
sudo cp -r configs/kvmd/* configs/nginx configs/janus "$ROOTFS/etc/kvmd/"
sudo cp -r web extras contrib/keymaps "$ROOTFS/usr/share/kvmd/"
sudo cp testenv/fakes/vcgencmd "$ROOTFS/usr/bin/"
sudo cp -r testenv/js/* "$ROOTFS/usr/share/janus/javascript/"
sudo cp "build/platform/$platform_id" "$ROOTFS/usr/share/kvmd/platform" || { echo "错误:复制平台文件 build/platform/$platform_id 失败" >&2; exit 1; }
sudo cp scripts/kvmd-gencert scripts/kvmd-bootconfig scripts/kvmd-certbot scripts/kvmd-udev-hdmiusb-check scripts/kvmd-udev-restart-pass build/scripts/kvmd-firstrun.sh "$ROOTFS/usr/bin/"
sudo chmod +x "$ROOTFS/usr/bin/kvmd-gencert" "$ROOTFS/usr/bin/kvmd-bootconfig" "$ROOTFS/usr/bin/kvmd-certbot" "$ROOTFS/usr/bin/kvmd-udev-hdmiusb-check" "$ROOTFS/usr/bin/kvmd-udev-restart-pass" "$ROOTFS/usr/bin/kvmd-firstrun.sh"
# 尝试下载或使用本地 rc.local 文件
download_rc_local "$platform_id" || echo "信息rc.local 文件不存在,跳过"
if [ -f "$SRCPATH/image/$platform_id/rc.local" ]; then
echo "信息:复制设备特定的 rc.local 文件..."
sudo cp "$SRCPATH/image/$platform_id/rc.local" "$ROOTFS/etc/"
fi
echo "信息:从预编译目录复制二进制文件和库..."
sudo cp "$PREBUILT_DIR/tmp/lib/"* "$ROOTFS/lib/"*-linux-*/ || echo "警告:复制 /tmp/lib/* 失败,可能源目录或目标目录不存在或不匹配"
sudo cp "$PREBUILT_DIR/tmp/ustreamer/ustreamer" "$PREBUILT_DIR/tmp/ustreamer/ustreamer-dump" "$PREBUILT_DIR/usr/bin/janus" "$ROOTFS/usr/bin/" || { echo "错误:复制 ustreamer/janus 二进制文件失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/tmp/ustreamer/janus/libjanus_ustreamer.so" "$ROOTFS/usr/lib/ustreamer/janus/" || { echo "错误:复制 libjanus_ustreamer.so 失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/tmp/wheel/"*.whl "$ROOTFS/tmp/wheel/" || { echo "错误:复制 Python wheel 文件失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/usr/lib/janus/transports/"* "$ROOTFS/usr/lib/janus/transports/" || { echo "错误:复制 Janus transports 失败" >&2; exit 1; }
# 禁用 apt-file
if [ -f "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" ]; then
echo "信息:禁用 apt-file 配置..."
sudo mv "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf.disabled"
fi
echo "信息:基础文件配置完成。"
}
# --- KVMD 安装与配置 ---
install_base_packages() {
echo "信息:在 chroot 环境中更新源并安装基础软件包..."
run_in_chroot "
apt-get update && \\
apt install -y --no-install-recommends \\
libxkbcommon-x11-0 nginx tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim \\
iptables network-manager curl kmod libmicrohttpd12 libjansson4 libssl3 \\
libsofia-sip-ua0 libglib2.0-0 libopus0 libogg0 libcurl4 libconfig9 \\
python3-pip net-tools libavcodec59 libavformat59 libavutil57 libswscale6 \\
libavfilter8 libavdevice59 v4l-utils libv4l-0 nano unzip dnsmasq python3-systemd && \\
apt clean && \\
rm -rf /var/lib/apt/lists/*
"
}
configure_network() {
local network_type="$1" # "systemd-networkd" or others (default network-manager)
if [ "$network_type" = "systemd-networkd" ]; then
echo "信息:在 chroot 环境中配置 systemd-networkd..."
# onecloud 与 onecloud-pro 均启用基于 SN 的 MAC 地址生成
if [ "$TARGET_DEVICE_NAME" = "onecloud" ] || [ "$TARGET_DEVICE_NAME" = "onecloud-pro" ]; then
echo "信息:为 ${TARGET_DEVICE_NAME} 平台配置基于 SN 的 MAC 地址生成机制..."
# 复制MAC地址生成脚本
sudo cp "$SCRIPT_DIR/scripts/generate-random-mac.sh" "$ROOTFS/usr/local/bin/"
sudo chmod +x "$ROOTFS/usr/local/bin/generate-random-mac.sh"
# 复制systemd服务文件
sudo cp "$SCRIPT_DIR/services/kvmd-generate-mac.service" "$ROOTFS/etc/systemd/system/"
# 创建初始网络配置文件不包含MAC地址将由脚本生成
run_in_chroot "
echo -e '[Match]\\nName=eth0\\n\\n[Network]\\nDHCP=yes' > /etc/systemd/network/99-eth0.network && \\
systemctl mask NetworkManager && \\
systemctl unmask systemd-networkd && \\
systemctl enable systemd-networkd systemd-resolved && \\
systemctl enable kvmd-generate-mac.service
"
echo "信息:${TARGET_DEVICE_NAME} 基于 SN 的 MAC 地址生成机制配置完成"
fi
else
echo "信息:使用默认的网络管理器 (NetworkManager)..."
# 可能需要确保 NetworkManager 是启用的 (通常默认是)
run_in_chroot "systemctl enable NetworkManager"
fi
}
install_python_deps() {
echo "信息:在 chroot 环境中安装 Python 依赖 (wheels)..."
run_in_chroot "
pip3 install --no-cache-dir --break-system-packages /tmp/wheel/*.whl && \\
pip3 cache purge && \\
rm -rf /tmp/wheel
"
}
configure_kvmd_core() {
echo "信息:在 chroot 环境中安装和配置 KVMD 核心..."
# 复制KVMD首次运行脚本和服务
echo "信息配置KVMD首次运行初始化服务..."
sudo cp "build/services/kvmd-firstrun.service" "$ROOTFS/etc/systemd/system/"
# 安装KVMD但不执行需要在首次运行时完成的操作
run_in_chroot "
cd /One-KVM && \\
python3 setup.py install && \\
systemctl enable kvmd-firstrun.service
"
echo "信息KVMD核心安装完成证书生成等初始化操作将在首次开机时执行"
}
configure_system() {
echo "信息:在 chroot 环境中配置系统级设置 (sudoers, udev, services)..."
run_in_chroot "
cat /One-KVM/configs/os/sudoers/v2-hdmiusb >> /etc/sudoers && \\
cat /One-KVM/configs/os/udev/v2-hdmiusb-rpi4.rules > /etc/udev/rules.d/99-kvmd.rules && \\
echo 'libcomposite' >> /etc/modules && \\
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-kvmd-extra.conf && \\
mv /usr/local/bin/kvmd* /usr/bin/ || echo '信息:/usr/local/bin/kvmd* 未找到或移动失败,可能已在/usr/bin' && \\
cp -r /One-KVM/configs/os/services/* /etc/systemd/system/ && \\
cp /One-KVM/configs/os/tmpfiles.conf /usr/lib/tmpfiles.d/ && \\
chmod +x /etc/update-motd.d/* || echo '警告chmod /etc/update-motd.d/* 失败' && \\
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/gpio.sh' >> /etc/sudoers && \\
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/usbrelay_hid.sh' >> /etc/sudoers && \\
systemd-sysusers /One-KVM/configs/os/sysusers.conf && \\
systemd-sysusers /One-KVM/configs/os/kvmd-webterm.conf && \\
ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata || echo '警告:创建 tesseract 链接失败' && \\
sed -i 's/8080/80/g' /etc/kvmd/override.yaml && \\
sed -i 's/4430/443/g' /etc/kvmd/override.yaml && \\
chown kvmd -R /var/lib/kvmd/msd/ && \\
rm /etc/resolv.conf && \\
printf '%s\\n' 'nameserver 1.1.1.1' 'nameserver 1.0.0.1' > /etc/resolv.conf && \
systemctl enable dnsmasq kvmd kvmd-otg kvmd-nginx kvmd-vnc kvmd-ipmi kvmd-webterm kvmd-janus kvmd-media kvmd-gostc && \\
systemctl disable nginx systemd-resolved && \\
rm -rf /One-KVM
"
}
install_webterm() {
local arch="$1" # armhf, aarch64, x86_64
local ttyd_arch="$arch"
if [ "$arch" = "armhf" ]; then
ttyd_arch="armhf"
elif [ "$arch" = "amd64" ]; then
ttyd_arch="x86_64"
elif [ "$arch" = "aarch64" ]; then
ttyd_arch="aarch64"
fi
echo "信息:在 chroot 环境中下载并安装 ttyd ($ttyd_arch)..."
run_in_chroot "
curl -L https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.${ttyd_arch} -o /usr/bin/ttyd && \\
chmod +x /usr/bin/ttyd && \\
mkdir -p /home/kvmd-webterm && \\
chown kvmd-webterm /home/kvmd-webterm
"
}
install_gostc() {
local arch="$1" # armhf, aarch64, x86_64
local gostc_arch="$arch"
local gostc_version="v2.0.8-beta.2"
# 根据架构映射下载文件名
case "$arch" in
armhf) gostc_arch="arm_7" ;;
aarch64) gostc_arch="arm64_v8.0" ;;
x86_64|amd64) gostc_arch="amd64_v1" ;;
*) echo "错误:不支持的架构 $arch"; exit 1 ;;
esac
echo "信息:在 chroot 环境中下载并安装 gostc ($gostc_arch)..."
run_in_chroot "
mkdir -p /tmp/gostc && cd /tmp/gostc && \\
curl -L https://github.com/mofeng-git/gostc-open/releases/download/${gostc_version}/gostc_linux_${gostc_arch}.tar.gz -o gostc.tar.gz && \\
tar -xzf gostc.tar.gz && \\
mv gostc /usr/bin/ && \\
chmod +x /usr/bin/gostc && \\
cd / && rm -rf /tmp/gostc
"
echo "信息:创建 gostc systemd 服务文件..."
run_in_chroot "
cat > /etc/systemd/system/kvmd-gostc.service << 'EOF'
[Unit]
Description=基于FRP开发的内网穿透 客户端/节点
ConditionFileIsExecutable=/usr/bin/gostc
After=network.target
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/usr/bin/gostc \"-web-addr\" \"0.0.0.0:18080\"
WorkingDirectory=/usr/bin
Restart=always
RestartSec=10
EnvironmentFile=-/etc/sysconfig/gostc
[Install]
WantedBy=multi-user.target
EOF
"
echo "信息gostc 安装和配置完成"
}
apply_kvmd_tweaks() {
local arch="$1" # armhf, aarch64, x86_64
local device_type="$2" # "gpio" or "video1" or other
local atx_setting=""
local hid_setting=""
echo "信息:根据架构 ($arch) 和设备类型 ($device_type) 调整 KVMD 配置..."
if [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then
echo "信息:目标平台为 x86_64/amd64 架构,禁用 OTG设置 ATX 为 USBRELAY_HID..."
run_in_chroot "
systemctl disable kvmd-otg && \\
sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh && \\
sed -i 's/device: \/dev\/ttyUSB0/device: \/dev\/kvmd-hid/g' /etc/kvmd/override.yaml
"
else
echo "信息::目标平台为 ARM 架构 ($arch)..."
# ARM 架构,配置 HID 为 OTG
hid_setting="otg"
run_in_chroot "
sed -i 's/#type: otg/type: otg/g' /etc/kvmd/override.yaml && \\
sed -i 's/device: \/dev\/ttyUSB0/#device: \/dev\/ttyUSB0/g' /etc/kvmd/override.yaml # 注释掉 ttyUSB0
"
echo "信息:设置 HID 为 $hid_setting"
run_in_chroot "sed -i 's/type: ch9329/type: $hid_setting/g' /etc/kvmd/override.yaml"
# 根据 device_type 配置 ATX
if [[ "$device_type" == *"gpio-onecloud-pro"* ]]; then
echo "信息:电源控制设备类型为 gpio设置 ATX 为 GPIO 并配置引脚..."
atx_setting="GPIO"
run_in_chroot "
sed -i 's/^ATX=.*/ATX=GPIO/' /etc/kvmd/atx.sh && \\
sed -i 's/SHUTDOWNPIN/gpiochip0 7/g' /etc/kvmd/custom_atx/gpio.sh && \\
sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh
"
elif [[ "$device_type" == *"gpio-onecloud"* ]]; then
echo "信息:电源控制设备类型为 gpio设置 ATX 为 GPIO 并配置引脚..."
atx_setting="GPIO"
run_in_chroot "
sed -i 's/^ATX=.*/ATX=GPIO/' /etc/kvmd/atx.sh && \\
sed -i 's/SHUTDOWNPIN/gpiochip1 7/g' /etc/kvmd/custom_atx/gpio.sh && \\
sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh
"
else
echo "信息:电源控制设备类型不是 gpio ($device_type),设置 ATX 为 USBRELAY_HID..."
atx_setting="USBRELAY_HID"
run_in_chroot "sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh"
fi
# 配置视频设备
if [[ "$device_type" == *"video1"* ]]; then
echo "信息:视频设备类型为 video1设置视频设备为 /dev/video1..."
run_in_chroot "sed -i 's|/dev/video0|/dev/video1|g' /etc/kvmd/override.yaml"
elif [[ "$device_type" == *"video1"* ]]; then
echo "信息:视频设备类型为 kvmd-video设置视频设备为 /dev/kvmd-video..."
run_in_chroot "sed -i 's|/dev/video0|/dev/kvmd-video|g' /etc/kvmd/override.yaml"
else
echo "信息:使用默认视频设备 /dev/video0..."
fi
fi
echo "信息KVMD 配置调整完成。"
run_in_chroot "apt remove -y --purge systemd-resolved"
}
# --- 整体安装流程 ---
install_and_configure_kvmd() {
local arch="$1" # 架构: armhf, aarch64, x86_64/amd64
local device_type="$2" # 设备特性: "gpio", "video1", "" (空或其他)
local network_type="$3" # 网络配置: "systemd-networkd", "" (默认 network-manager)
local host_arch="" # Docker 平台架构: arm, aarch64, amd64
# 映射架构名称
case "$arch" in
armhf) host_arch="arm" ;;
aarch64) host_arch="arm64" ;; # docker aarch64 平台名是 arm64
x86_64|amd64) host_arch="amd64"; arch="x86_64" ;; # 统一内部使用 x86_64
*) echo "错误:不支持的架构 $arch"; exit 1 ;;
esac
prepare_external_binaries "linux/$host_arch"
config_base_files "$TARGET_DEVICE_NAME" # 使用全局变量传递设备名
# 特定设备的额外文件配置 (如果存在)
# 将设备名中的连字符转换为下划线以匹配函数名
local device_func_name="${TARGET_DEVICE_NAME//-/_}"
if declare -f "config_${device_func_name}_files" > /dev/null; then
echo "信息:执行特定设备的文件配置函数 config_${device_func_name}_files ..."
"config_${device_func_name}_files"
fi
# 某些镜像可能需要准备DNS和换源
if [[ "$NEED_PREPARE_DNS" = true ]]; then
prepare_dns_and_mirrors
fi
# 可选强制使用特定armbian源
# delete_armbian_verify
# 执行安装步骤
install_base_packages
configure_network "$network_type"
install_python_deps
configure_kvmd_core
install_gostc "$arch" # 安装 gostc
configure_system
install_webterm "$arch" # 传递原始架构名给ttyd下载
apply_kvmd_tweaks "$arch" "$device_type"
run_in_chroot "df -h" # 显示最终磁盘使用情况
echo "信息One-KVM 安装和配置完成。"
}

105
build/functions/packaging.sh Executable file
View File

@ -0,0 +1,105 @@
#!/bin/bash
# --- 压缩函数 ---
# 压缩镜像文件(仅在 GitHub Actions 环境中)
compress_image_file() {
local file_path="$1"
if is_github_actions && [[ -f "$file_path" ]]; then
echo "信息:压缩镜像文件: $file_path"
if xz -9 -vv "$file_path"; then
echo "信息:压缩完成: ${file_path}.xz"
else
echo "警告:压缩文件 $file_path 失败"
fi
fi
}
# --- 打包函数 ---
pack_img() {
local device_name_friendly="$1" # e.g., "Vm", "Cumebox2"
local target_img_name="One-KVM_by-SilentWind_${device_name_friendly}_${DATE}.img"
local source_img="$TMPDIR/rootfs.img"
echo "信息:开始打包镜像 ($device_name_friendly)..."
ensure_dir "$OUTPUTDIR"
# 确保在打包前已经正确卸载了所有挂载点和loop设备
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "警告发现未卸载的挂载点或loop设备尝试再次卸载..."
unmount_all
fi
echo "信息:移动镜像文件 $source_img$OUTPUTDIR/$target_img_name ..."
sudo mv "$source_img" "$OUTPUTDIR/$target_img_name" || { echo "错误:移动镜像文件失败" >&2; exit 1; }
if [ "$device_name_friendly" = "Vm" ]; then
echo "信息:为 Vm 目标转换镜像格式 (vmdk, vdi)..."
local raw_img="$OUTPUTDIR/$target_img_name"
local vmdk_img="$OUTPUTDIR/One-KVM_by-SilentWind_Vmare-uefi_${DATE}.vmdk"
local vdi_img="$OUTPUTDIR/One-KVM_by-SilentWind_Virtualbox-uefi_${DATE}.vdi"
echo "信息:转换为 VMDK..."
sudo qemu-img convert -f raw -O vmdk "$raw_img" "$vmdk_img" || echo "警告:转换为 VMDK 失败"
echo "信息:转换为 VDI..."
sudo qemu-img convert -f raw -O vdi "$raw_img" "$vdi_img" || echo "警告:转换为 VDI 失败"
# 在 GitHub Actions 环境中压缩 VM 镜像文件
if is_github_actions; then
echo "信息:在 GitHub Actions 环境中压缩 VM 镜像文件..."
compress_image_file "$raw_img"
compress_image_file "$vmdk_img"
compress_image_file "$vdi_img"
fi
else
# 在 GitHub Actions 环境中压缩镜像文件
if is_github_actions; then
echo "信息:在 GitHub Actions 环境中压缩镜像文件..."
compress_image_file "$OUTPUTDIR/$target_img_name"
fi
fi
echo "信息:镜像打包完成: $OUTPUTDIR/$target_img_name"
}
pack_img_onecloud() {
local target_img_name="One-KVM_by-SilentWind_Onecloud_${DATE}.burn.img"
local rootfs_raw_img="$TMPDIR/rootfs.img"
local rootfs_sparse_img="$TMPDIR/7.rootfs.PARTITION.sparse"
local aml_packer="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
echo "信息:开始为 Onecloud 打包 burn 镜像..."
ensure_dir "$OUTPUTDIR"
# 确保在打包前已经正确卸载了所有挂载点和loop设备
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "警告发现未卸载的挂载点或loop设备尝试再次卸载..."
unmount_all
fi
# 自动下载 AmlImg 工具(如果不存在)
download_file_if_missing "$aml_packer" || { echo "错误:下载 AmlImg 工具失败" >&2; exit 1; }
sudo chmod +x "$aml_packer" || { echo "错误:设置 AmlImg 工具执行权限失败" >&2; exit 1; }
echo "信息:将 raw rootfs 转换为 sparse image..."
# 先删除可能存在的旧 sparse 文件
sudo rm -f "$rootfs_sparse_img"
sudo img2simg "$rootfs_raw_img" "$rootfs_sparse_img" || { echo "错误img2simg 转换失败" >&2; exit 1; }
sudo rm "$rootfs_raw_img" # 删除 raw 文件,因为它已被转换
echo "信息:使用 AmlImg 工具打包..."
sudo "$aml_packer" pack "$OUTPUTDIR/$target_img_name" "$TMPDIR/" || { echo "错误AmlImg 打包失败" >&2; exit 1; }
echo "信息:清理 Onecloud 临时文件..."
sudo rm -f "$TMPDIR/6.boot.PARTITION.sparse" "$TMPDIR/7.rootfs.PARTITION.sparse" "$TMPDIR/dts.img"
# 在 GitHub Actions 环境中压缩 Onecloud 镜像文件
if is_github_actions; then
echo "信息:在 GitHub Actions 环境中压缩 Onecloud 镜像文件..."
compress_image_file "$OUTPUTDIR/$target_img_name"
fi
echo "信息Onecloud burn 镜像打包完成: $OUTPUTDIR/$target_img_name"
}

View File

@ -1,41 +1,123 @@
#!/bin/bash #!/bin/bash
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
# 定义颜色代码
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[0;33m' YELLOW='\033[0;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' NC='\033[0m'
echo -e "${GREEN}One-KVM pre-starting...${NC}" # 输出日志的函数
log_info() {
echo -e "${GREEN}[INFO] $1${NC}"
}
log_warn() {
echo -e "${YELLOW}[WARN] $1${NC}"
}
log_error() {
echo -e "${RED}[ERROR] $1${NC}"
}
# 初始化检查
log_info "One-KVM 正在启动..."
# 首次初始化配置
if [ ! -f /etc/kvmd/.init_flag ]; then if [ ! -f /etc/kvmd/.init_flag ]; then
echo -e "${GREEN}One-KVM is initializing first...${NC}" \ log_info "首次初始化配置..."
&& mkdir -p /etc/kvmd/ \
&& mv /etc/kvmd_backup/* /etc/kvmd/ \
&& touch /etc/kvmd/.docker_flag \
&& sed -i 's/localhost.localdomain/docker/g' /etc/kvmd/meta.yaml \
&& sed -i 's/localhost/localhost:4430/g' /etc/kvmd/kvm_input.sh \
&& /usr/share/kvmd/kvmd-gencert --do-the-thing \
&& /usr/share/kvmd/kvmd-gencert --do-the-thing --vnc \
|| echo -e "${RED}One-KVM config moving and self-signed SSL certificates init failed.${NC}"
if [ "$NOSSL" == 1 ]; then # 创建必要目录并移动配置文件
echo -e "${GREEN}One-KVM self-signed SSL is disabled.${NC}" \ if mkdir -p /etc/kvmd/ && \
&& python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf -o nginx/https/enabled=false \ mv /etc/kvmd_backup/* /etc/kvmd/ && \
|| echo -e "${RED}One-KVM nginx config init failed.${NC}" touch /etc/kvmd/.docker_flag && \
sed -i 's/localhost.localdomain/docker/g' /etc/kvmd/meta.yaml && \
sed -i 's/localhost/localhost:4430/g' /etc/kvmd/kvm_input.sh; then
log_info "移动配置文件完成"
else else
python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf \ log_error "移动配置文件失败"
|| echo -e "${RED}One-KVM nginx config init failed.${NC}" exit 1
fi fi
# SSL证书配置
if ! /usr/share/kvmd/kvmd-gencert --do-the-thing; then
log_error "Nginx SSL 证书生成失败"
exit 1
fi
if ! /usr/share/kvmd/kvmd-gencert --do-the-thing --vnc; then
log_error "VNC SSL 证书生成失败"
exit 1
fi
# 设置用户名和密码
if [ ! -z "$USERNAME" ] && [ ! -z "$PASSWORD" ]; then
# 设置自定义用户名和密码
if python -m kvmd.apps.htpasswd del admin \
&& echo "$PASSWORD" | python -m kvmd.apps.htpasswd add -i "$USERNAME" \
&& echo "$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/vncpasswd \
&& echo "$USERNAME:$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/ipmipasswd; then
log_info "用户凭据设置成功"
else
log_error "用户凭据设置失败"
exit 1
fi
elif [ ! -z "$PASSWORD" ] && [ -z "$USERNAME" ]; then
# 只设置密码保持admin用户名
if echo "$PASSWORD" | python -m kvmd.apps.htpasswd set -i "admin" \
&& echo "$PASSWORD -> admin:$PASSWORD" > /etc/kvmd/vncpasswd \
&& echo "admin:$PASSWORD -> admin:$PASSWORD" > /etc/kvmd/ipmipasswd; then
log_info "admin 用户密码设置成功"
else
log_error "admin 用户密码设置失败"
exit 1
fi
else
log_warn "未设置 USERNAME 和 PASSWORD 环境变量,使用默认值(admin/admin)"
fi
# SSL开关配置
if [ "$NOSSL" == 1 ]; then
log_info "已禁用SSL"
if ! python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf -o nginx/https/enabled=false; then
log_error "Nginx 配置失败"
exit 1
fi
else
if ! python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf; then
log_error "Nginx 配置失败"
exit 1
fi
fi
# 认证配置
if [ "$NOAUTH" == "1" ]; then if [ "$NOAUTH" == "1" ]; then
sed -i "s/enabled: true/enabled: false/g" /etc/kvmd/override.yaml \ sed -i "s/enabled: true/enabled: false/g" /etc/kvmd/override.yaml
&& echo -e "${GREEN}One-KVM auth is disabled.${NC}" log_info "已禁用认证"
fi fi
#add supervisord conf #add supervisord conf
if [ "$NOWEBTERM" == "1" ]; then if [ "$NOWEBTERM" == "1" ]; then
echo -e "${GREEN}One-KVM webterm is disabled.${NC}" log_info "已禁用 WebTerm 功能"
rm -r /usr/share/kvmd/extras/webterm rm -r /usr/share/kvmd/extras/webterm
else else
cat >> /etc/kvmd/supervisord.conf << EOF cat >> /etc/kvmd/supervisord.conf << EOF
@ -58,7 +140,7 @@ EOF
fi fi
if [ "$NOVNC" == "1" ]; then if [ "$NOVNC" == "1" ]; then
echo -e "${GREEN}One-KVM VNC is disabled.${NC}" log_info "已禁用 VNC 功能"
rm -r /usr/share/kvmd/extras/vnc rm -r /usr/share/kvmd/extras/vnc
else else
cat >> /etc/kvmd/supervisord.conf << EOF cat >> /etc/kvmd/supervisord.conf << EOF
@ -77,7 +159,7 @@ EOF
fi fi
if [ "$NOIPMI" == "1" ]; then if [ "$NOIPMI" == "1" ]; then
echo -e "${GREEN}One-KVM IPMI is disabled.${NC}" log_info "已禁用 IPMI 功能"
rm -r /usr/share/kvmd/extras/ipmi rm -r /usr/share/kvmd/extras/ipmi
else else
cat >> /etc/kvmd/supervisord.conf << EOF cat >> /etc/kvmd/supervisord.conf << EOF
@ -95,50 +177,124 @@ redirect_stderr=true
EOF EOF
fi fi
if [ "$NOGOSTC" == "1" ]; then
log_info "已禁用 GOSTC 功能"
rm -rf /usr/share/kvmd/extras/gostc
else
cat >> /etc/kvmd/supervisord.conf << EOF
[program:kvmd-gostc]
command=/usr/bin/gostc -web-addr 127.0.0.1:18080
autostart=true
autorestart=true
startsecs=5
priority=300
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
EOF
fi
#switch OTG config #switch OTG config
if [ "$OTG" == "1" ]; then if [ "$OTG" == "1" ]; then
echo -e "${GREEN}One-KVM OTG is enabled.${NC}" log_info "已启用 OTG 功能"
sed -i "s/ch9329/otg/g" /etc/kvmd/override.yaml sed -i "s/ch9329/otg/g" /etc/kvmd/override.yaml
sed -i "s/device: \/dev\/ttyUSB0//g" /etc/kvmd/override.yaml sed -i "s|device: /dev/ttyUSB0||g" /etc/kvmd/override.yaml
fi
#if [ ! -z "$SHUTDOWNPIN" ! -z "$REBOOTPIN" ]; then
if [ ! -z "$VIDEONUM" ]; then
sed -i "s/\/dev\/video0/\/dev\/video$VIDEONUM/g" /etc/kvmd/override.yaml \
&& echo -e "${GREEN}One-KVM video device is set to /dev/video$VIDEONUM.${NC}"
fi
#set htpasswd
if [ ! -z "$USERNAME" ] && [ ! -z "$PASSWORD" ]; then
python -m kvmd.apps.htpasswd del admin \
&& echo $PASSWORD | python -m kvmd.apps.htpasswd set -i "$USERNAME" \
&& echo "$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/vncpasswd \
&& echo "$USERNAME:$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/ipmipasswd \
|| echo -e "${RED}One-KVM htpasswd init failed.${NC}"
else
echo -e "${YELLOW} USERNAME and PASSWORD environment variables is not set, using defalut(admin/admin).${NC}"
fi
if [ "$NOMSD" == 1 ]; then if [ "$NOMSD" == 1 ]; then
echo -e "${GREEN}One-KVM MSD is disabled.${NC}" log_info "已禁用 MSD 功能"
else else
sed -i "s/#type: otg/type: otg/g" /etc/kvmd/override.yaml sed -i "s/#type: otg/type: otg/g" /etc/kvmd/override.yaml
fi fi
fi
if [ ! -z "$VIDEONUM" ]; then
if sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/override.yaml && \
sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
log_info "视频设备已设置为 /dev/video$VIDEONUM"
fi
fi
if [ ! -z "$AUDIONUM" ]; then
if sed -i "s/hw:0/hw:$AUDIONUM/g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
log_info "音频设备已设置为 hw:$AUDIONUM"
fi
fi
if [ ! -z "$CH9329SPEED" ]; then
if sed -i "s/speed: 9600/speed: $CH9329SPEED/g" /etc/kvmd/override.yaml; then
log_info "CH9329 串口速率已设置为 $CH9329SPEED"
fi
fi
if [ ! -z "$CH9329NUM" ]; then
if sed -i "s|/dev/ttyUSB0|/dev/ttyUSB$CH9329NUM|g" /etc/kvmd/override.yaml; then
log_info "CH9329 串口设备已设置为 $CH9329NUM"
fi
fi
if [ ! -z "$CH9329TIMEOUT" ]; then
if sed -i "s/read_timeout: 0.3/read_timeout: $CH9329TIMEOUT/g" /etc/kvmd/override.yaml; then
log_info "CH9329 超时已设置为 $CH9329TIMEOUT"
fi
fi
if [ ! -z "$H264PRESET" ]; then
if sed -i "s/ultrafast/$H264PRESET/g" /etc/kvmd/override.yaml; then
log_info "H264 预设已设置为 $H264PRESET"
fi
fi
if [ ! -z "$VIDEOFORMAT" ]; then
if sed -i "s/--format=mjpeg/--format=$VIDEOFORMAT/g" /etc/kvmd/override.yaml; then
log_info "视频输入格式已设置为 $VIDEOFORMAT"
fi
fi
if [ ! -z "$HWENCODER" ]; then
if sed -i "s/--h264-hwenc=disabled/--h264-hwenc=$HWENCODER/g" /etc/kvmd/override.yaml; then
log_info "硬件编码器已设置为 $HWENCODER"
fi
fi
# 设置WEB端口
if [ ! -z "$HTTPPORT" ]; then
if sed -i "s/port: 8080/port: $HTTPPORT/g" /etc/kvmd/override.yaml; then
log_info "HTTP 端口已设置为 $HTTPPORT"
fi
fi
if [ ! -z "$HTTPSPORT" ]; then
if sed -i "s/port: 4430/port: $HTTPSPORT/g" /etc/kvmd/override.yaml; then
log_info "HTTPS 端口已设置为 $HTTPSPORT"
fi
fi
touch /etc/kvmd/.init_flag touch /etc/kvmd/.init_flag
log_info "初始化配置完成"
fi fi
#Trying usb_gadget # OTG设备配置
if [ "$OTG" == "1" ]; then if [ "$OTG" == "1" ]; then
echo "Trying OTG Port..." log_info "正在配置 OTG 设备..."
rm -r /run/kvmd/otg &> /dev/null rm -r /run/kvmd/otg &> /dev/null
modprobe libcomposite || echo -e "${RED}Linux libcomposite module modprobe failed.${NC}"
python -m kvmd.apps.otg start \ if ! modprobe libcomposite; then
&& ln -s /dev/hidg1 /dev/kvmd-hid-mouse \ log_error "加载 libcomposite 模块失败"
&& ln -s /dev/hidg0 /dev/kvmd-hid-keyboard \ exit 1
|| echo -e "${RED}OTG Port mount failed.${NC}" fi
if python -m kvmd.apps.otg start; then
ln -s /dev/hidg1 /dev/kvmd-hid-mouse
ln -s /dev/hidg0 /dev/kvmd-hid-keyboard
ln -s /dev/hidg2 /dev/kvmd-hid-mouse-alt
log_info "OTG 设备配置完成"
else
log_warn "OTG 设备挂载失败"
#exit 1
fi
fi fi
echo -e "${GREEN}One-KVM starting...${NC}" log_info "One-KVM 配置文件准备完成,正在启动服务..."
exec supervisord -c /etc/kvmd/supervisord.conf exec supervisord -c /etc/kvmd/supervisord.conf

View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=chainedbox

3
build/platform/cumebox2 Normal file
View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=cumebox2

3
build/platform/e900v22c Normal file
View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=e900v22c

View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=octopus-flanet

3
build/platform/oec-turbo Normal file
View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=oec-turbo

View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=onecloud-pro

View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=orangepi-zero

3
build/platform/vm Normal file
View File

@ -0,0 +1,3 @@
PIKVM_MODEL=v2_model
PIKVM_VIDEO=usb_video
PIKVM_BOARD=vm

21
build/record.txt Normal file
View File

@ -0,0 +1,21 @@
wget https://github.com/hzyitc/AmlImg/releases/download/v0.3.1/AmlImg_v0.3.1_linux_amd64 -O /mnt/src/image/onecloud/AmlImg_v0.3.1_linux_amd64
chmod +x /mnt/src/image/onecloud/AmlImg_v0.3.1_linux_amd64
#!/bin/bash
# 文件映射脚本
# 本地目录前缀:/mnt
# 远程URL前缀https://files.mofeng.run
LOCAL_PREFIX="/mnt"
REMOTE_PREFIX="https://files.mofeng.run"
# 文件相对路径
REL_PATH="src/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal_support-dvd-emulation.burn.img"
LOCAL_FILE="$LOCAL_PREFIX/$REL_PATH"
REMOTE_URL="$REMOTE_PREFIX/$REL_PATH"
echo "下载 $REMOTE_URL 到 $LOCAL_FILE"
mkdir -p "$(dirname "$LOCAL_FILE")"
wget -O "$LOCAL_FILE" "$REMOTE_URL"

View File

@ -0,0 +1,122 @@
#!/bin/bash
# 为玩客云/玩客云Pro 平台生成 MAC 地址的一次性脚本
# 此脚本在首次开机时执行,为 eth0 网卡生成并应用基于 SN 的 MAC 地址,失败时回退到随机 MAC
set -e
NETWORK_CONFIG="/etc/systemd/network/99-eth0.network"
LOCK_FILE="/var/lib/kvmd/.mac-generated"
PLATFORM_FILE="/usr/share/kvmd/platform"
EFUSE_SYSFS_PATH=""
SN_PREFIX=""
SN_EXPECTED_LENGTH=13
# 按平台设置 EFUSE 与 SN 参数;未知平台时按 efuse 路径探测
detect_platform_params() {
local platform=""
if [ -f "$PLATFORM_FILE" ]; then
platform=$(tr -d '\n' < "$PLATFORM_FILE")
fi
case "$platform" in
onecloud)
EFUSE_SYSFS_PATH="/sys/bus/nvmem/devices/meson8b-efuse0/nvmem"
SN_PREFIX="OCP"
;;
onecloud-pro)
EFUSE_SYSFS_PATH="/sys/devices/platform/efuse/efuse0/nvmem"
SN_PREFIX="ODC"
;;
esac
if [ -z "$EFUSE_SYSFS_PATH" ] || [ -z "$SN_PREFIX" ]; then
if [ -e "/sys/devices/platform/efuse/efuse0/nvmem" ]; then
EFUSE_SYSFS_PATH="/sys/devices/platform/efuse/efuse0/nvmem"
SN_PREFIX="ODC"
elif [ -e "/sys/bus/nvmem/devices/meson8b-efuse0/nvmem" ]; then
EFUSE_SYSFS_PATH="/sys/bus/nvmem/devices/meson8b-efuse0/nvmem"
SN_PREFIX="OCP"
fi
fi
}
# 检查是否已经执行过
if [ -f "$LOCK_FILE" ]; then
echo "MAC地址已经生成过跳过执行"
exit 0
fi
# 生成MAC地址函数
generate_random_mac() {
detect_platform_params
# 尝试根据 SN 生成唯一 MAC 地址
if [ -f "$EFUSE_SYSFS_PATH" ]; then
sn_offset=$(grep --binary-files=text -boP "$SN_PREFIX" "$EFUSE_SYSFS_PATH" | head -n1 | cut -d: -f1)
if [ -n "$sn_offset" ]; then
sn=$(cat "$EFUSE_SYSFS_PATH" | dd bs=1 skip="$sn_offset" count="$SN_EXPECTED_LENGTH" 2>/dev/null)
if [ ${#sn} -eq $SN_EXPECTED_LENGTH ]; then
echo "S/N: $sn" >&2 # 输出到 stderr避免干扰返回值
# 使用 SN 的 SHA-256 哈希生成后 5 字节(避免多余管道)
sn_hash=$(printf %s "$sn" | sha256sum | cut -d' ' -f1)
# 直接用 Bash 子串获取哈希末 10 个字符并插入分隔符
mac_hex=${sn_hash: -10}
mac_suffix=$(printf "%s:%s:%s:%s:%s" "${mac_hex:0:2}" "${mac_hex:2:2}" "${mac_hex:4:2}" "${mac_hex:6:2}" "${mac_hex:8:2}")
printf "02:%s\n" "$mac_suffix"
return 0
fi
fi
fi
# 若 SN 获取失败,回退到随机逻辑
echo "警告: 无法获取 SN回退到随机 MAC 生成" >&2
printf "02:%02x:%02x:%02x:%02x:%02x\n" \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256))
}
echo "正在生成基于 SN 的 MAC 地址..."
# 生成新的MAC地址
NEW_MAC=$(generate_random_mac)
echo "生成的MAC地址: $NEW_MAC"
# 验证 MAC 地址格式
if ! [[ $NEW_MAC =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then
echo "错误: 生成的 MAC 地址格式无效: $NEW_MAC"
exit 1
fi
# 备份原配置文件
if [ -f "$NETWORK_CONFIG" ]; then
cp "$NETWORK_CONFIG" "${NETWORK_CONFIG}.backup"
fi
# 更新网络配置文件
cat > "$NETWORK_CONFIG" << EOF
[Match]
Name=eth0
[Network]
DHCP=yes
[Link]
MACAddress=$NEW_MAC
EOF
echo "已更新网络配置文件: $NETWORK_CONFIG"
# 创建锁定文件,防止重复执行
mkdir -p "$(dirname "$LOCK_FILE")"
echo "MAC地址生成时间: $(date)" > "$LOCK_FILE"
# 禁用此服务,确保只运行一次
systemctl disable kvmd-generate-mac.service
echo "MAC地址生成完成: $NEW_MAC"
echo "服务已自动禁用,下次开机不会再执行"
exit 0

View File

@ -0,0 +1,34 @@
#!/bin/bash
# KVMD首次运行初始化脚本
# 在首次开机时执行KVMD服务启动前的必要初始化操作
set -e
LOCK_FILE="/var/lib/kvmd/.kvmd-firstrun-completed"
# 检查是否已经执行过
[ -f "$LOCK_FILE" ] && { echo "[KVMD-FirstRun] 初始化已完成,跳过执行"; exit 0; }
echo "[KVMD-FirstRun] 开始KVMD首次运行初始化..."
# 1. 生成KVMD主证书
echo "[KVMD-FirstRun] 生成KVMD主证书..."
kvmd-gencert --do-the-thing
# 2. 生成VNC证书
echo "[KVMD-FirstRun] 生成VNC证书..."
kvmd-gencert --do-the-thing --vnc
# 3. 生成nginx配置文件
echo "[KVMD-FirstRun] 生成nginx配置文件..."
kvmd-nginx-mkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf || echo "[KVMD-FirstRun] 警告: nginx配置生成失败"
# 创建锁定文件
mkdir -p "$(dirname "$LOCK_FILE")"
echo "KVMD首次运行初始化完成 - $(date)" > "$LOCK_FILE"
# 禁用服务
systemctl disable kvmd-firstrun.service || echo "[KVMD-FirstRun] 警告: 服务禁用失败"
echo "[KVMD-FirstRun] 初始化完成!"

View File

@ -0,0 +1,26 @@
[Unit]
Description=KVMD First Run Initialization (One-time)
Documentation=https://github.com/your-repo/One-KVM
Before=kvmd.service
Before=kvmd-nginx.service
Before=kvmd-otg.service
Before=kvmd-vnc.service
Before=kvmd-ipmi.service
Before=kvmd-webterm.service
Before=kvmd-janus.service
Before=kvmd-media.service
After=local-fs.target
After=network.target
Wants=local-fs.target
ConditionPathExists=!/var/lib/kvmd/.kvmd-firstrun-completed
[Service]
Type=oneshot
ExecStart=/usr/bin/kvmd-firstrun.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,18 @@
[Unit]
Description=Generate Random MAC Address for OneCloud (One-time)
Documentation=https://github.com/your-repo/One-KVM
Before=systemd-networkd.service
Before=network-pre.target
Wants=network-pre.target
After=local-fs.target
ConditionPathExists=!/var/lib/kvmd/.mac-generated
[Service]
Type=oneshot
ExecStart=/usr/local/bin/generate-random-mac.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

82
check-code.sh Executable file
View File

@ -0,0 +1,82 @@
#!/bin/bash
# 本地代码质量检查脚本
set -e
cd "$(dirname "$0")"
echo "🔍 运行代码质量检查..."
# 检查参数,如果有参数则只运行指定的检查
CHECK_TYPE="${1:-all}"
run_flake8() {
echo "📝 运行 flake8 代码风格检查..."
flake8 --config=testenv/linters/flake8.ini kvmd testenv/tests *.py
}
run_pylint() {
echo "🔎 运行 pylint 代码质量分析..."
pylint -j0 --rcfile=testenv/linters/pylint.ini --output-format=colorized --reports=no kvmd testenv/tests *.py || true
}
run_mypy() {
echo "🔧 运行 mypy 类型检查..."
mypy --config-file=testenv/linters/mypy.ini --cache-dir=testenv/.mypy_cache kvmd testenv/tests *.py || true
}
run_vulture() {
echo "💀 运行 vulture 死代码检测..."
vulture --ignore-names=_format_P,Plugin --ignore-decorators=@exposed_http,@exposed_ws,@pytest.fixture kvmd testenv/tests *.py testenv/linters/vulture-wl.py || true
}
run_eslint() {
echo "📜 运行 eslint JavaScript检查..."
if command -v eslint >/dev/null 2>&1; then
eslint --cache-location=/tmp --config=testenv/linters/eslintrc.js --color web/share/js || true
else
echo "⚠️ eslint 未安装,跳过"
fi
}
run_htmlhint() {
echo "📄 运行 htmlhint HTML检查..."
if command -v htmlhint >/dev/null 2>&1; then
htmlhint --config=testenv/linters/htmlhint.json web/*.html web/*/*.html || true
else
echo "⚠️ htmlhint 未安装,跳过"
fi
}
run_shellcheck() {
echo "🐚 运行 shellcheck Shell脚本检查..."
if command -v shellcheck >/dev/null 2>&1; then
shellcheck --color=always kvmd.install scripts/* || true
else
echo "⚠️ shellcheck 未安装,跳过"
fi
}
case "$CHECK_TYPE" in
flake8) run_flake8 ;;
pylint) run_pylint ;;
mypy) run_mypy ;;
vulture) run_vulture ;;
eslint) run_eslint ;;
htmlhint) run_htmlhint ;;
shellcheck) run_shellcheck ;;
all)
run_flake8
run_pylint
run_mypy
run_vulture
run_eslint
run_htmlhint
run_shellcheck
;;
*)
echo "用法: $0 [flake8|pylint|mypy|vulture|eslint|htmlhint|shellcheck|all]"
exit 1
;;
esac
echo "✅ 代码质量检查完成!"

View File

@ -1,4 +1,7 @@
video: { video: {
sink = "kvmd::ustreamer::h264" sink = "kvmd::ustreamer::h264"
} }
acap: {
device = "hw:0,0"
tc358743 = "/dev/video0"
}

View File

@ -1,4 +1,24 @@
#!/bin/bash #!/bin/bash
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
if [ -e /etc/update-motd.d/10-armbian-header ]; then /etc/update-motd.d/10-armbian-header; fi if [ -e /etc/update-motd.d/10-armbian-header ]; then /etc/update-motd.d/10-armbian-header; fi
if [ -e /etc/update-motd.d/30-armbian-sysinfo ]; then /etc/update-motd.d/30-armbian-sysinfo; fi if [ -e /etc/update-motd.d/30-armbian-sysinfo ]; then /etc/update-motd.d/30-armbian-sysinfo; fi
@ -15,8 +35,6 @@ printf "
____________________________________________________________________________ ____________________________________________________________________________
欢迎使用 One-KVM基于开源程序 PiKVM 的 IP-KVM 应用
项目链接: 项目链接:
* One-KVMhttps://github.com/mofeng-git/One-KVM * One-KVMhttps://github.com/mofeng-git/One-KVM

View File

@ -1,5 +1,26 @@
#!/bin/bash #!/bin/bash
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
ATX=USBRELAY_HID
echo $ATX echo $ATX
case $ATX in case $ATX in
GPIO) GPIO)

View File

@ -1,4 +1,24 @@
#!/bin/bash #!/bin/bash
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'

View File

@ -1,4 +1,24 @@
#!/bin/bash #!/bin/bash
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
case $1 in case $1 in
short) short)
gpioset -m time -s 1 SHUTDOWNPIN=0 gpioset -m time -s 1 SHUTDOWNPIN=0

View File

@ -1,3 +1,24 @@
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
import sys import sys
import hid import hid

View File

@ -1,4 +1,24 @@
#!/bin/bash #!/bin/bash
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
case $1 in case $1 in
short) short)
python3 /etc/kvmd/custom_atx/usbrelay_hid.py 1 on python3 /etc/kvmd/custom_atx/usbrelay_hid.py 1 on

View File

@ -1 +1 @@
admin:$apr1$.6mu9N8n$xOuGesr4JZZkdiZo/j318. admin:{SSHA512}3zSmw/L9zIkpQdX5bcy6HntTxltAzTuGNP6NjHRRgOcNZkA0K+Lsrj3QplO9Gr3BA5MYVVki9rAVnFNCcIdtYC6FkLJWCmHs

View File

@ -1,14 +1,11 @@
# This file describes the credentials for IPMI users. The first pair separated by colon # This file describes the credentials for IPMI users in format "login:password",
# is the login and password with which the user can access to IPMI. The second pair # one per line. The passwords are NOT encrypted.
# is the name and password with which the user can access to KVMD API. The arrow is used
# as a separator and shows the direction of user registration in the system.
# #
# WARNING! IPMI protocol is completely unsafe by design. In short, the authentication # WARNING! IPMI protocol is completely unsafe by design. In short, the authentication
# process for IPMI 2.0 mandates that the server send a salted SHA1 or MD5 hash of the # process for IPMI 2.0 mandates that the server send a salted SHA1 or MD5 hash of the
# requested user's password to the client, prior to the client authenticating. Never use # requested user's password to the client, prior to the client authenticating.
# the same passwords for KVMD and IPMI users. This default configuration is shown here
# for example only.
# #
# And even better not to use IPMI. Instead, you can directly use KVMD API via curl. # NEVER use the same passwords for KVMD and IPMI users.
# This default configuration is shown here just for the example only.
admin:admin -> admin:admin admin:admin

View File

@ -0,0 +1,97 @@
# Don't touch this file otherwise your device may stop working.
# Use override.yaml to modify required settings.
# You can find a working configuration in /usr/share/kvmd/configs.default/kvmd.
override: !include [override.d, override.yaml]
logging: !include logging.yaml
kvmd:
auth: !include auth.yaml
info:
hw:
ignore_past: true
fan:
unix: /run/kvmd/fan.sock
hid:
type: otg
atx:
type: gpio
power_led_pin: 4
hdd_led_pin: 5
power_switch_pin: 23
reset_switch_pin: 27
msd:
type: otg
streamer:
h264_bitrate:
default: 5000
cmd:
- "/usr/bin/ustreamer"
- "--device=/dev/kvmd-video"
- "--persistent"
- "--dv-timings"
- "--format=uyvy"
- "--buffers=6"
- "--encoder=m2m-image"
- "--workers=3"
- "--quality={quality}"
- "--desired-fps={desired_fps}"
- "--drop-same-frames=30"
- "--unix={unix}"
- "--unix-rm"
- "--unix-mode=0660"
- "--exit-on-parent-death"
- "--process-name-prefix={process_name_prefix}"
- "--notify-parent"
- "--no-log-colors"
- "--jpeg-sink=kvmd::ustreamer::jpeg"
- "--jpeg-sink-mode=0660"
- "--h264-sink=kvmd::ustreamer::h264"
- "--h264-sink-mode=0660"
- "--h264-bitrate={h264_bitrate}"
- "--h264-gop={h264_gop}"
gpio:
drivers:
__v4_locator__:
type: locator
scheme:
__v3_usb_breaker__:
pin: 22
mode: output
initial: true
pulse: false
__v4_locator__:
driver: __v4_locator__
pin: 12
mode: output
pulse: false
__v4_const1__:
pin: 6
mode: output
initial: false
switch: false
pulse: false
media:
memsink:
h264:
sink: "kvmd::ustreamer::h264"
vnc:
memsink:
jpeg:
sink: "kvmd::ustreamer::jpeg"
h264:
sink: "kvmd::ustreamer::h264"

View File

@ -0,0 +1,98 @@
# Don't touch this file otherwise your device may stop working.
# Use override.yaml to modify required settings.
# You can find a working configuration in /usr/share/kvmd/configs.default/kvmd.
override: !include [override.d, override.yaml]
logging: !include logging.yaml
kvmd:
auth: !include auth.yaml
info:
hw:
ignore_past: true
fan:
unix: /run/kvmd/fan.sock
hid:
type: otg
atx:
type: gpio
power_led_pin: 4
hdd_led_pin: 5
power_switch_pin: 23
reset_switch_pin: 27
msd:
type: otg
streamer:
h264_bitrate:
default: 5000
cmd:
- "/usr/bin/ustreamer"
- "--device=/dev/kvmd-video"
- "--persistent"
- "--dv-timings"
- "--format=uyvy"
- "--format-swap-rgb"
- "--buffers=8"
- "--encoder=m2m-image"
- "--workers=3"
- "--quality={quality}"
- "--desired-fps={desired_fps}"
- "--drop-same-frames=30"
- "--unix={unix}"
- "--unix-rm"
- "--unix-mode=0660"
- "--exit-on-parent-death"
- "--process-name-prefix={process_name_prefix}"
- "--notify-parent"
- "--no-log-colors"
- "--jpeg-sink=kvmd::ustreamer::jpeg"
- "--jpeg-sink-mode=0660"
- "--h264-sink=kvmd::ustreamer::h264"
- "--h264-sink-mode=0660"
- "--h264-bitrate={h264_bitrate}"
- "--h264-gop={h264_gop}"
gpio:
drivers:
__v4_locator__:
type: locator
scheme:
__v3_usb_breaker__:
pin: 22
mode: output
initial: true
pulse: false
__v4_locator__:
driver: __v4_locator__
pin: 12
mode: output
pulse: false
__v4_const1__:
pin: 6
mode: output
initial: false
switch: false
pulse: false
media:
memsink:
h264:
sink: "kvmd::ustreamer::h264"
vnc:
memsink:
jpeg:
sink: "kvmd::ustreamer::jpeg"
h264:
sink: "kvmd::ustreamer::h264"

View File

@ -4,6 +4,11 @@
# will be displayed in the web interface. # will be displayed in the web interface.
server: server:
host: localhost.localdomain host: "@auto"
kvm: {} kvm: {
base_on: "PiKVM",
app_name: "One-KVM",
main_version: "241204",
author: "SilentWind"
}

View File

@ -2,16 +2,14 @@ kvmd:
auth: auth:
enabled: true enabled: true
server:
unix_mode: 0666
access_log_format: '[%P / %{X-Real-IP}i] ''%r'' => 响应:%s大小%b来源''%{Referer}i'';用户代理:''%{User-Agent}i'''
atx: atx:
type: disabled type: disabled
hid: hid:
type: ch9329 type: ch9329
device: /dev/ttyUSB0 device: /dev/ttyUSB0
speed: 9600
read_timeout: 0.3
jiggler: jiggler:
active: false active: false
@ -23,6 +21,9 @@ kvmd:
msd: msd:
#type: otg #type: otg
remount_cmd: /bin/true remount_cmd: /bin/true
msd_path: /var/lib/kvmd/msd
normalfiles_path: NormalFiles
normalfiles_size: 256
ocr: ocr:
langs: langs:
@ -31,23 +32,23 @@ kvmd:
streamer: streamer:
resolution: resolution:
default: 1280x720 default: 1920x1080
forever: true forever: true
desired_fps: desired_fps:
default: 30 default: 60
max: 60 max: 60
h264_bitrate: h264_bitrate:
default: 2000 default: 8000
cmd: cmd:
- "/usr/bin/ustreamer" - "/usr/bin/ustreamer"
- "--device=/dev/video0" - "--device=/dev/video0"
- "--persistent" - "--persistent"
- "--format=mjpeg" - "--format=mjpeg"
- "--encoder=LIBX264-VIDEO" - "--encoder=FFMPEG-VIDEO"
- "--resolution={resolution}" - "--resolution={resolution}"
- "--desired-fps={desired_fps}" - "--desired-fps={desired_fps}"
- "--drop-same-frames=30" - "--drop-same-frames=30"
@ -65,6 +66,7 @@ kvmd:
- "--jpeg-sink-mode=0660" - "--jpeg-sink-mode=0660"
- "--h264-bitrate={h264_bitrate}" - "--h264-bitrate={h264_bitrate}"
- "--h264-gop={h264_gop}" - "--h264-gop={h264_gop}"
- "--h264-hwenc=disabled"
- "--slowdown" - "--slowdown"
gpio: gpio:
drivers: drivers:
@ -148,20 +150,26 @@ vnc:
h264: h264:
sink: "kvmd::ustreamer::h264" sink: "kvmd::ustreamer::h264"
media:
memsink:
h264:
sink: 'kvmd::ustreamer::h264'
jpeg:
sink: 'kvmd::ustreamer::jpeg'
otgnet: otgnet:
commands: commands:
post_start_cmd: post_start_cmd:
- "/bin/true" - "/bin/true"
pre_stop_cmd: pre_stop_cmd:
- "/bin/true" - "/bin/true"
sysctl_cmd:
#- "/usr/sbin/sysctl"
- "/bin/true"
nginx: nginx:
http: http:
port: 8080 port: 8080
https: https:
port: 4430 port: 4430
languages:
console: zh
web: zh

View File

@ -32,6 +32,16 @@ stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0 stdout_logfile_maxbytes = 0
redirect_stderr=true redirect_stderr=true
[program:kvmd-media]
command=python -m kvmd.apps.media --run
autostart=true
autorestart=true
priority=13
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
[program:kvmd-nginx] [program:kvmd-nginx]
command=nginx -c /etc/kvmd/nginx/nginx.conf -g 'daemon off;user root; error_log stderr;' command=nginx -c /etc/kvmd/nginx/nginx.conf -g 'daemon off;user root; error_log stderr;'
autostart=true autostart=true
@ -53,4 +63,3 @@ stopasgroup=true
stdout_logfile=/dev/stdout stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0 stdout_logfile_maxbytes = 0
redirect_stderr=true redirect_stderr=true

View File

@ -1,12 +1,9 @@
# This file describes the credentials for VNCAuth. The left part before arrow is a passphrase # This file contains passwords for the legacy VNCAuth, one per line.
# for VNCAuth. The right part is username and password with which the user can access to KVMD API. # The passwords are NOT encrypted.
# The arrow is used as a separator and shows the relationship of user registrations on the system.
# #
# Never use the same passwords for VNC and IPMI users. This default configuration is shown here # WARNING! The VNCAuth method is NOT secure and should not be used at all.
# for example only. # But we support it for compatibility with some clients.
# #
# If this file does not contain any entries, VNCAuth will be disabled and you will only be able # NEVER use the same passwords for KVMD, IPMI and VNCAuth users.
# to login in using your KVMD username and password using VeNCrypt methods.
# pa$$phr@se -> admin:password
admin -> admin:admin admin -> admin:admin

View File

@ -24,6 +24,7 @@ location @login {
location /login { location /login {
root /usr/share/kvmd/web; root /usr/share/kvmd/web;
include /etc/kvmd/nginx/loc-nocache.conf;
auth_request off; auth_request off;
} }
@ -65,6 +66,7 @@ location /api/hid/print {
proxy_pass http://kvmd; proxy_pass http://kvmd;
include /etc/kvmd/nginx/loc-proxy.conf; include /etc/kvmd/nginx/loc-proxy.conf;
include /etc/kvmd/nginx/loc-bigpost.conf; include /etc/kvmd/nginx/loc-bigpost.conf;
proxy_read_timeout 7d;
auth_request off; auth_request off;
} }

View File

@ -1,4 +1,2 @@
limit_rate 6250k;
limit_rate_after 50k;
client_max_body_size 0; client_max_body_size 0;
proxy_request_buffering off; proxy_request_buffering off;

View File

@ -39,9 +39,9 @@ http {
% if https_enabled: % if https_enabled:
server { server {
listen ${http_port}; listen ${http_ipv4}:${http_port};
% if ipv6_enabled: % if ipv6_enabled:
listen [::]:${http_port}; listen [${http_ipv6}]:${http_port};
% endif % endif
include /etc/kvmd/nginx/certbot.ctx-server.conf; include /etc/kvmd/nginx/certbot.ctx-server.conf;
location / { location / {
@ -54,9 +54,9 @@ http {
} }
server { server {
listen ${https_port} ssl http2; listen ${https_ipv4}:${https_port} ssl;
% if ipv6_enabled: % if ipv6_enabled:
listen [::]:${https_port} ssl http2; listen [${https_ipv6}]:${https_port} ssl;
% endif % endif
include /etc/kvmd/nginx/ssl.conf; include /etc/kvmd/nginx/ssl.conf;
include /etc/kvmd/nginx/kvmd.ctx-server.conf; include /etc/kvmd/nginx/kvmd.ctx-server.conf;
@ -66,9 +66,9 @@ http {
% else: % else:
server { server {
listen ${http_port}; listen ${http_ipv4}:${http_port};
% if ipv6_enabled: % if ipv6_enabled:
listen [::]:${http_port}; listen [${http_ipv6}]:${http_port};
% endif % endif
include /etc/kvmd/nginx/certbot.ctx-server.conf; include /etc/kvmd/nginx/certbot.ctx-server.conf;
include /etc/kvmd/nginx/kvmd.ctx-server.conf; include /etc/kvmd/nginx/kvmd.ctx-server.conf;

View File

@ -3,7 +3,7 @@
initramfs initramfs-linux.img followkernel initramfs initramfs-linux.img followkernel
hdmi_force_hotplug=1 hdmi_force_hotplug=1
gpu_mem=128 gpu_mem=192
enable_uart=1 enable_uart=1
dtoverlay=disable-bt dtoverlay=disable-bt

View File

@ -1 +1 @@
s/rootwait/rootwait cma=128M/g s/rootwait/rootwait cma=192M/g

View File

@ -0,0 +1,16 @@
[Unit]
Description=PiKVM - Local HID to KVMD proxy
After=kvmd.service systemd-udevd.service
[Service]
User=kvmd-localhid
Group=kvmd-localhid
Type=simple
Restart=always
RestartSec=3
ExecStart=/usr/bin/kvmd-localhid --run
TimeoutStopSec=3
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,16 @@
[Unit]
Description=PiKVM - Media proxy server
After=kvmd.service
[Service]
User=kvmd-media
Group=kvmd-media
Type=simple
Restart=always
RestartSec=3
ExecStart=/usr/bin/kvmd-media --run
TimeoutStopSec=3
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,12 @@
[Unit]
Description=PiKVM - Display reboot message on the OLED
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/bin/bash -c "kill -USR1 `systemctl show -P MainPID kvmd-oled`"
ExecStop=/bin/true
RemainAfterExit=yes
[Install]
WantedBy=reboot.target

View File

@ -0,0 +1,14 @@
[Unit]
Description=PiKVM - Display shutdown message on the OLED
Conflicts=reboot.target
Before=shutdown.target poweroff.target halt.target
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/bin/bash -c "kill -USR2 `systemctl show -P MainPID kvmd-oled`"
ExecStop=/bin/true
RemainAfterExit=yes
[Install]
WantedBy=shutdown.target

View File

@ -0,0 +1,15 @@
[Unit]
Description=PiKVM - A small OLED daemon
After=systemd-modules-load.service
ConditionPathExists=/dev/i2c-1
[Service]
Type=simple
Restart=always
RestartSec=3
ExecStartPre=/usr/bin/kvmd-oled --interval=3 --clear-on-exit --image=@hello.ppm
ExecStart=/usr/bin/kvmd-oled
TimeoutStopSec=3
[Install]
WantedBy=multi-user.target

View File

@ -1,15 +0,0 @@
[Unit]
Description=PiKVM - Video Passthrough on V4 Plus
Wants=dev-kvmd\x2dvideo.device
After=dev-kvmd\x2dvideo.device systemd-modules-load.service
[Service]
Type=simple
Restart=always
RestartSec=3
ExecStart=/usr/bin/ustreamer-v4p --unix-follow /run/kvmd/ustreamer.sock
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target

View File

@ -2,11 +2,11 @@
Description=PiKVM - EDID loader for TC358743 Description=PiKVM - EDID loader for TC358743
Wants=dev-kvmd\x2dvideo.device Wants=dev-kvmd\x2dvideo.device
After=dev-kvmd\x2dvideo.device systemd-modules-load.service After=dev-kvmd\x2dvideo.device systemd-modules-load.service
Before=kvmd.service kvmd-pass.service Before=kvmd.service
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --set-edid=file=/etc/kvmd/tc358743-edid.hex --fix-edid-checksums --info-edid ExecStart=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --set-edid=file=/etc/kvmd/tc358743-edid.hex --info-edid
ExecStop=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --clear-edid ExecStop=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --clear-edid
RemainAfterExit=true RemainAfterExit=true

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=One-KVM - The main daemon Description=One-KVM - The main daemon
After=network.target network-online.target nss-lookup.target After=network.target network-online.target nss-lookup.target rc-local.service
[Service] [Service]
User=kvmd User=kvmd

View File

@ -0,0 +1,8 @@
# Fix https://github.com/pikvm/pikvm/issues/1514:
# Wait for any single network interface, not all configured ones
# (Rationale: when user configures Wi-Fi via pikvm.txt or otherwise,
# we do not delete the Ethernet config, which means it will remain active
# regardless of whether the user ever intended to use Ethernet.)
[Service]
ExecStart=
ExecStart=/usr/lib/systemd/systemd-networkd-wait-online --any

View File

@ -1,15 +1,20 @@
g kvmd - - g kvmd - -
g kvmd-selfauth - -
g kvmd-media - -
g kvmd-pst - - g kvmd-pst - -
g kvmd-ipmi - - g kvmd-ipmi - -
g kvmd-vnc - - g kvmd-vnc - -
g kvmd-localhid - -
g kvmd-nginx - - g kvmd-nginx - -
g kvmd-janus - - g kvmd-janus - -
g kvmd-certbot - - g kvmd-certbot - -
u kvmd - "PiKVM - The main daemon" - u kvmd - "PiKVM - The main daemon" -
u kvmd-media - "PiKVM - The media proxy"
u kvmd-pst - "PiKVM - Persistent storage" - u kvmd-pst - "PiKVM - Persistent storage" -
u kvmd-ipmi - "PiKVM - IPMI to KVMD proxy" - u kvmd-ipmi - "PiKVM - IPMI to KVMD proxy" -
u kvmd-vnc - "PiKVM - VNC to KVMD/Streamer proxy" - u kvmd-vnc - "PiKVM - VNC to KVMD/Streamer proxy" -
u kvmd-localhid - "PiKVM - Local HID to KVMD proxy" -
u kvmd-nginx - "PiKVM - HTTP entrypoint" - u kvmd-nginx - "PiKVM - HTTP entrypoint" -
u kvmd-janus - "PiKVM - Janus WebRTC Gateway" - u kvmd-janus - "PiKVM - Janus WebRTC Gateway" -
u kvmd-certbot - "PiKVM - Certbot-Renew for KVMD-Nginx" u kvmd-certbot - "PiKVM - Certbot-Renew for KVMD-Nginx"
@ -19,18 +24,29 @@ m kvmd gpio
m kvmd uucp m kvmd uucp
m kvmd spi m kvmd spi
m kvmd systemd-journal m kvmd systemd-journal
m kvmd kvmd-media
m kvmd kvmd-pst
m kvmd-media kvmd
m kvmd-pst kvmd m kvmd-pst kvmd
m kvmd-ipmi kvmd m kvmd-ipmi kvmd
m kvmd-ipmi kvmd-selfauth
m kvmd-vnc kvmd m kvmd-vnc kvmd
m kvmd-vnc kvmd-selfauth
m kvmd-vnc kvmd-certbot m kvmd-vnc kvmd-certbot
m kvmd-localhid input
m kvmd-localhid kvmd
m kvmd-localhid kvmd-selfauth
m kvmd-janus kvmd m kvmd-janus kvmd
m kvmd-janus audio m kvmd-janus audio
m kvmd-nginx kvmd m kvmd-nginx kvmd
m kvmd-nginx kvmd-media
m kvmd-nginx kvmd-janus m kvmd-nginx kvmd-janus
m kvmd-nginx kvmd-certbot m kvmd-nginx kvmd-certbot

View File

@ -1,3 +1,15 @@
# Here are described some bindings for PiKVM devices. # Here are described some bindings for PiKVM devices.
# Do not edit this file. # Do not edit this file.
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="eda3", SYMLINK+="kvmd-hid-bridge"
ACTION!="remove", KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="eda3", SYMLINK+="kvmd-hid-bridge"
ACTION!="remove", KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="1080", SYMLINK+="kvmd-switch"
# Disable USB autosuspend for critical devices
ACTION!="remove", SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="eda3", GOTO="kvmd-usb"
ACTION!="remove", SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="1080", GOTO="kvmd-usb"
GOTO="end"
LABEL="kvmd-usb"
ATTR{power/control}="on", ATTR{power/autosuspend_delay_ms}="-1"
LABEL="end"

View File

@ -1,7 +0,0 @@
# https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name
# https://wiki.archlinux.org/index.php/Udev#Setting_static_device_names
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTR{index}=="0", GROUP="kvmd", SYMLINK+="kvmd-video"
KERNEL=="hidg0", GROUP="kvmd", SYMLINK+="kvmd-hid-keyboard"
KERNEL=="hidg1", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse"
KERNEL=="hidg2", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse-alt"
KERNEL=="ttyUSB0", GROUP="kvmd", SYMLINK+="kvmd-hid"

View File

@ -4,3 +4,4 @@ KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", PROGRAM="/usr/bin/kvmd-udev-hdm
KERNEL=="hidg0", GROUP="kvmd", SYMLINK+="kvmd-hid-keyboard" KERNEL=="hidg0", GROUP="kvmd", SYMLINK+="kvmd-hid-keyboard"
KERNEL=="hidg1", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse" KERNEL=="hidg1", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse"
KERNEL=="hidg2", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse-alt" KERNEL=="hidg2", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse-alt"
KERNEL=="ttyUSB0", GROUP="kvmd", SYMLINK+="kvmd-hid"

File diff suppressed because it is too large Load Diff

View File

@ -49,13 +49,15 @@ oneeighth 0x03 shift altgr
quotedbl 0x04 quotedbl 0x04
3 0x04 shift 3 0x04 shift
numbersign 0x04 altgr numbersign 0x04 altgr
sterling 0x04 shift altgr # KVMD
#sterling 0x04 shift altgr
# evdev 5 (0x5), QKeyCode "4", number 0x5 # evdev 5 (0x5), QKeyCode "4", number 0x5
apostrophe 0x05 apostrophe 0x05
4 0x05 shift 4 0x05 shift
braceleft 0x05 altgr braceleft 0x05 altgr
dollar 0x05 shift altgr # KVMD
#dollar 0x05 shift altgr
# evdev 6 (0x6), QKeyCode "5", number 0x6 # evdev 6 (0x6), QKeyCode "5", number 0x6
parenleft 0x06 parenleft 0x06
@ -91,7 +93,8 @@ plusminus 0x0a shift altgr
agrave 0x0b agrave 0x0b
0 0x0b shift 0 0x0b shift
at 0x0b altgr at 0x0b altgr
degree 0x0b shift altgr # KVMD
#degree 0x0b shift altgr
# evdev 12 (0xc), QKeyCode "minus", number 0xc # evdev 12 (0xc), QKeyCode "minus", number 0xc
parenright 0x0c parenright 0x0c
@ -122,7 +125,8 @@ AE 0x10 shift altgr
z 0x11 z 0x11
Z 0x11 shift Z 0x11 shift
guillemotleft 0x11 altgr guillemotleft 0x11 altgr
less 0x11 shift altgr #KVMD
#less 0x11 shift altgr
# evdev 18 (0x12), QKeyCode "e", number 0x12 # evdev 18 (0x12), QKeyCode "e", number 0x12
e 0x12 e 0x12
@ -200,7 +204,8 @@ Greek_OMEGA 0x1e shift altgr
s 0x1f s 0x1f
S 0x1f shift S 0x1f shift
ssharp 0x1f altgr ssharp 0x1f altgr
section 0x1f shift altgr # KVMD
#section 0x1f shift altgr
# evdev 32 (0x20), QKeyCode "d", number 0x20 # evdev 32 (0x20), QKeyCode "d", number 0x20
d 0x20 d 0x20
@ -247,7 +252,8 @@ Lstroke 0x26 shift altgr
# evdev 39 (0x27), QKeyCode "semicolon", number 0x27 # evdev 39 (0x27), QKeyCode "semicolon", number 0x27
m 0x27 m 0x27
M 0x27 shift M 0x27 shift
mu 0x27 altgr # KVMD
#mu 0x27 altgr
masculine 0x27 shift altgr masculine 0x27 shift altgr
# evdev 40 (0x28), QKeyCode "apostrophe", number 0x28 # evdev 40 (0x28), QKeyCode "apostrophe", number 0x28
@ -280,7 +286,8 @@ Lstroke 0x2c shift altgr
x 0x2d x 0x2d
X 0x2d shift X 0x2d shift
guillemotright 0x2d altgr guillemotright 0x2d altgr
greater 0x2d shift altgr # KVMD
#greater 0x2d shift altgr
# evdev 46 (0x2e), QKeyCode "c", number 0x2e # evdev 46 (0x2e), QKeyCode "c", number 0x2e
c 0x2e c 0x2e

View File

@ -0,0 +1,6 @@
name: GOSTC
description: GOSTC Server
icon: share/svg/gostc.svg
path: extras/gostc
daemon: kvmd-gostc
place: 11

View File

@ -0,0 +1,7 @@
location /extras/gostc {
proxy_pass http://127.0.0.1:18080;
include /etc/kvmd/nginx/loc-proxy.conf;
include /etc/kvmd/nginx/loc-websocket.conf;
include /etc/kvmd/nginx/loc-login.conf;
include /etc/kvmd/nginx/loc-nocache.conf;
}

View File

@ -0,0 +1,5 @@
name: Media
description: KVMD Media Proxy
path: media
daemon: kvmd-media
place: -1

View File

@ -0,0 +1,3 @@
upstream media {
server unix:/run/kvmd/media.sock fail_timeout=0s max_fails=0;
}

View File

@ -0,0 +1,7 @@
location /api/media/ws {
rewrite ^/api/media/ws$ /ws break;
rewrite ^/api/media/ws\?(.*)$ /ws?$1 break;
proxy_pass http://media;
include /etc/kvmd/nginx/loc-proxy.conf;
include /etc/kvmd/nginx/loc-websocket.conf;
}

View File

@ -69,9 +69,10 @@ class _X11Key:
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class _KeyMapping: class _KeyMapping:
web_name: str web_name: str
evdev_name: str
mcu_code: int mcu_code: int
usb_key: _UsbKey usb_key: _UsbKey
ps2_key: _Ps2Key ps2_key: (_Ps2Key | None)
at1_code: int at1_code: int
x11_keys: set[_X11Key] x11_keys: set[_X11Key]
@ -107,7 +108,9 @@ def _parse_usb_key(key: str) -> _UsbKey:
return _UsbKey(code, is_modifier) return _UsbKey(code, is_modifier)
def _parse_ps2_key(key: str) -> _Ps2Key: def _parse_ps2_key(key: str) -> (_Ps2Key | None):
if ":" not in key:
return None
(code_type, raw_code) = key.split(":") (code_type, raw_code) = key.split(":")
return _Ps2Key( return _Ps2Key(
code=int(raw_code, 16), code=int(raw_code, 16),
@ -122,6 +125,7 @@ def _read_keymap_csv(path: str) -> list[_KeyMapping]:
if len(row) >= 6: if len(row) >= 6:
keymap.append(_KeyMapping( keymap.append(_KeyMapping(
web_name=row["web_name"], web_name=row["web_name"],
evdev_name=row["evdev_name"],
mcu_code=int(row["mcu_code"]), mcu_code=int(row["mcu_code"]),
usb_key=_parse_usb_key(row["usb_key"]), usb_key=_parse_usb_key(row["usb_key"]),
ps2_key=_parse_ps2_key(row["ps2_key"]), ps2_key=_parse_ps2_key(row["ps2_key"]),
@ -150,6 +154,7 @@ def main() -> None:
# Fields list: # Fields list:
# - Web # - Web
# - Linux/evdev
# - MCU code # - MCU code
# - USB code (^ for the modifier mask) # - USB code (^ for the modifier mask)
# - PS/2 key # - PS/2 key

View File

@ -24,8 +24,8 @@ upload:
bash -ex -c " \ bash -ex -c " \
current=`cat .current`; \ current=`cat .current`; \
if [ '$($@_CURRENT)' == 'spi' ] || [ '$($@_CURRENT)' == 'aum' ]; then \ if [ '$($@_CURRENT)' == 'spi' ] || [ '$($@_CURRENT)' == 'aum' ]; then \
gpioset 0 25=1; \ gpioset -c gpiochip0 -t 30ms,0 25=1; \
gpioset 0 25=0; \ gpioset -c gpiochip0 -t 30ms,0 25=0; \
fi \ fi \
" "
platformio run --environment '$($@_CURRENT)' --project-conf 'platformio-$($@_CONFIG).ini' --target upload platformio run --environment '$($@_CURRENT)' --project-conf 'platformio-$($@_CONFIG).ini' --target upload

View File

@ -2,6 +2,7 @@ programmer
id = "rpi"; id = "rpi";
desc = "RPi SPI programmer"; desc = "RPi SPI programmer";
type = "linuxspi"; type = "linuxspi";
prog_modes = PM_ISP;
reset = 25; reset = 25;
baudrate = 400000; baudrate = 400000;
; ;

View File

@ -148,5 +148,8 @@ void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) {
case 109: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 19; return; // KanaMode case 109: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 19; return; // KanaMode
case 110: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 100; return; // Convert case 110: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 100; return; // Convert
case 111: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 103; return; // NonConvert case 111: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 103; return; // NonConvert
case 112: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 35; return; // AudioVolumeMute
case 113: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 50; return; // AudioVolumeUp
case 114: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 33; return; // AudioVolumeDown
} }
} }

View File

@ -38,7 +38,9 @@ void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) {
switch (code) { switch (code) {
% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): % for km in sorted(keymap, key=operator.attrgetter("mcu_code")):
% if km.ps2_key is not None:
case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.web_name} case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.web_name}
% endif
% endfor % endfor
} }
} }

View File

@ -136,6 +136,10 @@ uint8_t keymapUsb(uint8_t code) {
case 109: return 136; // KanaMode case 109: return 136; // KanaMode
case 110: return 138; // Convert case 110: return 138; // Convert
case 111: return 139; // NonConvert case 111: return 139; // NonConvert
case 112: return 127; // AudioVolumeMute
case 113: return 128; // AudioVolumeUp
case 114: return 129; // AudioVolumeDown
case 115: return 111; // F20
default: return 0; default: return 0;
} }
} }

View File

@ -82,8 +82,6 @@ build_flags =
-DCDC_DISABLED -DCDC_DISABLED
upload_protocol = custom upload_protocol = custom
upload_flags = upload_flags =
-C
$PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf
-C -C
+avrdude-rpi.conf +avrdude-rpi.conf
-P -P

View File

@ -28,11 +28,14 @@ define libdep
endef endef
.pico-sdk: .pico-sdk:
$(call libdep,pico-sdk,raspberrypi/pico-sdk,6a7db34ff63345a7badec79ebea3aaef1712f374) $(call libdep,pico-sdk,raspberrypi/pico-sdk,6a7db34ff63345a7badec79ebea3aaef1712f374)
.pico-sdk.patches: .pico-sdk
patch -d .pico-sdk -p1 < patches/pico-sdk.patch
touch .pico-sdk.patches
.tinyusb: .tinyusb:
$(call libdep,tinyusb,hathach/tinyusb,d713571cd44f05d2fc72efc09c670787b74106e0) $(call libdep,tinyusb,hathach/tinyusb,d713571cd44f05d2fc72efc09c670787b74106e0)
.ps2x2pico: .ps2x2pico:
$(call libdep,ps2x2pico,No0ne/ps2x2pico,404aaf02949d5bee8013e3b5d0b3239abf6e13bd) $(call libdep,ps2x2pico,No0ne/ps2x2pico,26ce89d597e598bb0ac636622e064202d91a9efc)
deps: .pico-sdk .tinyusb .ps2x2pico deps: .pico-sdk .pico-sdk.patches .tinyusb .ps2x2pico
.PHONY: deps .PHONY: deps

View File

@ -0,0 +1,10 @@
diff --git a/tools/pioasm/CMakeLists.txt b/tools/pioasm/CMakeLists.txt
index 322408a..fc8e4b8 100644
--- a/tools/pioasm/CMakeLists.txt
+++ b/tools/pioasm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
project(pioasm CXX)
set(CMAKE_CXX_STANDARD 11)

View File

@ -19,7 +19,7 @@ target_sources(${target_name} PRIVATE
${PS2_PATH}/ps2in.c ${PS2_PATH}/ps2in.c
${PS2_PATH}/ps2kb.c ${PS2_PATH}/ps2kb.c
${PS2_PATH}/ps2ms.c ${PS2_PATH}/ps2ms.c
${PS2_PATH}/scancodesets.c ${PS2_PATH}/scancodes.c
) )
target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage) target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage)
target_compile_options(${target_name} PRIVATE -Wall -Wextra) target_compile_options(${target_name} PRIVATE -Wall -Wextra)

View File

@ -53,7 +53,7 @@ static u8 _kbd_keys[6] = {0};
static u8 _mouse_buttons = 0; static u8 _mouse_buttons = 0;
static s16 _mouse_abs_x = 0; static s16 _mouse_abs_x = 0;
static s16 _mouse_abs_y = 0; static s16 _mouse_abs_y = 0;
#define _MOUSE_CLEAR { _mouse_buttons = 0; _mouse_abs_x = 0; _mouse_abs_y = 0; } #define _MOUSE_CLEAR { _mouse_buttons = 0; }
static void _kbd_sync_report(bool new); static void _kbd_sync_report(bool new);
@ -193,7 +193,7 @@ void ph_usb_send_clear(void) {
if (PH_O_IS_MOUSE_USB) { if (PH_O_IS_MOUSE_USB) {
_MOUSE_CLEAR; _MOUSE_CLEAR;
if (PH_O_IS_MOUSE_USB_ABS) { if (PH_O_IS_MOUSE_USB_ABS) {
_mouse_abs_send_report(0, 0); _mouse_abs_send_report(_mouse_abs_x, _mouse_abs_y);
} else { // PH_O_IS_MOUSE_USB_REL } else { // PH_O_IS_MOUSE_USB_REL
_mouse_rel_send_report(0, 0, 0, 0); _mouse_rel_send_report(0, 0, 0, 0);
} }

View File

@ -138,6 +138,10 @@ inline u8 ph_usb_keymap(u8 key) {
case 109: return 136; // KanaMode case 109: return 136; // KanaMode
case 110: return 138; // Convert case 110: return 138; // Convert
case 111: return 139; // NonConvert case 111: return 139; // NonConvert
case 112: return 127; // AudioVolumeMute
case 113: return 128; // AudioVolumeUp
case 114: return 129; // AudioVolumeDown
case 115: return 111; // F20
} }
return 0; return 0;
} }

View File

@ -1,112 +1,116 @@
web_name,mcu_code,usb_key,ps2_key,at1_code,x11_names web_name,evdev_name,mcu_code,usb_key,ps2_key,at1_code,x11_names
KeyA,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a" KeyA,KEY_A,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a"
KeyB,2,0x05,reg:0x32,0x30,"^XK_B,XK_b" KeyB,KEY_B,2,0x05,reg:0x32,0x30,"^XK_B,XK_b"
KeyC,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c" KeyC,KEY_C,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c"
KeyD,4,0x07,reg:0x23,0x20,"^XK_D,XK_d" KeyD,KEY_D,4,0x07,reg:0x23,0x20,"^XK_D,XK_d"
KeyE,5,0x08,reg:0x24,0x12,"^XK_E,XK_e" KeyE,KEY_E,5,0x08,reg:0x24,0x12,"^XK_E,XK_e"
KeyF,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f" KeyF,KEY_F,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f"
KeyG,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g" KeyG,KEY_G,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g"
KeyH,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h" KeyH,KEY_H,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h"
KeyI,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i" KeyI,KEY_I,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i"
KeyJ,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j" KeyJ,KEY_J,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j"
KeyK,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k" KeyK,KEY_K,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k"
KeyL,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l" KeyL,KEY_L,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l"
KeyM,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m" KeyM,KEY_M,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m"
KeyN,14,0x11,reg:0x31,0x31,"^XK_N,XK_n" KeyN,KEY_N,14,0x11,reg:0x31,0x31,"^XK_N,XK_n"
KeyO,15,0x12,reg:0x44,0x18,"^XK_O,XK_o" KeyO,KEY_O,15,0x12,reg:0x44,0x18,"^XK_O,XK_o"
KeyP,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p" KeyP,KEY_P,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p"
KeyQ,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q" KeyQ,KEY_Q,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q"
KeyR,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r" KeyR,KEY_R,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r"
KeyS,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s" KeyS,KEY_S,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s"
KeyT,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t" KeyT,KEY_T,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t"
KeyU,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u" KeyU,KEY_U,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u"
KeyV,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v" KeyV,KEY_V,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v"
KeyW,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w" KeyW,KEY_W,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w"
KeyX,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x" KeyX,KEY_X,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x"
KeyY,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y" KeyY,KEY_Y,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y"
KeyZ,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z" KeyZ,KEY_Z,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z"
Digit1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam" Digit1,KEY_1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam"
Digit2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at" Digit2,KEY_2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at"
Digit3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign" Digit3,KEY_3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign"
Digit4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar" Digit4,KEY_4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar"
Digit5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent" Digit5,KEY_5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent"
Digit6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum" Digit6,KEY_6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum"
Digit7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand" Digit7,KEY_7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand"
Digit8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk" Digit8,KEY_8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk"
Digit9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft" Digit9,KEY_9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft"
Digit0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright" Digit0,KEY_0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright"
Enter,37,0x28,reg:0x5a,0x1c,XK_Return Enter,KEY_ENTER,37,0x28,reg:0x5a,0x1c,XK_Return
Escape,38,0x29,reg:0x76,0x01,XK_Escape Escape,KEY_ESC,38,0x29,reg:0x76,0x01,XK_Escape
Backspace,39,0x2a,reg:0x66,0x0e,XK_BackSpace Backspace,KEY_BACKSPACE,39,0x2a,reg:0x66,0x0e,XK_BackSpace
Tab,40,0x2b,reg:0x0d,0x0f,XK_Tab Tab,KEY_TAB,40,0x2b,reg:0x0d,0x0f,XK_Tab
Space,41,0x2c,reg:0x29,0x39,XK_space Space,KEY_SPACE,41,0x2c,reg:0x29,0x39,XK_space
Minus,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore" Minus,KEY_MINUS,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore"
Equal,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus" Equal,KEY_EQUAL,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus"
BracketLeft,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft" BracketLeft,KEY_LEFTBRACE,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft"
BracketRight,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright" BracketRight,KEY_RIGHTBRACE,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright"
Backslash,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar" Backslash,KEY_BACKSLASH,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar"
Semicolon,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon" Semicolon,KEY_SEMICOLON,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon"
Quote,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl" Quote,KEY_APOSTROPHE,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl"
Backquote,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde" Backquote,KEY_GRAVE,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde"
Comma,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less" Comma,KEY_COMMA,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less"
Period,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater" Period,KEY_DOT,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater"
Slash,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question" Slash,KEY_SLASH,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question"
CapsLock,53,0x39,reg:0x58,0x3a,XK_Caps_Lock CapsLock,KEY_CAPSLOCK,53,0x39,reg:0x58,0x3a,XK_Caps_Lock
F1,54,0x3a,reg:0x05,0x3b,XK_F1 F1,KEY_F1,54,0x3a,reg:0x05,0x3b,XK_F1
F2,55,0x3b,reg:0x06,0x3c,XK_F2 F2,KEY_F2,55,0x3b,reg:0x06,0x3c,XK_F2
F3,56,0x3c,reg:0x04,0x3d,XK_F3 F3,KEY_F3,56,0x3c,reg:0x04,0x3d,XK_F3
F4,57,0x3d,reg:0x0c,0x3e,XK_F4 F4,KEY_F4,57,0x3d,reg:0x0c,0x3e,XK_F4
F5,58,0x3e,reg:0x03,0x3f,XK_F5 F5,KEY_F5,58,0x3e,reg:0x03,0x3f,XK_F5
F6,59,0x3f,reg:0x0b,0x40,XK_F6 F6,KEY_F6,59,0x3f,reg:0x0b,0x40,XK_F6
F7,60,0x40,reg:0x83,0x41,XK_F7 F7,KEY_F7,60,0x40,reg:0x83,0x41,XK_F7
F8,61,0x41,reg:0x0a,0x42,XK_F8 F8,KEY_F8,61,0x41,reg:0x0a,0x42,XK_F8
F9,62,0x42,reg:0x01,0x43,XK_F9 F9,KEY_F9,62,0x42,reg:0x01,0x43,XK_F9
F10,63,0x43,reg:0x09,0x44,XK_F10 F10,KEY_F10,63,0x43,reg:0x09,0x44,XK_F10
F11,64,0x44,reg:0x78,0x57,XK_F11 F11,KEY_F11,64,0x44,reg:0x78,0x57,XK_F11
F12,65,0x45,reg:0x07,0x58,XK_F12 F12,KEY_F12,65,0x45,reg:0x07,0x58,XK_F12
PrintScreen,66,0x46,print:0xff,0x54,XK_Sys_Req PrintScreen,KEY_SYSRQ,66,0x46,print:0xff,0x54,XK_Sys_Req
Insert,67,0x49,spec:0x70,0xe052,XK_Insert Insert,KEY_INSERT,67,0x49,spec:0x70,0xe052,XK_Insert
Home,68,0x4a,spec:0x6c,0xe047,XK_Home Home,KEY_HOME,68,0x4a,spec:0x6c,0xe047,XK_Home
PageUp,69,0x4b,spec:0x7d,0xe049,XK_Page_Up PageUp,KEY_PAGEUP,69,0x4b,spec:0x7d,0xe049,XK_Page_Up
Delete,70,0x4c,spec:0x71,0xe053,XK_Delete Delete,KEY_DELETE,70,0x4c,spec:0x71,0xe053,XK_Delete
End,71,0x4d,spec:0x69,0xe04f,XK_End End,KEY_END,71,0x4d,spec:0x69,0xe04f,XK_End
PageDown,72,0x4e,spec:0x7a,0xe051,XK_Page_Down PageDown,KEY_PAGEDOWN,72,0x4e,spec:0x7a,0xe051,XK_Page_Down
ArrowRight,73,0x4f,spec:0x74,0xe04d,XK_Right ArrowRight,KEY_RIGHT,73,0x4f,spec:0x74,0xe04d,XK_Right
ArrowLeft,74,0x50,spec:0x6b,0xe04b,XK_Left ArrowLeft,KEY_LEFT,74,0x50,spec:0x6b,0xe04b,XK_Left
ArrowDown,75,0x51,spec:0x72,0xe050,XK_Down ArrowDown,KEY_DOWN,75,0x51,spec:0x72,0xe050,XK_Down
ArrowUp,76,0x52,spec:0x75,0xe048,XK_Up ArrowUp,KEY_UP,76,0x52,spec:0x75,0xe048,XK_Up
ControlLeft,77,^0x01,reg:0x14,0x1d,XK_Control_L ControlLeft,KEY_LEFTCTRL,77,^0x01,reg:0x14,0x1d,XK_Control_L
ShiftLeft,78,^0x02,reg:0x12,0x2a,XK_Shift_L ShiftLeft,KEY_LEFTSHIFT,78,^0x02,reg:0x12,0x2a,XK_Shift_L
AltLeft,79,^0x04,reg:0x11,0x38,XK_Alt_L AltLeft,KEY_LEFTALT,79,^0x04,reg:0x11,0x38,XK_Alt_L
MetaLeft,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L" MetaLeft,KEY_LEFTMETA,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L"
ControlRight,81,^0x10,spec:0x14,0xe01d,XK_Control_R ControlRight,KEY_RIGHTCTRL,81,^0x10,spec:0x14,0xe01d,XK_Control_R
ShiftRight,82,^0x20,reg:0x59,0x36,XK_Shift_R ShiftRight,KEY_RIGHTSHIFT,82,^0x20,reg:0x59,0x36,XK_Shift_R
AltRight,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift" AltRight,KEY_RIGHTALT,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift"
MetaRight,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R" MetaRight,KEY_RIGHTMETA,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R"
Pause,85,0x48,pause:0xff,0xe046,XK_Pause Pause,KEY_PAUSE,85,0x48,pause:0xff,0xe046,XK_Pause
ScrollLock,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock ScrollLock,KEY_SCROLLLOCK,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock
NumLock,87,0x53,reg:0x77,0x45,XK_Num_Lock NumLock,KEY_NUMLOCK,87,0x53,reg:0x77,0x45,XK_Num_Lock
ContextMenu,88,0x65,spec:0x2f,0xe05d,XK_Menu ContextMenu,KEY_CONTEXT_MENU,88,0x65,spec:0x2f,0xe05d,XK_Menu
NumpadDivide,89,0x54,spec:0x4a,0xe035,XK_KP_Divide NumpadDivide,KEY_KPSLASH,89,0x54,spec:0x4a,0xe035,XK_KP_Divide
NumpadMultiply,90,0x55,reg:0x7c,0x37,XK_multiply NumpadMultiply,KEY_KPASTERISK,90,0x55,reg:0x7c,0x37,XK_multiply
NumpadSubtract,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract NumpadSubtract,KEY_KPMINUS,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract
NumpadAdd,92,0x57,reg:0x79,0x4e,XK_KP_Add NumpadAdd,KEY_KPPLUS,92,0x57,reg:0x79,0x4e,XK_KP_Add
NumpadEnter,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter NumpadEnter,KEY_KPENTER,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter
Numpad1,94,0x59,reg:0x69,0x4f,XK_KP_1 Numpad1,KEY_KP1,94,0x59,reg:0x69,0x4f,XK_KP_1
Numpad2,95,0x5a,reg:0x72,0x50,XK_KP_2 Numpad2,KEY_KP2,95,0x5a,reg:0x72,0x50,XK_KP_2
Numpad3,96,0x5b,reg:0x7a,0x51,XK_KP_3 Numpad3,KEY_KP3,96,0x5b,reg:0x7a,0x51,XK_KP_3
Numpad4,97,0x5c,reg:0x6b,0x4b,XK_KP_4 Numpad4,KEY_KP4,97,0x5c,reg:0x6b,0x4b,XK_KP_4
Numpad5,98,0x5d,reg:0x73,0x4c,XK_KP_5 Numpad5,KEY_KP5,98,0x5d,reg:0x73,0x4c,XK_KP_5
Numpad6,99,0x5e,reg:0x74,0x4d,XK_KP_6 Numpad6,KEY_KP6,99,0x5e,reg:0x74,0x4d,XK_KP_6
Numpad7,100,0x5f,reg:0x6c,0x47,XK_KP_7 Numpad7,KEY_KP7,100,0x5f,reg:0x6c,0x47,XK_KP_7
Numpad8,101,0x60,reg:0x75,0x48,XK_KP_8 Numpad8,KEY_KP8,101,0x60,reg:0x75,0x48,XK_KP_8
Numpad9,102,0x61,reg:0x7d,0x49,XK_KP_9 Numpad9,KEY_KP9,102,0x61,reg:0x7d,0x49,XK_KP_9
Numpad0,103,0x62,reg:0x70,0x52,XK_KP_0 Numpad0,KEY_KP0,103,0x62,reg:0x70,0x52,XK_KP_0
NumpadDecimal,104,0x63,reg:0x71,0x53,XK_KP_Decimal NumpadDecimal,KEY_KPDOT,104,0x63,reg:0x71,0x53,XK_KP_Decimal
Power,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep Power,KEY_POWER,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep
IntlBackslash,106,0x64,reg:0x61,0x56,"" IntlBackslash,KEY_102ND,106,0x64,reg:0x61,0x56,
IntlYen,107,0x89,reg:0x6a,0x7d,"" IntlYen,KEY_YEN,107,0x89,reg:0x6a,0x7d,
IntlRo,108,0x87,reg:0x51,0x73,"" IntlRo,KEY_RO,108,0x87,reg:0x51,0x73,
KanaMode,109,0x88,reg:0x13,0x70,"" KanaMode,KEY_KATAKANA,109,0x88,reg:0x13,0x70,
Convert,110,0x8a,reg:0x64,0x79,"" Convert,KEY_HENKAN,110,0x8a,reg:0x64,0x79,
NonConvert,111,0x8b,reg:0x67,0x7b,"" NonConvert,KEY_MUHENKAN,111,0x8b,reg:0x67,0x7b,
AudioVolumeMute,KEY_MUTE,112,0x7f,spec:0x23,0xe020,
AudioVolumeUp,KEY_VOLUMEUP,113,0x80,spec:0x32,0xe030,
AudioVolumeDown,KEY_VOLUMEDOWN,114,0x81,spec:0x21,0xe02e,
F20,KEY_F20,115,0x6f,,0x5a,

1 web_name evdev_name mcu_code usb_key ps2_key at1_code x11_names
2 KeyA KEY_A 1 0x04 reg:0x1c 0x1e ^XK_A,XK_a
3 KeyB KEY_B 2 0x05 reg:0x32 0x30 ^XK_B,XK_b
4 KeyC KEY_C 3 0x06 reg:0x21 0x2e ^XK_C,XK_c
5 KeyD KEY_D 4 0x07 reg:0x23 0x20 ^XK_D,XK_d
6 KeyE KEY_E 5 0x08 reg:0x24 0x12 ^XK_E,XK_e
7 KeyF KEY_F 6 0x09 reg:0x2b 0x21 ^XK_F,XK_f
8 KeyG KEY_G 7 0x0a reg:0x34 0x22 ^XK_G,XK_g
9 KeyH KEY_H 8 0x0b reg:0x33 0x23 ^XK_H,XK_h
10 KeyI KEY_I 9 0x0c reg:0x43 0x17 ^XK_I,XK_i
11 KeyJ KEY_J 10 0x0d reg:0x3b 0x24 ^XK_J,XK_j
12 KeyK KEY_K 11 0x0e reg:0x42 0x25 ^XK_K,XK_k
13 KeyL KEY_L 12 0x0f reg:0x4b 0x26 ^XK_L,XK_l
14 KeyM KEY_M 13 0x10 reg:0x3a 0x32 ^XK_M,XK_m
15 KeyN KEY_N 14 0x11 reg:0x31 0x31 ^XK_N,XK_n
16 KeyO KEY_O 15 0x12 reg:0x44 0x18 ^XK_O,XK_o
17 KeyP KEY_P 16 0x13 reg:0x4d 0x19 ^XK_P,XK_p
18 KeyQ KEY_Q 17 0x14 reg:0x15 0x10 ^XK_Q,XK_q
19 KeyR KEY_R 18 0x15 reg:0x2d 0x13 ^XK_R,XK_r
20 KeyS KEY_S 19 0x16 reg:0x1b 0x1f ^XK_S,XK_s
21 KeyT KEY_T 20 0x17 reg:0x2c 0x14 ^XK_T,XK_t
22 KeyU KEY_U 21 0x18 reg:0x3c 0x16 ^XK_U,XK_u
23 KeyV KEY_V 22 0x19 reg:0x2a 0x2f ^XK_V,XK_v
24 KeyW KEY_W 23 0x1a reg:0x1d 0x11 ^XK_W,XK_w
25 KeyX KEY_X 24 0x1b reg:0x22 0x2d ^XK_X,XK_x
26 KeyY KEY_Y 25 0x1c reg:0x35 0x15 ^XK_Y,XK_y
27 KeyZ KEY_Z 26 0x1d reg:0x1a 0x2c ^XK_Z,XK_z
28 Digit1 KEY_1 27 0x1e reg:0x16 0x02 XK_1,^XK_exclam
29 Digit2 KEY_2 28 0x1f reg:0x1e 0x03 XK_2,^XK_at
30 Digit3 KEY_3 29 0x20 reg:0x26 0x04 XK_3,^XK_numbersign
31 Digit4 KEY_4 30 0x21 reg:0x25 0x05 XK_4,^XK_dollar
32 Digit5 KEY_5 31 0x22 reg:0x2e 0x06 XK_5,^XK_percent
33 Digit6 KEY_6 32 0x23 reg:0x36 0x07 XK_6,^XK_asciicircum
34 Digit7 KEY_7 33 0x24 reg:0x3d 0x08 XK_7,^XK_ampersand
35 Digit8 KEY_8 34 0x25 reg:0x3e 0x09 XK_8,^XK_asterisk
36 Digit9 KEY_9 35 0x26 reg:0x46 0x0a XK_9,^XK_parenleft
37 Digit0 KEY_0 36 0x27 reg:0x45 0x0b XK_0,^XK_parenright
38 Enter KEY_ENTER 37 0x28 reg:0x5a 0x1c XK_Return
39 Escape KEY_ESC 38 0x29 reg:0x76 0x01 XK_Escape
40 Backspace KEY_BACKSPACE 39 0x2a reg:0x66 0x0e XK_BackSpace
41 Tab KEY_TAB 40 0x2b reg:0x0d 0x0f XK_Tab
42 Space KEY_SPACE 41 0x2c reg:0x29 0x39 XK_space
43 Minus KEY_MINUS 42 0x2d reg:0x4e 0x0c XK_minus,^XK_underscore
44 Equal KEY_EQUAL 43 0x2e reg:0x55 0x0d XK_equal,^XK_plus
45 BracketLeft KEY_LEFTBRACE 44 0x2f reg:0x54 0x1a XK_bracketleft,^XK_braceleft
46 BracketRight KEY_RIGHTBRACE 45 0x30 reg:0x5b 0x1b XK_bracketright,^XK_braceright
47 Backslash KEY_BACKSLASH 46 0x31 reg:0x5d 0x2b XK_backslash,^XK_bar
48 Semicolon KEY_SEMICOLON 47 0x33 reg:0x4c 0x27 XK_semicolon,^XK_colon
49 Quote KEY_APOSTROPHE 48 0x34 reg:0x52 0x28 XK_apostrophe,^XK_quotedbl
50 Backquote KEY_GRAVE 49 0x35 reg:0x0e 0x29 XK_grave,^XK_asciitilde
51 Comma KEY_COMMA 50 0x36 reg:0x41 0x33 XK_comma,^XK_less
52 Period KEY_DOT 51 0x37 reg:0x49 0x34 XK_period,^XK_greater
53 Slash KEY_SLASH 52 0x38 reg:0x4a 0x35 XK_slash,^XK_question
54 CapsLock KEY_CAPSLOCK 53 0x39 reg:0x58 0x3a XK_Caps_Lock
55 F1 KEY_F1 54 0x3a reg:0x05 0x3b XK_F1
56 F2 KEY_F2 55 0x3b reg:0x06 0x3c XK_F2
57 F3 KEY_F3 56 0x3c reg:0x04 0x3d XK_F3
58 F4 KEY_F4 57 0x3d reg:0x0c 0x3e XK_F4
59 F5 KEY_F5 58 0x3e reg:0x03 0x3f XK_F5
60 F6 KEY_F6 59 0x3f reg:0x0b 0x40 XK_F6
61 F7 KEY_F7 60 0x40 reg:0x83 0x41 XK_F7
62 F8 KEY_F8 61 0x41 reg:0x0a 0x42 XK_F8
63 F9 KEY_F9 62 0x42 reg:0x01 0x43 XK_F9
64 F10 KEY_F10 63 0x43 reg:0x09 0x44 XK_F10
65 F11 KEY_F11 64 0x44 reg:0x78 0x57 XK_F11
66 F12 KEY_F12 65 0x45 reg:0x07 0x58 XK_F12
67 PrintScreen KEY_SYSRQ 66 0x46 print:0xff 0x54 XK_Sys_Req
68 Insert KEY_INSERT 67 0x49 spec:0x70 0xe052 XK_Insert
69 Home KEY_HOME 68 0x4a spec:0x6c 0xe047 XK_Home
70 PageUp KEY_PAGEUP 69 0x4b spec:0x7d 0xe049 XK_Page_Up
71 Delete KEY_DELETE 70 0x4c spec:0x71 0xe053 XK_Delete
72 End KEY_END 71 0x4d spec:0x69 0xe04f XK_End
73 PageDown KEY_PAGEDOWN 72 0x4e spec:0x7a 0xe051 XK_Page_Down
74 ArrowRight KEY_RIGHT 73 0x4f spec:0x74 0xe04d XK_Right
75 ArrowLeft KEY_LEFT 74 0x50 spec:0x6b 0xe04b XK_Left
76 ArrowDown KEY_DOWN 75 0x51 spec:0x72 0xe050 XK_Down
77 ArrowUp KEY_UP 76 0x52 spec:0x75 0xe048 XK_Up
78 ControlLeft KEY_LEFTCTRL 77 ^0x01 reg:0x14 0x1d XK_Control_L
79 ShiftLeft KEY_LEFTSHIFT 78 ^0x02 reg:0x12 0x2a XK_Shift_L
80 AltLeft KEY_LEFTALT 79 ^0x04 reg:0x11 0x38 XK_Alt_L
81 MetaLeft KEY_LEFTMETA 80 ^0x08 spec:0x1f 0xe05b XK_Meta_L,XK_Super_L
82 ControlRight KEY_RIGHTCTRL 81 ^0x10 spec:0x14 0xe01d XK_Control_R
83 ShiftRight KEY_RIGHTSHIFT 82 ^0x20 reg:0x59 0x36 XK_Shift_R
84 AltRight KEY_RIGHTALT 83 ^0x40 spec:0x11 0xe038 XK_Alt_R,XK_ISO_Level3_Shift
85 MetaRight KEY_RIGHTMETA 84 ^0x80 spec:0x27 0xe05c XK_Meta_R,XK_Super_R
86 Pause KEY_PAUSE 85 0x48 pause:0xff 0xe046 XK_Pause
87 ScrollLock KEY_SCROLLLOCK 86 0x47 reg:0x7e 0x46 XK_Scroll_Lock
88 NumLock KEY_NUMLOCK 87 0x53 reg:0x77 0x45 XK_Num_Lock
89 ContextMenu KEY_CONTEXT_MENU 88 0x65 spec:0x2f 0xe05d XK_Menu
90 NumpadDivide KEY_KPSLASH 89 0x54 spec:0x4a 0xe035 XK_KP_Divide
91 NumpadMultiply KEY_KPASTERISK 90 0x55 reg:0x7c 0x37 XK_multiply
92 NumpadSubtract KEY_KPMINUS 91 0x56 reg:0x7b 0x4a XK_KP_Subtract
93 NumpadAdd KEY_KPPLUS 92 0x57 reg:0x79 0x4e XK_KP_Add
94 NumpadEnter KEY_KPENTER 93 0x58 spec:0x5a 0xe01c XK_KP_Enter
95 Numpad1 KEY_KP1 94 0x59 reg:0x69 0x4f XK_KP_1
96 Numpad2 KEY_KP2 95 0x5a reg:0x72 0x50 XK_KP_2
97 Numpad3 KEY_KP3 96 0x5b reg:0x7a 0x51 XK_KP_3
98 Numpad4 KEY_KP4 97 0x5c reg:0x6b 0x4b XK_KP_4
99 Numpad5 KEY_KP5 98 0x5d reg:0x73 0x4c XK_KP_5
100 Numpad6 KEY_KP6 99 0x5e reg:0x74 0x4d XK_KP_6
101 Numpad7 KEY_KP7 100 0x5f reg:0x6c 0x47 XK_KP_7
102 Numpad8 KEY_KP8 101 0x60 reg:0x75 0x48 XK_KP_8
103 Numpad9 KEY_KP9 102 0x61 reg:0x7d 0x49 XK_KP_9
104 Numpad0 KEY_KP0 103 0x62 reg:0x70 0x52 XK_KP_0
105 NumpadDecimal KEY_KPDOT 104 0x63 reg:0x71 0x53 XK_KP_Decimal
106 Power KEY_POWER 105 0x66 spec:0x5e 0xe05e XK_XF86_Sleep
107 IntlBackslash KEY_102ND 106 0x64 reg:0x61 0x56
108 IntlYen KEY_YEN 107 0x89 reg:0x6a 0x7d
109 IntlRo KEY_RO 108 0x87 reg:0x51 0x73
110 KanaMode KEY_KATAKANA 109 0x88 reg:0x13 0x70
111 Convert KEY_HENKAN 110 0x8a reg:0x64 0x79
112 NonConvert KEY_MUHENKAN 111 0x8b reg:0x67 0x7b
113 AudioVolumeMute KEY_MUTE 112 0x7f spec:0x23 0xe020
114 AudioVolumeUp KEY_VOLUMEUP 113 0x80 spec:0x32 0xe030
115 AudioVolumeDown KEY_VOLUMEDOWN 114 0x81 spec:0x21 0xe02e
116 F20 KEY_F20 115 0x6f 0x5a

View File

@ -27,7 +27,8 @@ post_upgrade() {
done done
chown kvmd /var/lib/kvmd/msd 2>/dev/null || true chown kvmd /var/lib/kvmd/msd 2>/dev/null || true
chown kvmd-pst /var/lib/kvmd/pst 2>/dev/null || true chown kvmd-pst:kvmd-pst /var/lib/kvmd/pst 2>/dev/null || true
chmod 1775 /var/lib/kvmd/pst 2>/dev/null || true
if [ ! -e /etc/kvmd/nginx/ssl/server.crt ]; then if [ ! -e /etc/kvmd/nginx/ssl/server.crt ]; then
echo "==> Generating KVMD-Nginx certificate ..." echo "==> Generating KVMD-Nginx certificate ..."
@ -92,6 +93,32 @@ disable_overscan=1
EOF EOF
fi fi
if [[ "$(vercmp "$2" 4.4)" -lt 0 ]]; then
systemctl disable kvmd-pass || true
fi
if [[ "$(vercmp "$2" 4.5)" -lt 0 ]]; then
sed -i 's/X-kvmd\.pst-user=kvmd-pst/X-kvmd.pst-user=kvmd-pst,X-kvmd.pst-group=kvmd-pst/g' /etc/fstab
touch -t 200701011000 /etc/fstab
fi
if [[ "$(vercmp "$2" 4.31)" -lt 0 ]]; then
if [[ "$(systemctl is-enabled kvmd-janus || true)" = enabled || "$(systemctl is-enabled kvmd-janus-static || true)" = enabled ]]; then
systemctl enable kvmd-media || true
fi
fi
if [[ "$(vercmp "$2" 4.47)" -lt 0 ]]; then
cp /usr/share/kvmd/configs.default/janus/janus.plugin.ustreamer.jcfg /etc/kvmd/janus || true
fi
if [[ "$(vercmp "$2" 4.60)" -lt 0 ]]; then
if grep -q "^dtoverlay=vc4-kms-v3d" /boot/config.txt; then
sed -i -e "s/cma=128M/cma=192M/g" /boot/cmdline.txt || true
sed -i -e "s/^gpu_mem=128/gpu_mem=192/g" /boot/config.txt || true
fi
fi
# Some update deletes /etc/motd, WTF # Some update deletes /etc/motd, WTF
# shellcheck disable=SC2015,SC2166 # shellcheck disable=SC2015,SC2166
[ ! -f /etc/motd -a -f /etc/motd.pacsave ] && mv /etc/motd.pacsave /etc/motd || true [ ! -f /etc/motd -a -f /etc/motd.pacsave ] && mv /etc/motd.pacsave /etc/motd || true

View File

@ -20,4 +20,4 @@
# ========================================================================== # # ========================================================================== #
__version__ = "4.3" __version__ = "4.94"

View File

@ -23,6 +23,7 @@
import asyncio import asyncio
import threading import threading
import dataclasses import dataclasses
import typing
import gpiod import gpiod
@ -83,9 +84,9 @@ class AioReader: # pylint: disable=too-many-instance-attributes
self.__path, self.__path,
consumer=self.__consumer, consumer=self.__consumer,
config={tuple(pins): gpiod.LineSettings(edge_detection=gpiod.line.Edge.BOTH)}, config={tuple(pins): gpiod.LineSettings(edge_detection=gpiod.line.Edge.BOTH)},
) as line_request: ) as line_req:
line_request.wait_edge_events(0.1) line_req.wait_edge_events(0.1)
self.__values = { self.__values = {
pin: _DebouncedValue( pin: _DebouncedValue(
initial=bool(value.value), initial=bool(value.value),
@ -93,32 +94,33 @@ class AioReader: # pylint: disable=too-many-instance-attributes
notifier=self.__notifier, notifier=self.__notifier,
loop=self.__loop, loop=self.__loop,
) )
for (pin, value) in zip(pins, line_request.get_values(pins)) for (pin, value) in zip(pins, line_req.get_values(pins))
} }
self.__loop.call_soon_threadsafe(self.__notifier.notify) self.__loop.call_soon_threadsafe(self.__notifier.notify)
while not self.__stop_event.is_set(): while not self.__stop_event.is_set():
if line_request.wait_edge_events(1): if line_req.wait_edge_events(1):
new: dict[int, bool] = {} new: dict[int, bool] = {}
for event in line_request.read_edge_events(): for event in line_req.read_edge_events():
(pin, value) = self.__parse_event(event) (pin, state) = self.__parse_event(event)
new[pin] = value new[pin] = state
for (pin, value) in new.items(): for (pin, state) in new.items():
self.__values[pin].set(value) self.__values[pin].set(state)
else: # Timeout else: # Timeout
# XXX: Лимит был актуален для 1.6. Надо проверить, поменялось ли это в 2.x. # XXX: Лимит был актуален для 1.6. Надо проверить, поменялось ли это в 2.x.
# Размер буфера ядра - 16 эвентов на линии. При превышении этого числа, # Размер буфера ядра - 16 эвентов на линии. При превышении этого числа,
# новые эвенты потеряются. Это не баг, это фича, как мне объяснили в LKML. # новые эвенты потеряются. Это не баг, это фича, как мне объяснили в LKML.
# Штош. Будем с этим жить и синхронизировать состояния при таймауте. # Штош. Будем с этим жить и синхронизировать состояния при таймауте.
for (pin, value) in zip(pins, line_request.get_values(pins)): for (pin, value) in zip(pins, line_req.get_values(pins)):
self.__values[pin].set(bool(value.value)) # type: ignore self.__values[pin].set(bool(value.value)) # type: ignore
def __parse_event(self, event: gpiod.EdgeEvent) -> tuple[int, bool]: def __parse_event(self, event: gpiod.EdgeEvent) -> tuple[int, bool]:
if event.event_type == event.Type.RISING_EDGE: match event.event_type:
case event.Type.RISING_EDGE:
return (event.line_offset, True) return (event.line_offset, True)
elif event.event_type == event.Type.FALLING_EDGE: case event.Type.FALLING_EDGE:
return (event.line_offset, False) return (event.line_offset, False)
raise RuntimeError(f"Invalid event {event} type: {event.type}") typing.assert_never(event.event_type)
class _DebouncedValue: class _DebouncedValue:

View File

@ -22,8 +22,6 @@
import subprocess import subprocess
from .languages import Languages
from .logging import get_logger from .logging import get_logger
from . import tools from . import tools
@ -38,13 +36,13 @@ async def remount(name: str, base_cmd: list[str], rw: bool) -> bool:
part.format(mode=mode) part.format(mode=mode)
for part in base_cmd for part in base_cmd
] ]
logger.info(Languages().gettext("Remounting %s storage to %s: %s ..."), name, mode.upper(), tools.cmdfmt(cmd)) logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), tools.cmdfmt(cmd))
try: try:
proc = await aioproc.log_process(cmd, logger) proc = await aioproc.log_process(cmd, logger)
if proc.returncode != 0: if proc.returncode != 0:
assert proc.returncode is not None assert proc.returncode is not None
raise subprocess.CalledProcessError(proc.returncode, cmd) raise subprocess.CalledProcessError(proc.returncode, cmd)
except Exception as err: except Exception as ex:
logger.error(Languages().gettext("Can't remount %s storage: %s"), name, tools.efmt(err)) logger.error("Can't remount %s storage: %s", name, tools.efmt(ex))
return False return False
return True return True

View File

@ -59,14 +59,25 @@ def queue_get_last_sync( # pylint: disable=invalid-name
# ===== # =====
class AioProcessNotifier: class AioProcessNotifier:
def __init__(self) -> None: def __init__(self) -> None:
self.__queue: "multiprocessing.Queue[None]" = multiprocessing.Queue() self.__queue: "multiprocessing.Queue[int]" = multiprocessing.Queue()
def notify(self) -> None: def notify(self, mask: int=0) -> None:
self.__queue.put_nowait(None) self.__queue.put_nowait(mask)
async def wait(self) -> None: async def wait(self) -> int:
while not (await queue_get_last(self.__queue, 0.1))[0]: while True:
pass mask = await aiotools.run_async(self.__get)
if mask >= 0:
return mask
def __get(self) -> int:
try:
mask = self.__queue.get(timeout=0.1)
while not self.__queue.empty():
mask |= self.__queue.get()
return mask
except queue.Empty:
return -1
# ===== # =====

View File

@ -26,7 +26,6 @@ import asyncio
import asyncio.subprocess import asyncio.subprocess
import logging import logging
from .languages import Languages
import setproctitle import setproctitle
from .logging import get_logger from .logging import get_logger
@ -86,7 +85,7 @@ async def log_stdout_infinite(proc: asyncio.subprocess.Process, logger: logging.
else: else:
empty += 1 empty += 1
if empty == 100: # asyncio bug if empty == 100: # asyncio bug
raise RuntimeError(Languages().gettext("Asyncio process: too many empty lines")) raise RuntimeError("Asyncio process: too many empty lines")
async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: logging.Logger) -> None: # pylint: disable=no-member async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: logging.Logger) -> None: # pylint: disable=no-member
@ -101,14 +100,14 @@ async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: lo
if proc.returncode is not None: if proc.returncode is not None:
raise raise
await proc.wait() await proc.wait()
logger.info(Languages().gettext("Process killed: retcode=%d"), proc.returncode) logger.info("Process killed: retcode=%d", proc.returncode)
except asyncio.CancelledError: except asyncio.CancelledError:
pass pass
except Exception: except Exception:
if proc.returncode is None: if proc.returncode is None:
logger.exception(Languages().gettext("Can't kill process pid=%d"), proc.pid) logger.exception("Can't kill process pid=%d", proc.pid)
else: else:
logger.info(Languages().gettext("Process killed: retcode=%d"), proc.returncode) logger.info("Process killed: retcode=%d", proc.returncode)
def rename_process(suffix: str, prefix: str="kvmd") -> None: def rename_process(suffix: str, prefix: str="kvmd") -> None:
@ -117,7 +116,7 @@ def rename_process(suffix: str, prefix: str="kvmd") -> None:
def settle(name: str, suffix: str, prefix: str="kvmd") -> logging.Logger: def settle(name: str, suffix: str, prefix: str="kvmd") -> logging.Logger:
logger = get_logger(1) logger = get_logger(1)
logger.info(Languages().gettext("Started %s pid=%d"), name, os.getpid()) logger.info("Started %s pid=%d", name, os.getpid())
os.setpgrp() os.setpgrp()
rename_process(suffix, prefix) rename_process(suffix, prefix)
return logger return logger

View File

@ -45,6 +45,11 @@ async def read_file(path: str) -> str:
return (await file.read()) return (await file.read())
async def write_file(path: str, text: str) -> None:
async with aiofiles.open(path, "w") as file:
await file.write(text)
# ===== # =====
def run(coro: Coroutine, final: (Coroutine | None)=None) -> None: def run(coro: Coroutine, final: (Coroutine | None)=None) -> None:
# https://github.com/aio-libs/aiohttp/blob/a1d4dac1d/aiohttp/web.py#L515 # https://github.com/aio-libs/aiohttp/blob/a1d4dac1d/aiohttp/web.py#L515
@ -112,9 +117,9 @@ def shield_fg(aw: Awaitable): # type: ignore
if inner.cancelled(): if inner.cancelled():
outer.forced_cancel() outer.forced_cancel()
else: else:
err = inner.exception() ex = inner.exception()
if err is not None: if ex is not None:
outer.set_exception(err) outer.set_exception(ex)
else: else:
outer.set_result(inner.result()) outer.set_result(inner.result())
@ -166,7 +171,7 @@ def create_deadly_task(name: str, coro: Coroutine) -> asyncio.Task:
except asyncio.CancelledError: except asyncio.CancelledError:
pass pass
except Exception: except Exception:
logger.exception("Unhandled exception in deadly task, killing myself ...") logger.exception("Unhandled exception in deadly task %r, killing myself ...", name)
pid = os.getpid() pid = os.getpid()
if pid == 1: if pid == 1:
os._exit(1) # Docker workaround # pylint: disable=protected-access os._exit(1) # Docker workaround # pylint: disable=protected-access
@ -206,6 +211,18 @@ async def wait_first(*aws: asyncio.Task) -> tuple[set[asyncio.Task], set[asyncio
return (await asyncio.wait(list(aws), return_when=asyncio.FIRST_COMPLETED)) return (await asyncio.wait(list(aws), return_when=asyncio.FIRST_COMPLETED))
# =====
async def spawn_and_follow(*coros: Coroutine) -> None:
tasks: list[asyncio.Task] = list(map(asyncio.create_task, coros))
try:
await asyncio.gather(*tasks)
except Exception:
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
raise
# ===== # =====
async def close_writer(writer: asyncio.StreamWriter) -> bool: async def close_writer(writer: asyncio.StreamWriter) -> bool:
closing = writer.is_closing() closing = writer.is_closing()
@ -232,25 +249,26 @@ async def close_writer(writer: asyncio.StreamWriter) -> bool:
# ===== # =====
class AioNotifier: class AioNotifier:
def __init__(self) -> None: def __init__(self) -> None:
self.__queue: "asyncio.Queue[None]" = asyncio.Queue() self.__queue: "asyncio.Queue[int]" = asyncio.Queue()
def notify(self) -> None: def notify(self, mask: int=0) -> None:
self.__queue.put_nowait(None) self.__queue.put_nowait(mask)
async def wait(self, timeout: (float | None)=None) -> None: async def wait(self, timeout: (float | None)=None) -> int:
mask = 0
if timeout is None: if timeout is None:
await self.__queue.get() mask = await self.__queue.get()
else: else:
try: try:
await asyncio.wait_for( mask = await asyncio.wait_for(
asyncio.ensure_future(self.__queue.get()), asyncio.ensure_future(self.__queue.get()),
timeout=timeout, timeout=timeout,
) )
except asyncio.TimeoutError: except asyncio.TimeoutError:
return # False return -1
while not self.__queue.empty(): while not self.__queue.empty():
await self.__queue.get() mask |= await self.__queue.get()
# return True return mask
# ===== # =====
@ -296,7 +314,7 @@ class AioExclusiveRegion:
def is_busy(self) -> bool: def is_busy(self) -> bool:
return self.__busy return self.__busy
async def enter(self) -> None: def enter(self) -> None:
if not self.__busy: if not self.__busy:
self.__busy = True self.__busy = True
try: try:
@ -308,22 +326,22 @@ class AioExclusiveRegion:
return return
raise self.__exc_type() raise self.__exc_type()
async def exit(self) -> None: def exit(self) -> None:
self.__busy = False self.__busy = False
if self.__notifier: if self.__notifier:
self.__notifier.notify() self.__notifier.notify()
async def __aenter__(self) -> None: def __enter__(self) -> None:
await self.enter() self.enter()
async def __aexit__( def __exit__(
self, self,
_exc_type: type[BaseException], _exc_type: type[BaseException],
_exc: BaseException, _exc: BaseException,
_tb: types.TracebackType, _tb: types.TracebackType,
) -> None: ) -> None:
await self.exit() self.exit()
async def run_region_task( async def run_region_task(
@ -338,7 +356,7 @@ async def run_region_task(
async def wrapper() -> None: async def wrapper() -> None:
try: try:
async with region: with region:
entered.set_result(None) entered.set_result(None)
await func(*args, **kwargs) await func(*args, **kwargs)
except region.get_exc_type(): except region.get_exc_type():

View File

@ -31,12 +31,8 @@ import pygments
import pygments.lexers.data import pygments.lexers.data
import pygments.formatters import pygments.formatters
from gettext import translation
from .. import tools from .. import tools
from ..mouse import MouseRange
from ..plugins import UnknownPluginError from ..plugins import UnknownPluginError
from ..plugins.auth import get_auth_service_class from ..plugins.auth import get_auth_service_class
from ..plugins.hid import get_hid_class from ..plugins.hid import get_hid_class
@ -69,6 +65,7 @@ from ..validators.basic import valid_string_list
from ..validators.auth import valid_user from ..validators.auth import valid_user
from ..validators.auth import valid_users_list from ..validators.auth import valid_users_list
from ..validators.auth import valid_expire
from ..validators.os import valid_abs_path from ..validators.os import valid_abs_path
from ..validators.os import valid_abs_file from ..validators.os import valid_abs_file
@ -77,12 +74,14 @@ from ..validators.os import valid_unix_mode
from ..validators.os import valid_options from ..validators.os import valid_options
from ..validators.os import valid_command from ..validators.os import valid_command
from ..validators.net import valid_ip
from ..validators.net import valid_ip_or_host from ..validators.net import valid_ip_or_host
from ..validators.net import valid_net from ..validators.net import valid_net
from ..validators.net import valid_port from ..validators.net import valid_port
from ..validators.net import valid_ports_list from ..validators.net import valid_ports_list
from ..validators.net import valid_mac from ..validators.net import valid_mac
from ..validators.net import valid_ssl_ciphers from ..validators.net import valid_ssl_ciphers
from ..validators.net import valid_ice_servers
from ..validators.hid import valid_hid_key from ..validators.hid import valid_hid_key
from ..validators.hid import valid_hid_mouse_output from ..validators.hid import valid_hid_mouse_output
@ -105,9 +104,6 @@ from ..validators.hw import valid_otg_gadget
from ..validators.hw import valid_otg_id from ..validators.hw import valid_otg_id
from ..validators.hw import valid_otg_ethernet from ..validators.hw import valid_otg_ethernet
from ..validators.languages import valid_languages
from ..languages import Languages
# ===== # =====
def init( def init(
@ -129,7 +125,6 @@ def init(
add_help=add_help, add_help=add_help,
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file, parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file,
help="Set config file path", metavar="<file>") help="Set config file path", metavar="<file>")
parser.add_argument("-o", "--set-options", default=[], nargs="+", parser.add_argument("-o", "--set-options", default=[], nargs="+",
@ -153,18 +148,9 @@ def init(
)) ))
raise SystemExit() raise SystemExit()
config = _init_config(options.config, options.set_options, **load) config = _init_config(options.config, options.set_options, **load)
logging.captureWarnings(True) logging.captureWarnings(True)
logging.config.dictConfig(config.logging) logging.config.dictConfig(config.logging)
if isinstance(config.get("languages"), dict) and isinstance(config["languages"].get("console"), str):
i18n_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+"/i18n"
Languages.init("message", i18n_path, config["languages"]["console"])
gettext = Languages().gettext
logging.addLevelName(20, gettext("INFO"))
logging.addLevelName(30, gettext("WARNING"))
logging.addLevelName(40, gettext("ERROR"))
if cli_logging: if cli_logging:
logging.getLogger().handlers[0].setFormatter(logging.Formatter( logging.getLogger().handlers[0].setFormatter(logging.Formatter(
"-- {levelname:>7} -- {message}", "-- {levelname:>7} -- {message}",
@ -173,7 +159,10 @@ def init(
if check_run and not options.run: if check_run and not options.run:
raise SystemExit( raise SystemExit(
gettext("To prevent accidental startup, you must specify the --run option to start.\n")+gettext("Try the --help option to find out what this service does.\n")+gettext("Make sure you understand exactly what you are doing!")) "To prevent accidental startup, you must specify the --run option to start.\n"
"Try the --help option to find out what this service does.\n"
"Make sure you understand exactly what you are doing!"
)
return (parser, remaining, config) return (parser, remaining, config)
@ -183,8 +172,8 @@ def _init_config(config_path: str, override_options: list[str], **load_flags: bo
config_path = os.path.expanduser(config_path) config_path = os.path.expanduser(config_path)
try: try:
raw_config: dict = load_yaml_file(config_path) raw_config: dict = load_yaml_file(config_path)
except Exception as err: except Exception as ex:
raise SystemExit(f"ConfigError: Can't read config file {config_path!r}:\n{tools.efmt(err)}") raise SystemExit(f"ConfigError: Can't read config file {config_path!r}:\n{tools.efmt(ex)}")
if not isinstance(raw_config, dict): if not isinstance(raw_config, dict):
raise SystemExit(f"ConfigError: Top-level of the file {config_path!r} must be a dictionary") raise SystemExit(f"ConfigError: Top-level of the file {config_path!r} must be a dictionary")
@ -199,11 +188,19 @@ def _init_config(config_path: str, override_options: list[str], **load_flags: bo
config = make_config(raw_config, scheme) config = make_config(raw_config, scheme)
return config return config
except (ConfigError, UnknownPluginError) as err: except (ConfigError, UnknownPluginError) as ex:
raise SystemExit(f"ConfigError: {err}") raise SystemExit(f"ConfigError: {ex}")
def _patch_raw(raw_config: dict) -> None: # pylint: disable=too-many-branches def _patch_raw(raw_config: dict) -> None: # pylint: disable=too-many-branches
for (sub, cmd) in [("iface", "ip_cmd"), ("firewall", "iptables_cmd")]:
if isinstance(raw_config.get("otgnet"), dict):
if isinstance(raw_config["otgnet"].get(sub), dict):
if raw_config["otgnet"][sub].get(cmd):
raw_config["otgnet"].setdefault("commands", {})
raw_config["otgnet"]["commands"][cmd] = raw_config["otgnet"][sub][cmd]
del raw_config["otgnet"][sub][cmd]
if isinstance(raw_config.get("otg"), dict): if isinstance(raw_config.get("otg"), dict):
for (old, new) in [ for (old, new) in [
("msd", "msd"), ("msd", "msd"),
@ -371,6 +368,12 @@ def _get_config_scheme() -> dict:
"auth": { "auth": {
"enabled": Option(True, type=valid_bool), "enabled": Option(True, type=valid_bool),
"expire": Option(0, type=valid_expire),
"usc": {
"users": Option([], type=valid_users_list), # PiKVM username has a same regex as a UNIX username
"groups": Option(["kvmd-selfauth"], type=valid_users_list), # groupname has a same regex as a username
},
"internal": { "internal": {
"type": Option("htpasswd"), "type": Option("htpasswd"),
@ -419,19 +422,7 @@ def _get_config_scheme() -> dict:
"hid": { "hid": {
"type": Option("", type=valid_stripped_string_not_empty), "type": Option("", type=valid_stripped_string_not_empty),
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file), "keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
"ignore_keys": Option([], type=functools.partial(valid_string_list, subval=valid_hid_key)),
"mouse_x_range": {
"min": Option(MouseRange.MIN, type=valid_hid_mouse_move),
"max": Option(MouseRange.MAX, type=valid_hid_mouse_move),
},
"mouse_y_range": {
"min": Option(MouseRange.MIN, type=valid_hid_mouse_move),
"max": Option(MouseRange.MAX, type=valid_hid_mouse_move),
},
# Dynamic content # Dynamic content
}, },
@ -483,7 +474,7 @@ def _get_config_scheme() -> dict:
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"), "unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(2.0, type=valid_float_f01), "timeout": Option(2.0, type=valid_float_f01),
"snapshot_timeout": Option(1.0, type=valid_float_f01), # error_delay * 3 + 1 "snapshot_timeout": Option(5.0, type=valid_float_f01), # error_delay * 3 + 1
"process_name_prefix": Option("kvmd/streamer"), "process_name_prefix": Option("kvmd/streamer"),
@ -528,6 +519,38 @@ def _get_config_scheme() -> dict:
"table": Option([], type=valid_ugpio_view_table), "table": Option([], type=valid_ugpio_view_table),
}, },
}, },
"switch": {
"device": Option("/dev/kvmd-switch", type=valid_abs_path, unpack_as="device_path"),
"default_edid": Option("/etc/kvmd/switch-edid.hex", type=valid_abs_path, unpack_as="default_edid_path"),
"ignore_hpd_on_top": Option(False, type=valid_bool),
},
},
"media": {
"server": {
"unix": Option("/run/kvmd/media.sock", type=valid_abs_path, unpack_as="unix_path"),
"unix_rm": Option(True, type=valid_bool),
"unix_mode": Option(0o660, type=valid_unix_mode),
"heartbeat": Option(15.0, type=valid_float_f01),
"access_log_format": Option("[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---"
" referer='%{Referer}i'; user_agent='%{User-Agent}i'"),
},
"memsink": {
"jpeg": {
"sink": Option("", unpack_as="obj"),
"lock_timeout": Option(1.0, type=valid_float_f01),
"wait_timeout": Option(1.0, type=valid_float_f01),
"drop_same_frames": Option(0.0, type=valid_float_f0),
},
"h264": {
"sink": Option("", unpack_as="obj"),
"lock_timeout": Option(1.0, type=valid_float_f01),
"wait_timeout": Option(1.0, type=valid_float_f01),
"drop_same_frames": Option(0.0, type=valid_float_f0),
},
},
}, },
"pst": { "pst": {
@ -553,16 +576,17 @@ def _get_config_scheme() -> dict:
"vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation "vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation
"product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget "product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget
"manufacturer": Option("PiKVM", type=valid_stripped_string), "manufacturer": Option("PiKVM", type=valid_stripped_string),
"product": Option("Composite KVM Device", type=valid_stripped_string), "product": Option("PiKVM Composite Device", type=valid_stripped_string),
"serial": Option("CAFEBABE", type=valid_stripped_string, if_none=None), "serial": Option("CAFEBABE", type=valid_stripped_string, if_none=None),
"config": Option("", type=valid_stripped_string),
"device_version": Option(-1, type=functools.partial(valid_number, min=-1, max=0xFFFF)), "device_version": Option(-1, type=functools.partial(valid_number, min=-1, max=0xFFFF)),
"usb_version": Option(0x0200, type=valid_otg_id), "usb_version": Option(0x0200, type=valid_otg_id),
"max_power": Option(250, type=functools.partial(valid_number, min=50, max=500)), "max_power": Option(250, type=functools.partial(valid_number, min=50, max=500)),
"remote_wakeup": Option(False, type=valid_bool), "remote_wakeup": Option(True, type=valid_bool),
"gadget": Option("kvmd", type=valid_otg_gadget), "gadget": Option("kvmd", type=valid_otg_gadget),
"config": Option("PiKVM device", type=valid_stripped_string_not_empty),
"udc": Option("", type=valid_stripped_string), "udc": Option("", type=valid_stripped_string),
"endpoints": Option(9, type=valid_int_f0),
"init_delay": Option(3.0, type=valid_float_f01), "init_delay": Option(3.0, type=valid_float_f01),
"user": Option("kvmd", type=valid_user), "user": Option("kvmd", type=valid_user),
@ -576,6 +600,9 @@ def _get_config_scheme() -> dict:
"mouse": { "mouse": {
"start": Option(True, type=valid_bool), "start": Option(True, type=valid_bool),
}, },
"mouse_alt": {
"start": Option(True, type=valid_bool),
},
}, },
"msd": { "msd": {
@ -586,6 +613,18 @@ def _get_config_scheme() -> dict:
"rw": Option(False, type=valid_bool), "rw": Option(False, type=valid_bool),
"removable": Option(True, type=valid_bool), "removable": Option(True, type=valid_bool),
"fua": Option(True, type=valid_bool), "fua": Option(True, type=valid_bool),
"inquiry_string": {
"cdrom": {
"vendor": Option("PiKVM", type=valid_stripped_string),
"product": Option("Optical Drive", type=valid_stripped_string),
"revision": Option("1.00", type=valid_stripped_string),
},
"flash": {
"vendor": Option("PiKVM", type=valid_stripped_string),
"product": Option("Flash Drive", type=valid_stripped_string),
"revision": Option("1.00", type=valid_stripped_string),
},
},
}, },
}, },
@ -602,6 +641,11 @@ def _get_config_scheme() -> dict:
"kvm_mac": Option("", type=valid_mac, if_empty=""), "kvm_mac": Option("", type=valid_mac, if_empty=""),
}, },
"audio": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
},
"drives": { "drives": {
"enabled": Option(False, type=valid_bool), "enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool), "start": Option(True, type=valid_bool),
@ -612,6 +656,18 @@ def _get_config_scheme() -> dict:
"rw": Option(True, type=valid_bool), "rw": Option(True, type=valid_bool),
"removable": Option(True, type=valid_bool), "removable": Option(True, type=valid_bool),
"fua": Option(True, type=valid_bool), "fua": Option(True, type=valid_bool),
"inquiry_string": {
"cdrom": {
"vendor": Option("PiKVM", type=valid_stripped_string),
"product": Option("Optical Drive", type=valid_stripped_string),
"revision": Option("1.00", type=valid_stripped_string),
},
"flash": {
"vendor": Option("PiKVM", type=valid_stripped_string),
"product": Option("Flash Drive", type=valid_stripped_string),
"revision": Option("1.00", type=valid_stripped_string),
},
},
}, },
}, },
}, },
@ -620,7 +676,6 @@ def _get_config_scheme() -> dict:
"otgnet": { "otgnet": {
"iface": { "iface": {
"net": Option("172.30.30.0/24", type=functools.partial(valid_net, v6=False)), "net": Option("172.30.30.0/24", type=functools.partial(valid_net, v6=False)),
"ip_cmd": Option(["/usr/bin/ip"], type=valid_command),
}, },
"firewall": { "firewall": {
@ -628,10 +683,13 @@ def _get_config_scheme() -> dict:
"allow_tcp": Option([], type=valid_ports_list), "allow_tcp": Option([], type=valid_ports_list),
"allow_udp": Option([67], type=valid_ports_list), "allow_udp": Option([67], type=valid_ports_list),
"forward_iface": Option("", type=valid_stripped_string), "forward_iface": Option("", type=valid_stripped_string),
"iptables_cmd": Option(["/usr/sbin/iptables", "--wait=5"], type=valid_command),
}, },
"commands": { "commands": {
"ip_cmd": Option(["/usr/bin/ip"], type=valid_command),
"iptables_cmd": Option(["/usr/sbin/iptables", "--wait=5"], type=valid_command),
"sysctl_cmd": Option(["/usr/sbin/sysctl"], type=valid_command),
"pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command), "pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command),
"pre_start_cmd_remove": Option([], type=valid_options), "pre_start_cmd_remove": Option([], type=valid_options),
"pre_start_cmd_append": Option([], type=valid_options), "pre_start_cmd_append": Option([], type=valid_options),
@ -696,6 +754,7 @@ def _get_config_scheme() -> dict:
"desired_fps": Option(30, type=valid_stream_fps), "desired_fps": Option(30, type=valid_stream_fps),
"mouse_output": Option("usb", type=valid_hid_mouse_output), "mouse_output": Option("usb", type=valid_hid_mouse_output),
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file), "keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
"scroll_rate": Option(4, type=functools.partial(valid_number, min=1, max=30)),
"server": { "server": {
"host": Option("", type=valid_ip_or_host, if_empty=""), "host": Option("", type=valid_ip_or_host, if_empty=""),
@ -747,8 +806,8 @@ def _get_config_scheme() -> dict:
"auth": { "auth": {
"vncauth": { "vncauth": {
"enabled": Option(False, type=valid_bool), "enabled": Option(False, type=valid_bool, unpack_as="vncpass_enabled"),
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="path"), "file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="vncpass_path"),
}, },
"vencrypt": { "vencrypt": {
"enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"), "enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"),
@ -756,12 +815,23 @@ def _get_config_scheme() -> dict:
}, },
}, },
"localhid": {
"kvmd": {
"unix": Option("/run/kvmd/kvmd.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
},
"nginx": { "nginx": {
"http": { "http": {
"ipv4": Option("0.0.0.0", type=functools.partial(valid_ip, v6=False)),
"ipv6": Option("::", type=functools.partial(valid_ip, v4=False)),
"port": Option(80, type=valid_port), "port": Option(80, type=valid_port),
}, },
"https": { "https": {
"enabled": Option(True, type=valid_bool), "enabled": Option(True, type=valid_bool),
"ipv4": Option("0.0.0.0", type=functools.partial(valid_ip, v6=False)),
"ipv6": Option("::", type=functools.partial(valid_ip, v4=False)),
"port": Option(443, type=valid_port), "port": Option(443, type=valid_port),
}, },
}, },
@ -791,6 +861,7 @@ def _get_config_scheme() -> dict:
], type=valid_command), ], type=valid_command),
"cmd_remove": Option([], type=valid_options), "cmd_remove": Option([], type=valid_options),
"cmd_append": Option([], type=valid_options), "cmd_append": Option([], type=valid_options),
"local_ice_servers": Option([], type=valid_ice_servers, unpack_as="ice_servers"),
}, },
"watchdog": { "watchdog": {
@ -798,9 +869,4 @@ def _get_config_scheme() -> dict:
"timeout": Option(300, type=valid_int_f1), "timeout": Option(300, type=valid_int_f1),
"interval": Option(30, type=valid_int_f1), "interval": Option(30, type=valid_int_f1),
}, },
"languages": {
"console": Option("default", type=valid_languages),
"web": Option("default", type=valid_languages),
},
} }

View File

@ -22,259 +22,22 @@
import sys import sys
import os import os
import re
import dataclasses
import contextlib
import subprocess import subprocess
import argparse import argparse
import time import time
from typing import IO
from typing import Generator
from typing import Callable from typing import Callable
from ...validators.basic import valid_bool from ...validators.basic import valid_bool
from ...validators.basic import valid_int_f0 from ...validators.basic import valid_int_f0
from ...edid import EdidNoBlockError
from ...edid import Edid
# from .. import init # from .. import init
# ===== # =====
class NoBlockError(Exception):
pass
@contextlib.contextmanager
def _smart_open(path: str, mode: str) -> Generator[IO, None, None]:
fd = (0 if "r" in mode else 1)
with (os.fdopen(fd, mode, closefd=False) if path == "-" else open(path, mode)) as file:
yield file
if "w" in mode:
file.flush()
@dataclasses.dataclass(frozen=True)
class _CeaBlock:
tag: int
data: bytes
def __post_init__(self) -> None:
assert 0 < self.tag <= 0b111
assert 0 < len(self.data) <= 0b11111
@property
def size(self) -> int:
return len(self.data) + 1
def pack(self) -> bytes:
header = (self.tag << 5) | len(self.data)
return header.to_bytes() + self.data
@classmethod
def first_from_raw(cls, raw: (bytes | list[int])) -> "_CeaBlock":
assert 0 < raw[0] <= 0xFF
tag = (raw[0] & 0b11100000) >> 5
data_size = (raw[0] & 0b00011111)
data = bytes(raw[1:data_size + 1])
return _CeaBlock(tag, data)
_CEA = 128
_CEA_AUDIO = 1
_CEA_SPEAKERS = 4
class _Edid:
# https://en.wikipedia.org/wiki/Extended_Display_Identification_Data
def __init__(self, path: str) -> None:
with _smart_open(path, "rb") as file:
data = file.read()
if data.startswith(b"\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00"):
self.__data = list(data)
else:
text = re.sub(r"\s", "", data.decode())
self.__data = [
int(text[index:index + 2], 16)
for index in range(0, len(text), 2)
]
assert len(self.__data) == 256, f"Invalid EDID length: {len(self.__data)}, should be 256 bytes"
assert self.__data[126] == 1, "Zero extensions number"
assert (self.__data[_CEA + 0], self.__data[_CEA + 1]) == (0x02, 0x03), "Can't find CEA extension"
def write_hex(self, path: str) -> None:
self.__update_checksums()
text = "\n".join(
"".join(
f"{item:0{2}X}"
for item in self.__data[index:index + 16]
)
for index in range(0, len(self.__data), 16)
) + "\n"
with _smart_open(path, "w") as file:
file.write(text)
def write_bin(self, path: str) -> None:
self.__update_checksums()
with _smart_open(path, "wb") as file:
file.write(bytes(self.__data))
def __update_checksums(self) -> None:
self.__data[127] = 256 - (sum(self.__data[:127]) % 256)
self.__data[255] = 256 - (sum(self.__data[128:255]) % 256)
# =====
def get_mfc_id(self) -> str:
raw = self.__data[8] << 8 | self.__data[9]
return bytes([
((raw >> 10) & 0b11111) + 0x40,
((raw >> 5) & 0b11111) + 0x40,
(raw & 0b11111) + 0x40,
]).decode("ascii")
def set_mfc_id(self, mfc_id: str) -> None:
assert len(mfc_id) == 3, "Mfc ID must be 3 characters long"
data = mfc_id.upper().encode("ascii")
for ch in data:
assert 0x41 <= ch <= 0x5A, "Mfc ID must contain only A-Z characters"
raw = (
(data[2] - 0x40)
| ((data[1] - 0x40) << 5)
| ((data[0] - 0x40) << 10)
)
self.__data[8] = (raw >> 8) & 0xFF
self.__data[9] = raw & 0xFF
# =====
def get_product_id(self) -> int:
return (self.__data[10] | self.__data[11] << 8)
def set_product_id(self, product_id: int) -> None:
assert 0 <= product_id <= 0xFFFF, f"Product ID should be from 0 to {0xFFFF}"
self.__data[10] = product_id & 0xFF
self.__data[11] = (product_id >> 8) & 0xFF
# =====
def get_serial(self) -> int:
return (
self.__data[12]
| self.__data[13] << 8
| self.__data[14] << 16
| self.__data[15] << 24
)
def set_serial(self, serial: int) -> None:
assert 0 <= serial <= 0xFFFFFFFF, f"Serial should be from 0 to {0xFFFFFFFF}"
self.__data[12] = serial & 0xFF
self.__data[13] = (serial >> 8) & 0xFF
self.__data[14] = (serial >> 16) & 0xFF
self.__data[15] = (serial >> 24) & 0xFF
# =====
def get_monitor_name(self) -> str:
return self.__get_dtd_text(0xFC, "Monitor Name")
def set_monitor_name(self, text: str) -> None:
self.__set_dtd_text(0xFC, "Monitor Name", text)
def get_monitor_serial(self) -> str:
return self.__get_dtd_text(0xFF, "Monitor Serial")
def set_monitor_serial(self, text: str) -> None:
self.__set_dtd_text(0xFF, "Monitor Serial", text)
def __get_dtd_text(self, d_type: int, name: str) -> str:
index = self.__find_dtd_text(d_type, name)
return bytes(self.__data[index:index + 13]).decode("cp437").strip()
def __set_dtd_text(self, d_type: int, name: str, text: str) -> None:
index = self.__find_dtd_text(d_type, name)
encoded = (text[:13] + "\n" + " " * 12)[:13].encode("cp437")
for (offset, ch) in enumerate(encoded):
self.__data[index + offset] = ch
def __find_dtd_text(self, d_type: int, name: str) -> int:
for index in [54, 72, 90, 108]:
if self.__data[index + 3] == d_type:
return index + 5
raise NoBlockError(f"Can't find DTD {name}")
# ===== CEA =====
def get_audio(self) -> bool:
(cbs, _) = self.__parse_cea()
audio = False
speakers = False
for cb in cbs:
if cb.tag == _CEA_AUDIO:
audio = True
elif cb.tag == _CEA_SPEAKERS:
speakers = True
return (audio and speakers and self.__get_basic_audio())
def set_audio(self, enabled: bool) -> None:
(cbs, dtds) = self.__parse_cea()
cbs = [cb for cb in cbs if cb.tag not in [_CEA_AUDIO, _CEA_SPEAKERS]]
if enabled:
cbs.append(_CeaBlock(_CEA_AUDIO, b"\x09\x7f\x07"))
cbs.append(_CeaBlock(_CEA_SPEAKERS, b"\x01\x00\x00"))
self.__replace_cea(cbs, dtds)
self.__set_basic_audio(enabled)
def __get_basic_audio(self) -> bool:
return bool(self.__data[_CEA + 3] & 0b01000000)
def __set_basic_audio(self, enabled: bool) -> None:
if enabled:
self.__data[_CEA + 3] |= 0b01000000
else:
self.__data[_CEA + 3] &= (0xFF - 0b01000000) # ~X
def __parse_cea(self) -> tuple[list[_CeaBlock], bytes]:
cea = self.__data[_CEA:]
dtd_begin = cea[2]
if dtd_begin == 0:
return ([], b"")
cbs: list[_CeaBlock] = []
if dtd_begin > 4:
raw = cea[4:dtd_begin]
while len(raw) != 0:
cb = _CeaBlock.first_from_raw(raw)
cbs.append(cb)
raw = raw[cb.size:]
dtds = b""
assert dtd_begin >= 4
raw = cea[dtd_begin:]
while len(raw) > (18 + 1) and raw[0] != 0:
dtds += bytes(raw[:18])
raw = raw[18:]
return (cbs, dtds)
def __replace_cea(self, cbs: list[_CeaBlock], dtds: bytes) -> None:
cbs_packed = b""
for cb in cbs:
cbs_packed += cb.pack()
raw = cbs_packed + dtds
assert len(raw) <= (128 - 4 - 1), "Too many CEA blocks or DTDs"
self.__data[_CEA + 2] = (0 if len(raw) == 0 else (len(cbs_packed) + 4))
for index in range(4, 127):
try:
ch = raw[index - 4]
except IndexError:
ch = 0
self.__data[_CEA + index] = ch
def _format_bool(value: bool) -> str: def _format_bool(value: bool) -> str:
return ("yes" if value else "no") return ("yes" if value else "no")
@ -283,7 +46,7 @@ def _make_format_hex(size: int) -> Callable[[int], str]:
return (lambda value: ("0x{:0%dX} ({})" % (size * 2)).format(value, value)) return (lambda value: ("0x{:0%dX} ({})" % (size * 2)).format(value, value))
def _print_edid(edid: _Edid) -> None: def _print_edid(edid: Edid) -> None:
for (key, get, fmt) in [ for (key, get, fmt) in [
("Manufacturer ID:", edid.get_mfc_id, str), ("Manufacturer ID:", edid.get_mfc_id, str),
("Product ID: ", edid.get_product_id, _make_format_hex(2)), ("Product ID: ", edid.get_product_id, _make_format_hex(2)),
@ -294,10 +57,37 @@ def _print_edid(edid: _Edid) -> None:
]: ]:
try: try:
print(key, fmt(get()), file=sys.stderr) # type: ignore print(key, fmt(get()), file=sys.stderr) # type: ignore
except NoBlockError: except EdidNoBlockError:
pass pass
def _find_out2_edid_path() -> str:
card = os.path.basename(os.readlink("/dev/dri/by-path/platform-gpu-card"))
path = f"/sys/devices/platform/gpu/drm/{card}/{card}-HDMI-A-2"
with open(os.path.join(path, "status")) as file:
if file.read().startswith("d"):
raise SystemExit("No display found")
return os.path.join(path, "edid")
def _adopt_out2_ids(dest: Edid) -> None:
src = Edid.from_file(_find_out2_edid_path())
dest.set_monitor_name(src.get_monitor_name())
try:
dest.get_monitor_serial()
except EdidNoBlockError:
pass
else:
try:
ser = src.get_monitor_serial()
except EdidNoBlockError:
ser = "{:08X}".format(src.get_serial())
dest.set_monitor_serial(ser)
dest.set_mfc_id(src.get_mfc_id())
dest.set_product_id(src.get_product_id())
dest.set_serial(src.get_serial())
# ===== # =====
def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-branches,too-many-statements def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-branches,too-many-statements
# (parent_parser, argv, _) = init( # (parent_parser, argv, _) = init(
@ -326,6 +116,10 @@ def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-bra
help="Import the specified bin/hex EDID to the [--edid] file as a hex text", metavar="<file>") help="Import the specified bin/hex EDID to the [--edid] file as a hex text", metavar="<file>")
parser.add_argument("--import-preset", choices=presets, parser.add_argument("--import-preset", choices=presets,
help="Restore default EDID or choose the preset", metavar=f"{{ {' | '.join(presets)} }}",) help="Restore default EDID or choose the preset", metavar=f"{{ {' | '.join(presets)} }}",)
parser.add_argument("--import-display-ids", action="store_true",
help="On PiKVM V4, import and adopt IDs from a physical display connected to the OUT2 port")
parser.add_argument("--import-display", action="store_true",
help="On PiKVM V4, import full EDID from a physical display connected to the OUT2 port")
parser.add_argument("--set-audio", type=valid_bool, parser.add_argument("--set-audio", type=valid_bool,
help="Enable or disable audio", metavar="<yes|no>") help="Enable or disable audio", metavar="<yes|no>")
parser.add_argument("--set-mfc-id", parser.add_argument("--set-mfc-id",
@ -348,30 +142,37 @@ def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-bra
help="Presets directory", metavar="<dir>") help="Presets directory", metavar="<dir>")
options = parser.parse_args(argv[1:]) options = parser.parse_args(argv[1:])
base: (_Edid | None) = None base: (Edid | None) = None
if options.import_preset: if options.import_preset:
imp = options.import_preset imp = options.import_preset
if "." in imp: if "." in imp:
(base_name, imp) = imp.split(".", 1) # v3.1080p-by-default (base_name, imp) = imp.split(".", 1) # v3.1080p-by-default
base = _Edid(os.path.join(options.presets_path, f"{base_name}.hex")) base = Edid.from_file(os.path.join(options.presets_path, f"{base_name}.hex"))
imp = f"_{imp}" imp = f"_{imp}"
options.imp = os.path.join(options.presets_path, f"{imp}.hex") options.imp = os.path.join(options.presets_path, f"{imp}.hex")
if options.import_display:
options.imp = _find_out2_edid_path()
orig_edid_path = options.edid_path orig_edid_path = options.edid_path
if options.imp: if options.imp:
options.export_hex = options.edid_path options.export_hex = options.edid_path
options.edid_path = options.imp options.edid_path = options.imp
edid = _Edid(options.edid_path) edid = Edid.from_file(options.edid_path)
changed = False changed = False
for cmd in dir(_Edid): if options.import_display_ids:
_adopt_out2_ids(edid)
changed = True
for cmd in dir(Edid):
if cmd.startswith("set_"): if cmd.startswith("set_"):
value = getattr(options, cmd) value = getattr(options, cmd)
if value is None and base is not None: if value is None and base is not None:
try: try:
value = getattr(base, cmd.replace("set_", "get_"))() value = getattr(base, cmd.replace("set_", "get_"))()
except NoBlockError: except EdidNoBlockError:
pass pass
if value is not None: if value is not None:
getattr(edid, cmd)(value) getattr(edid, cmd)(value)
@ -400,8 +201,7 @@ def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-bra
"/usr/bin/v4l2-ctl", "/usr/bin/v4l2-ctl",
f"--device={options.device_path}", f"--device={options.device_path}",
f"--set-edid=file={orig_edid_path}", f"--set-edid=file={orig_edid_path}",
"--fix-edid-checksums",
"--info-edid", "--info-edid",
], stdout=sys.stderr, check=True) ], stdout=sys.stderr, check=True)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as ex:
raise SystemExit(str(err)) raise SystemExit(str(ex))

Some files were not shown because too many files have changed in this diff Show More