mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-14 02:00:32 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a6e7edcd | ||
|
|
50c3e6a32a | ||
|
|
c8305cc65d | ||
|
|
aae4e936db | ||
|
|
45a04f7570 | ||
|
|
53ba69f4aa | ||
|
|
53229a9055 | ||
|
|
f97df0d830 | ||
|
|
8ed5e4abc3 | ||
|
|
1e727ddc1b | ||
|
|
da84a6d09f | ||
|
|
9c35c68eda | ||
|
|
651f9a4f4e | ||
|
|
7777f5e490 |
25
.github/workflows/build_img.yaml
vendored
25
.github/workflows/build_img.yaml
vendored
@ -50,6 +50,29 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Set build environment
|
||||||
id: build_env
|
id: build_env
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -184,4 +207,4 @@ jobs:
|
|||||||
if [ "${{ github.event.inputs.create_release }}" = "true" ]; then
|
if [ "${{ github.event.inputs.create_release }}" = "true" ]; then
|
||||||
echo "| **Release** | [${{ env.RELEASE_TAG }}](${{ steps.release.outputs.url }}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| **Release** | [${{ env.RELEASE_TAG }}](${{ steps.release.outputs.url }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
48
.github/workflows/docker-build.yaml
vendored
48
.github/workflows/docker-build.yaml
vendored
@ -47,6 +47,29 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
@ -117,6 +140,29 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
@ -191,4 +237,4 @@ jobs:
|
|||||||
echo "- **Platforms**: ${{ github.event.inputs.platforms }}" >> $GITHUB_STEP_SUMMARY
|
echo "- **Platforms**: ${{ github.event.inputs.platforms }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **Aliyun Enabled**: ${{ github.event.inputs.enable_aliyun }}" >> $GITHUB_STEP_SUMMARY
|
echo "- **Aliyun Enabled**: ${{ github.event.inputs.enable_aliyun }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
|
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' >> $GITHUB_STEP_SUMMARY
|
echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
59
README.md
59
README.md
@ -11,8 +11,8 @@
|
|||||||
[](https://github.com/mofeng-git/One-KVM/blob/master/LICENSE)
|
[](https://github.com/mofeng-git/One-KVM/blob/master/LICENSE)
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://one-kvm.mofeng.run">📖 详细文档</a> •
|
<a href="https://docs.one-kvm.cn">📖 详细文档</a> •
|
||||||
<a href="https://kvmd-demo.mofeng.run">🚀 在线演示</a> •
|
<a href="https://demo.one-kvm.cn/">🚀 在线演示</a> •
|
||||||
<a href="#快速开始">⚡ 快速开始</a> •
|
<a href="#快速开始">⚡ 快速开始</a> •
|
||||||
<a href="#功能介绍">📊 功能介绍</a>
|
<a href="#功能介绍">📊 功能介绍</a>
|
||||||
</p>
|
</p>
|
||||||
@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
**One-KVM** 是基于开源 [PiKVM](https://github.com/pikvm/pikvm) 项目进行二次开发的 DIY IP-KVM 解决方案。该方案利用成本较低的硬件设备,实现 BIOS 级别的远程服务器或工作站管理功能。
|
**One-KVM** 是基于开源 [PiKVM](https://github.com/pikvm/pikvm) 项目进行二次开发的 DIY IP-KVM 解决方案。该方案利用成本较低的硬件设备,实现 BIOS 级别的远程服务器或工作站管理功能。
|
||||||
|
|
||||||
|
> 本项目目前并无适配树莓派的计划。这是因为树莓派平台本质上属于 PiKVM 官方硬件生态和盈利的一部分。我们非常尊重和感谢上游项目 PiKVM ,因此 One-KVM 的设备适配主要聚焦于补充性场景,尽量避免与 PiKVM 官方产品产生重叠,以支持其可持续发展。
|
||||||
|
|
||||||
### 应用场景
|
### 应用场景
|
||||||
|
|
||||||
- **家庭实验室主机管理** - 远程管理服务器和开发设备
|
- **家庭实验室主机管理** - 远程管理服务器和开发设备
|
||||||
@ -68,7 +70,7 @@
|
|||||||
|:--------:|:-------:|:-----:|:------:|:------:|
|
|:--------:|:-------:|:-----:|:------:|:------:|
|
||||||
| 简体中文 WebUI | ✅ | ❌ | ✅ | ✅ |
|
| 简体中文 WebUI | ✅ | ❌ | ✅ | ✅ |
|
||||||
| 远程视频流 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 |
|
| 远程视频流 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 |
|
||||||
| H.264 视频编码 | CPU | GPU | 未知 | GPU |
|
| H.264 视频编码 | CPU/GPU | GPU | 未知 | GPU |
|
||||||
| 远程音频流 | ✅ | ✅ | ✅ | ✅ |
|
| 远程音频流 | ✅ | ✅ | ✅ | ✅ |
|
||||||
| 远程鼠键控制 | OTG/CH9329 | OTG/CH9329/Pico/Bluetooth | OTG | OTG |
|
| 远程鼠键控制 | OTG/CH9329 | OTG/CH9329/Pico/Bluetooth | OTG | OTG |
|
||||||
| VNC 控制 | ✅ | ✅ | ✅ | ✅ |
|
| VNC 控制 | ✅ | ✅ | ✅ | ✅ |
|
||||||
@ -87,7 +89,7 @@ Docker 版本支持 OTG 或 CH9329 作为虚拟 HID,兼容 amd64、arm64、arm
|
|||||||
#### 一键脚本部署
|
#### 一键脚本部署
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sSL https://one-kvm.mofeng.run/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
curl -sSL https://docs.one-kvm.cn/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 手动部署
|
#### 手动部署
|
||||||
@ -157,6 +159,7 @@ sudo docker run --name kvmd -itd \
|
|||||||
- **GitHub Releases:** [https://github.com/mofeng-git/One-KVM/releases](https://github.com/mofeng-git/One-KVM/releases)
|
- **GitHub Releases:** [https://github.com/mofeng-git/One-KVM/releases](https://github.com/mofeng-git/One-KVM/releases)
|
||||||
|
|
||||||
**其他下载方式:**
|
**其他下载方式:**
|
||||||
|
- **免登录高速下载:** [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.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)
|
- **百度网盘:** [https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj](https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj) (提取码:o9aj)
|
||||||
|
|
||||||
@ -170,19 +173,7 @@ sudo docker run --name kvmd -itd \
|
|||||||
| Virtualbox | Virtualbox-uefi | USB 采集卡、CH9329 | 241004 | ✅ |
|
| Virtualbox | Virtualbox-uefi | USB 采集卡、CH9329 | 241004 | ✅ |
|
||||||
| s905l3a 通用包 | E900v22c | USB 采集卡、OTG | 241004 | ✅ |
|
| s905l3a 通用包 | E900v22c | USB 采集卡、OTG | 241004 | ✅ |
|
||||||
| 我家云 | Chainedbox | USB 采集卡、OTG | 241004 | ✅ |
|
| 我家云 | Chainedbox | USB 采集卡、OTG | 241004 | ✅ |
|
||||||
| 龙芯久久派 | 2k0300 | USB 采集卡、CH9329 | 241025 | ✅ |
|
| 龙芯久久派 | 2k0300 | USB 采集卡、CH9329 | 241025 | ❌ |
|
||||||
|
|
||||||
## 🤝 贡献指南
|
|
||||||
|
|
||||||
欢迎各种形式的贡献!
|
|
||||||
|
|
||||||
### 如何贡献
|
|
||||||
|
|
||||||
1. **Fork 本仓库**
|
|
||||||
2. **创建功能分支:** `git checkout -b feature/AmazingFeature`
|
|
||||||
3. **提交更改:** `git commit -m 'Add some AmazingFeature'`
|
|
||||||
4. **推送到分支:** `git push origin feature/AmazingFeature`
|
|
||||||
5. **提交 Pull Request**
|
|
||||||
|
|
||||||
### 报告问题
|
### 报告问题
|
||||||
|
|
||||||
@ -280,6 +271,30 @@ sudo docker run --name kvmd -itd \
|
|||||||
|
|
||||||
- 爱发电用户_e3202
|
- 爱发电用户_e3202
|
||||||
|
|
||||||
|
- 一语念白
|
||||||
|
|
||||||
|
- 云边
|
||||||
|
|
||||||
|
- 爱发电用户_5a711
|
||||||
|
|
||||||
|
- 爱发电用户_9a706
|
||||||
|
|
||||||
|
- T0m9ir1SUKI
|
||||||
|
|
||||||
|
- 爱发电用户_56d52
|
||||||
|
|
||||||
|
- 爱发电用户_3N6F
|
||||||
|
|
||||||
|
- DUSK
|
||||||
|
|
||||||
|
- 飘零
|
||||||
|
|
||||||
|
- .
|
||||||
|
|
||||||
|
- 饭太稀
|
||||||
|
|
||||||
|
- 葱
|
||||||
|
|
||||||
- ......
|
- ......
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -296,10 +311,18 @@ sudo docker run --name kvmd -itd \
|
|||||||
**文件存储服务:**
|
**文件存储服务:**
|
||||||
- **[Huang1111公益计划](https://pan.huang1111.cn/s/mxkx3T1)** - 提供免登录下载服务
|
- **[Huang1111公益计划](https://pan.huang1111.cn/s/mxkx3T1)** - 提供免登录下载服务
|
||||||
|
|
||||||
|
**云服务商**
|
||||||
|
|
||||||
|
- **[林枫云](https://www.dkdun.cn)** - 赞助了本项目宁波大带宽服务器
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
|
||||||
|
|
||||||
## 📚 其他
|
## 📚 其他
|
||||||
|
|
||||||
### 使用的开源项目
|
### 使用的开源项目
|
||||||
|
|
||||||
本项目基于以下优秀开源项目进行二次开发:
|
本项目基于以下优秀开源项目进行二次开发:
|
||||||
|
|
||||||
- [PiKVM](https://github.com/pikvm/pikvm) - 开源的 DIY IP-KVM 解决方案
|
- [PiKVM](https://github.com/pikvm/pikvm) - 开源的 DIY IP-KVM 解决方案
|
||||||
|
|||||||
@ -113,58 +113,75 @@ RUN git clone --depth=1 https://github.com/gvalkov/python-evdev.git /tmp/python-
|
|||||||
&& python3 setup.py bdist_wheel --dist-dir /tmp/wheel/ \
|
&& python3 setup.py bdist_wheel --dist-dir /tmp/wheel/ \
|
||||||
&& rm -rf /tmp/python-evdev
|
&& rm -rf /tmp/python-evdev
|
||||||
|
|
||||||
# 编译安装 libnice、libsrtp、libwebsockets 和 janus-gateway
|
# 编译安装 libnice、libsrtp、libwebsockets 和 janus-gateway(显式 Release 与按架构优化)
|
||||||
RUN git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \
|
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 \
|
||||||
&& rm -rf /tmp/libnice \
|
&& 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 \
|
&& 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 xf 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 -j$(nproc) && make install \
|
&& make shared_library -j$(nproc) && make install \
|
||||||
&& cd /tmp \
|
&& cd /tmp \
|
||||||
&& rm -rf /tmp/libsrtp* \
|
&& rm -rf /tmp/libsrtp* \
|
||||||
&& git clone --depth=1 https://github.com/warmcat/libwebsockets /tmp/libwebsockets \
|
&& 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 \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="$CFLAGS -fPIC" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS -fPIC" .. \
|
||||||
&& make -j$(nproc) && make install \
|
&& make -j$(nproc) && make install \
|
||||||
&& cd /tmp \
|
&& cd /tmp \
|
||||||
&& rm -rf /tmp/libwebsockets \
|
&& rm -rf /tmp/libwebsockets \
|
||||||
&& git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \
|
&& 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-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins \
|
||||||
--disable-all-loggers --prefix=/usr \
|
--disable-all-loggers --prefix=/usr \
|
||||||
&& make -j$(nproc) && make install \
|
&& make -j$(nproc) && make install \
|
||||||
&& cd /tmp \
|
&& cd /tmp \
|
||||||
&& rm -rf /tmp/janus-gateway
|
&& rm -rf /tmp/janus-gateway
|
||||||
|
|
||||||
# 编译 Rockchip MPP、RGA 和 FFmpeg(仅 arm64)
|
# 编译 Rockchip MPP、RGA(仅 arm64,显式 Release 与按架构优化)
|
||||||
RUN if [ ${TARGETARCH} = arm64 ]; then \
|
RUN if [ ${TARGETARCH} = arm64 ]; then \
|
||||||
git clone --depth=1 https://github.com/rockchip-linux/mpp.git /tmp/rkmpp \
|
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 \
|
&& 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 .. \
|
&& 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 -j$(nproc) \
|
||||||
&& make install \
|
&& make install \
|
||||||
&& git clone -b jellyfin-rga --depth=1 https://github.com/nyanmisaka/rk-mirrors.git /tmp/rkrga \
|
&& git clone -b jellyfin-rga --depth=1 https://github.com/nyanmisaka/rk-mirrors.git /tmp/rkrga \
|
||||||
&& cd /tmp/ \
|
&& cd /tmp/ \
|
||||||
&& meson setup rkrga rkrga_build --prefix=/usr --libdir=lib --buildtype=release -Dcpp_args=-fpermissive -Dlibdrm=false -Dlibrga_demo=false \
|
&& 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 \
|
&& meson configure rkrga_build > /dev/null \
|
||||||
&& ninja -C rkrga_build install \
|
&& ninja -C rkrga_build install \
|
||||||
&& rm -rf /tmp/rkmpp /tmp/rkrga; \
|
&& rm -rf /tmp/rkmpp /tmp/rkrga; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 编译 ustreamer
|
# 编译 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 \
|
||||||
|
&& 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" \
|
||||||
&& if [ ${TARGETARCH} = arm64 ]; then \
|
&& if [ ${TARGETARCH} = arm64 ]; then \
|
||||||
make -j$(nproc) WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_MPP=1 WITH_DRM=1 -C /tmp/ustreamer; \
|
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_MPP=1 WITH_DRM=1 -C /tmp/ustreamer; \
|
||||||
else \
|
else \
|
||||||
make -j$(nproc) WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_DRM=1 -C /tmp/ustreamer; \
|
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_DRM=1 -C /tmp/ustreamer; \
|
||||||
fi \
|
fi \
|
||||||
&& /tmp/ustreamer/ustreamer -v \
|
&& /tmp/ustreamer/ustreamer -v \
|
||||||
&& /tmp/ustreamer/ustreamer-dump -v \
|
&& /tmp/ustreamer/ustreamer-dump -v \
|
||||||
@ -178,9 +195,8 @@ RUN mkdir /tmp/lib \
|
|||||||
/tmp/lib/ \
|
/tmp/lib/ \
|
||||||
&& find /usr/lib -name "libsrtp2.so.*" -exec cp {} /tmp/lib/ \; \
|
&& find /usr/lib -name "libsrtp2.so.*" -exec cp {} /tmp/lib/ \; \
|
||||||
&& find /usr/lib -name "libwebsockets.so.*" -exec cp {} /tmp/lib/ \; \
|
&& find /usr/lib -name "libwebsockets.so.*" -exec cp {} /tmp/lib/ \; \
|
||||||
&& if [ ${TARGETARCH} = arm64 ]; then \
|
&& [ "${TARGETARCH}" = "arm64" ] && \
|
||||||
find /usr/lib -name "libsw*.so.*" -exec cp {} /tmp/lib/ \; \
|
find /usr/lib -name "libsw*.so.*" -exec cp {} /tmp/lib/ \; && \
|
||||||
&& find /usr/lib -name "libpostproc.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 "librockchip*" -exec cp {} /tmp/lib/ \; && \
|
||||||
&& find /usr/lib -name "librga.so.*" -exec cp {} /tmp/lib/ \; ; \
|
find /usr/lib -name "librga.so.*" -exec cp {} /tmp/lib/ \; || true
|
||||||
fi
|
|
||||||
|
|||||||
@ -214,6 +214,12 @@ octopus_flanet_rootfs() {
|
|||||||
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
|
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; }
|
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
|
||||||
BOOTFS_MOUNTED=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 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; }
|
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
|
||||||
BOOTFS_MOUNTED=0
|
BOOTFS_MOUNTED=0
|
||||||
@ -314,8 +320,7 @@ config_cumebox2_files() {
|
|||||||
download_file_if_missing "$dtb_file" || echo "警告:下载 Cumebox2 DTB 失败"
|
download_file_if_missing "$dtb_file" || echo "警告:下载 Cumebox2 DTB 失败"
|
||||||
download_file_if_missing "$ssd_file" || echo "警告:下载 Cumebox2 ssd 脚本失败"
|
download_file_if_missing "$ssd_file" || echo "警告:下载 Cumebox2 ssd 脚本失败"
|
||||||
download_file_if_missing "$config_file" || echo "警告:下载 Cumebox2 配置文件失败"
|
download_file_if_missing "$config_file" || echo "警告:下载 Cumebox2 配置文件失败"
|
||||||
|
|
||||||
# 注意 DTB 路径可能需要根据实际 Armbian 版本调整
|
|
||||||
sudo cp "$dtb_file" "$ROOTFS/boot/dtb/amlogic/meson-gxl-s905x-khadas-vim.dtb" || echo "警告:复制 Cumebox2 DTB 失败"
|
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 cp "$ssd_file" "$ROOTFS/usr/bin/" || echo "警告:复制 Cumebox2 ssd 脚本失败"
|
||||||
sudo chmod +x "$ROOTFS/usr/bin/ssd" || echo "警告:设置 ssd 脚本执行权限失败"
|
sudo chmod +x "$ROOTFS/usr/bin/ssd" || echo "警告:设置 ssd 脚本执行权限失败"
|
||||||
@ -331,6 +336,9 @@ config_octopus_flanet_files() {
|
|||||||
download_file_if_missing "$config_file" || echo "警告:下载 Octopus-Planet 配置文件失败"
|
download_file_if_missing "$config_file" || echo "警告:下载 Octopus-Planet 配置文件失败"
|
||||||
|
|
||||||
sudo cp "$config_file" "$ROOTFS/etc/model_database.conf" || echo "警告:复制 model_database.conf 失败"
|
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() {
|
config_orangepi_zero_files() {
|
||||||
@ -344,13 +352,14 @@ config_orangepi_zero_files() {
|
|||||||
|
|
||||||
config_onecloud_pro_files() {
|
config_onecloud_pro_files() {
|
||||||
echo "信息:配置 Onecloud Pro 特定文件..."
|
echo "信息:配置 Onecloud Pro 特定文件..."
|
||||||
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() {
|
config_onecloud_files() {
|
||||||
echo "信息:配置 Onecloud 特定文件..."
|
echo "信息:配置 Onecloud 特定文件..."
|
||||||
|
|
||||||
# 在 ustreamer 命令中添加 DRM 设备配置
|
|
||||||
echo "信息:为 Onecloud 添加 DRM 设备支持..."
|
echo "信息:为 Onecloud 添加 DRM 设备支持..."
|
||||||
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card1\\\"\" /etc/kvmd/override.yaml"
|
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card1\\\"\" /etc/kvmd/override.yaml"
|
||||||
|
|
||||||
@ -384,25 +393,26 @@ config_oec_turbo_files() {
|
|||||||
echo "信息:配置 VPU 硬件编码支持..."
|
echo "信息:配置 VPU 硬件编码支持..."
|
||||||
run_in_chroot "sed -i 's/--h264-hwenc=disabled/--h264-hwenc=rkmpp/g' /etc/kvmd/override.yaml"
|
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'
|
run_in_chroot "cat > /etc/udev/rules.d/99-kvmd-hw-access.rules <<'EOF'
|
||||||
SUBSYSTEM=="dma_heap", KERNEL=="system-uncached", GROUP="render", MODE="0660"
|
# Generic hardware access for kvmd
|
||||||
KERNEL=="mpp_service", GROUP="render", MODE="0660"
|
# 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"
|
EOF"
|
||||||
|
|
||||||
|
|
||||||
# 配置 rc.local 自启脚本,添加设备权限设置
|
|
||||||
echo "信息:配置 rc.local 自启脚本..."
|
|
||||||
run_in_chroot "cat > /etc/rc.local << 'EOF'
|
|
||||||
#!/bin/bash
|
|
||||||
usermod -aG render,video kvmd
|
|
||||||
exit 0
|
|
||||||
EOF"
|
|
||||||
run_in_chroot "chmod +x /etc/rc.local"
|
|
||||||
|
|
||||||
# 确保 rc-local.service 被启用,以便 kvmd 服务可以依赖它
|
|
||||||
echo "信息:启用 rc-local.service 服务..."
|
|
||||||
run_in_chroot "systemctl enable rc-local.service"
|
|
||||||
|
|
||||||
# 替换 DTB 文件
|
# 替换 DTB 文件
|
||||||
replace_oec_turbo_dtb
|
replace_oec_turbo_dtb
|
||||||
|
|
||||||
|
|||||||
@ -159,7 +159,7 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$NOIPMI" == "1" ]; then
|
if [ "$NOIPMI" == "1" ]; then
|
||||||
log_info "已禁用IPMI功能"
|
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
|
||||||
@ -177,11 +177,30 @@ 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
|
||||||
log_info "已启用 OTG 功能"
|
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
|
||||||
if [ "$NOMSD" == 1 ]; then
|
if [ "$NOMSD" == 1 ]; then
|
||||||
log_info "已禁用 MSD 功能"
|
log_info "已禁用 MSD 功能"
|
||||||
else
|
else
|
||||||
@ -190,8 +209,8 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -z "$VIDEONUM" ]; then
|
if [ ! -z "$VIDEONUM" ]; then
|
||||||
if sed -i "s/\/dev\/video0/\/dev\/video$VIDEONUM/g" /etc/kvmd/override.yaml && \
|
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
|
sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
|
||||||
log_info "视频设备已设置为 /dev/video$VIDEONUM"
|
log_info "视频设备已设置为 /dev/video$VIDEONUM"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -208,6 +227,12 @@ EOF
|
|||||||
fi
|
fi
|
||||||
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 [ ! -z "$CH9329TIMEOUT" ]; then
|
||||||
if sed -i "s/read_timeout: 0.3/read_timeout: $CH9329TIMEOUT/g" /etc/kvmd/override.yaml; then
|
if sed -i "s/read_timeout: 0.3/read_timeout: $CH9329TIMEOUT/g" /etc/kvmd/override.yaml; then
|
||||||
log_info "CH9329 超时已设置为 $CH9329TIMEOUT 秒"
|
log_info "CH9329 超时已设置为 $CH9329TIMEOUT 秒"
|
||||||
@ -272,4 +297,4 @@ if [ "$OTG" == "1" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "One-KVM 配置文件准备完成,正在启动服务..."
|
log_info "One-KVM 配置文件准备完成,正在启动服务..."
|
||||||
exec supervisord -c /etc/kvmd/supervisord.conf
|
exec supervisord -c /etc/kvmd/supervisord.conf
|
||||||
|
|||||||
@ -66,9 +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-preset=ultrafast"
|
|
||||||
- "--h264-hwenc=disabled"
|
- "--h264-hwenc=disabled"
|
||||||
- "--h264-hwenc-fallback"
|
|
||||||
- "--slowdown"
|
- "--slowdown"
|
||||||
gpio:
|
gpio:
|
||||||
drivers:
|
drivers:
|
||||||
@ -159,10 +157,6 @@ media:
|
|||||||
|
|
||||||
jpeg:
|
jpeg:
|
||||||
sink: 'kvmd::ustreamer::jpeg'
|
sink: 'kvmd::ustreamer::jpeg'
|
||||||
janus:
|
|
||||||
stun:
|
|
||||||
host: stun.cloudflare.com
|
|
||||||
port: 3478
|
|
||||||
|
|
||||||
otgnet:
|
otgnet:
|
||||||
commands:
|
commands:
|
||||||
|
|||||||
@ -53,17 +53,6 @@ stdout_logfile=/dev/stdout
|
|||||||
stdout_logfile_maxbytes = 0
|
stdout_logfile_maxbytes = 0
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
[program:kvmd-gostc]
|
|
||||||
command=/usr/bin/gostc -web-addr 0.0.0.0:18080
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=5
|
|
||||||
priority=300
|
|
||||||
stopasgroup=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes = 0
|
|
||||||
redirect_stderr=true
|
|
||||||
|
|
||||||
[program:clean_when_exit]
|
[program:clean_when_exit]
|
||||||
command=/etc/kvmd/clean_when_exit.sh
|
command=/etc/kvmd/clean_when_exit.sh
|
||||||
autostart=true
|
autostart=true
|
||||||
@ -74,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
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,7 @@ 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
|
||||||
@ -860,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": {
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import asyncio
|
|||||||
import asyncio.subprocess
|
import asyncio.subprocess
|
||||||
import socket
|
import socket
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import netifaces
|
import netifaces
|
||||||
|
|
||||||
@ -43,6 +45,7 @@ class JanusRunner: # pylint: disable=too-many-instance-attributes
|
|||||||
cmd: list[str],
|
cmd: list[str],
|
||||||
cmd_remove: list[str],
|
cmd_remove: list[str],
|
||||||
cmd_append: list[str],
|
cmd_append: list[str],
|
||||||
|
ice_servers: list[dict[str, Any]],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.__stun = Stun(stun_host, stun_port, stun_timeout, stun_retries, stun_retries_delay)
|
self.__stun = Stun(stun_host, stun_port, stun_timeout, stun_retries, stun_retries_delay)
|
||||||
@ -52,6 +55,7 @@ class JanusRunner: # pylint: disable=too-many-instance-attributes
|
|||||||
self.__check_retries_delay = check_retries_delay
|
self.__check_retries_delay = check_retries_delay
|
||||||
|
|
||||||
self.__cmd = tools.build_cmd(cmd, cmd_remove, cmd_append)
|
self.__cmd = tools.build_cmd(cmd, cmd_remove, cmd_append)
|
||||||
|
self.__ice_servers = ice_servers
|
||||||
|
|
||||||
self.__janus_task: (asyncio.Task | None) = None
|
self.__janus_task: (asyncio.Task | None) = None
|
||||||
self.__janus_proc: (asyncio.subprocess.Process | None) = None # pylint: disable=no-member
|
self.__janus_proc: (asyncio.subprocess.Process | None) = None # pylint: disable=no-member
|
||||||
@ -173,13 +177,25 @@ class JanusRunner: # pylint: disable=too-many-instance-attributes
|
|||||||
part.format(**placeholders)
|
part.format(**placeholders)
|
||||||
for part in cmd
|
for part in cmd
|
||||||
]
|
]
|
||||||
self.__janus_proc = await aioproc.run_process(
|
env = {}
|
||||||
cmd=cmd,
|
ice_payload = self.__build_ice_payload(netcfg)
|
||||||
env={"JANUS_USTREAMER_WEB_ICE_URL": f"stun:{netcfg.stun_host}:{netcfg.stun_port}"},
|
if ice_payload:
|
||||||
)
|
env["JANUS_USTREAMER_WEB_ICE_URL"] = ice_payload
|
||||||
|
self.__janus_proc = await aioproc.run_process(cmd=cmd, env=env or None)
|
||||||
get_logger(0).info("Started Janus pid=%d: %s", self.__janus_proc.pid, tools.cmdfmt(cmd))
|
get_logger(0).info("Started Janus pid=%d: %s", self.__janus_proc.pid, tools.cmdfmt(cmd))
|
||||||
|
|
||||||
async def __kill_janus_proc(self) -> None:
|
async def __kill_janus_proc(self) -> None:
|
||||||
if self.__janus_proc:
|
if self.__janus_proc:
|
||||||
await aioproc.kill_process(self.__janus_proc, 5, get_logger(0))
|
await aioproc.kill_process(self.__janus_proc, 5, get_logger(0))
|
||||||
self.__janus_proc = None
|
self.__janus_proc = None
|
||||||
|
|
||||||
|
def __build_ice_payload(self, netcfg: _Netcfg) -> (str | None):
|
||||||
|
if self.__ice_servers:
|
||||||
|
try:
|
||||||
|
return f"json:{json.dumps(self.__ice_servers, ensure_ascii=False)}"
|
||||||
|
except Exception as ex: # pragma: no cover
|
||||||
|
get_logger(0).error("Can't encode ICE servers: %s", tools.efmt(ex))
|
||||||
|
return None
|
||||||
|
if netcfg.stun_host and netcfg.stun_port:
|
||||||
|
return f"stun:{netcfg.stun_host}:{netcfg.stun_port}"
|
||||||
|
return None
|
||||||
|
|||||||
@ -136,7 +136,12 @@ class Stun:
|
|||||||
return (StunNatType.FULL_CONE_NAT, resp)
|
return (StunNatType.FULL_CONE_NAT, resp)
|
||||||
|
|
||||||
if first.changed is None:
|
if first.changed is None:
|
||||||
raise RuntimeError(f"Changed addr is None: {first}")
|
get_logger(0).warning(
|
||||||
|
"STUN server %s:%d responded without CHANGED-ADDRESS; skipping NAT type detection",
|
||||||
|
self.__host,
|
||||||
|
self.__port,
|
||||||
|
)
|
||||||
|
return (StunNatType.ERROR, first)
|
||||||
resp = await self.__make_request("Change request [ext_ip != src_ip]", first.changed, b"")
|
resp = await self.__make_request("Change request [ext_ip != src_ip]", first.changed, b"")
|
||||||
if not resp.ok:
|
if not resp.ok:
|
||||||
return (StunNatType.CHANGED_ADDR_ERROR, resp)
|
return (StunNatType.CHANGED_ADDR_ERROR, resp)
|
||||||
|
|||||||
@ -120,3 +120,39 @@ def valid_ssl_ciphers(arg: Any) -> str:
|
|||||||
def valid_url(arg: Any) -> str:
|
def valid_url(arg: Any) -> str:
|
||||||
# XXX: VERY primitive
|
# XXX: VERY primitive
|
||||||
return check_re_match(arg, "HTTP(S) URL", r"^https?://[\[\w]+\S*")
|
return check_re_match(arg, "HTTP(S) URL", r"^https?://[\[\w]+\S*")
|
||||||
|
|
||||||
|
|
||||||
|
def valid_ice_servers(arg: Any) -> list[dict[str, Any]]:
|
||||||
|
name = "ICE servers list"
|
||||||
|
if arg is None:
|
||||||
|
return []
|
||||||
|
if not isinstance(arg, list):
|
||||||
|
raise_error(arg, name)
|
||||||
|
servers: list[dict[str, Any]] = []
|
||||||
|
for item in arg:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
raise_error(item, "ICE server entry")
|
||||||
|
urls = item.get("urls")
|
||||||
|
if isinstance(urls, str):
|
||||||
|
urls_list = [valid_stripped_string_not_empty(urls, "ICE server URL")]
|
||||||
|
elif isinstance(urls, list):
|
||||||
|
urls_list = [
|
||||||
|
valid_stripped_string_not_empty(url, "ICE server URL")
|
||||||
|
for url in urls
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
raise_error(urls, "ICE server URLs")
|
||||||
|
if not urls_list:
|
||||||
|
raise_error(urls, "ICE server URLs")
|
||||||
|
server: dict[str, Any] = {"urls": urls_list}
|
||||||
|
username = item.get("username")
|
||||||
|
if username is not None:
|
||||||
|
server["username"] = valid_stripped_string_not_empty(username, "ICE username")
|
||||||
|
credential = item.get("credential")
|
||||||
|
if credential is not None:
|
||||||
|
server["credential"] = valid_stripped_string_not_empty(credential, "ICE credential")
|
||||||
|
credential_type = item.get("credential_type") or item.get("credentialType")
|
||||||
|
if credential_type is not None:
|
||||||
|
server["credentialType"] = valid_stripped_string_not_empty(credential_type, "ICE credentialType")
|
||||||
|
servers.append(server)
|
||||||
|
return servers
|
||||||
|
|||||||
@ -781,7 +781,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="macro_script_events">Scripted events:</td>
|
<td i18n="macro_script_events">Scripted events:</td>
|
||||||
<tdvalue id="hid-recorder-events-count">0</tdvalue>
|
<td id="hid-recorder-events-count">0</td>
|
||||||
<td><sup><i i18n="macro_include_delays">include delays</i></sup></td>
|
<td><sup><i i18n="macro_include_delays">include delays</i></sup></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __organizeH
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var __ensureJanus = function(internal) {
|
var __ensureJanus = function(internal) {
|
||||||
if (__janus === null && !__stop && (!__ensuring || internal)) {
|
if (__janus === null && !__stop && (!__ensuring || internal)) {
|
||||||
__ensuring = true;
|
__ensuring = true;
|
||||||
__setInactive();
|
__setInactive();
|
||||||
@ -131,10 +131,75 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __organizeH
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var __decodeIcePayload = function(payload) {
|
||||||
|
if (typeof payload !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let data = payload.trim();
|
||||||
|
if (data.startsWith("json:")) {
|
||||||
|
data = data.slice(5).trim();
|
||||||
|
}
|
||||||
|
if (data.startsWith("{") || data.startsWith("[")) {
|
||||||
|
try {
|
||||||
|
let parsed = JSON.parse(data);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return parsed;
|
||||||
|
} else if (parsed && Array.isArray(parsed.servers)) {
|
||||||
|
return parsed.servers;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
__logError("Can't parse ICE payload:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var __normalizeIceEntry = function(entry) {
|
||||||
|
if (!entry || typeof entry !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let urls = entry.urls;
|
||||||
|
if (typeof urls === "string") {
|
||||||
|
urls = [urls];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(urls) || urls.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let normalized = {"urls": urls};
|
||||||
|
if (entry.username) {
|
||||||
|
normalized.username = entry.username;
|
||||||
|
}
|
||||||
|
if (entry.credential) {
|
||||||
|
normalized.credential = entry.credential;
|
||||||
|
}
|
||||||
|
if (entry.credentialType) {
|
||||||
|
normalized.credentialType = entry.credentialType;
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
};
|
||||||
|
|
||||||
|
var __normalizeIceServers = function(payload) {
|
||||||
|
let parsed = __decodeIcePayload(payload);
|
||||||
|
if (!parsed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let servers = [];
|
||||||
|
for (let entry of parsed) {
|
||||||
|
let normalized = __normalizeIceEntry(entry);
|
||||||
|
if (normalized) {
|
||||||
|
servers.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (servers.length > 0 ? servers : null);
|
||||||
|
};
|
||||||
|
|
||||||
var __getIceServers = function() {
|
var __getIceServers = function() {
|
||||||
if (__ice !== null && __ice.url) {
|
if (__ice !== null && __ice.url) {
|
||||||
__logInfo("Using the custom ICE Server got from uStreamer:", __ice);
|
let normalized = __normalizeIceServers(__ice.url);
|
||||||
|
if (normalized !== null) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
return [{"urls": __ice.url}];
|
return [{"urls": __ice.url}];
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user