Compare commits

..

16 Commits

Author SHA1 Message Date
mofeng-git
9b97e01b34 更像说明 2024-06-18 18:40:47 +08:00
mofeng-git
daa4b72625 删除多余文档 2024-06-18 18:30:26 +08:00
mofeng-git
99f2a1b09a 重构更新 2024-06-18 18:29:24 +08:00
mofeng-git
fdf58ea6f7 为玩客云设置Boot_SkipUSBBurning 2024-06-10 18:18:29 +08:00
mofeng-git
7f7b431824 fix 2024-06-10 17:30:25 +08:00
mofeng-git
4ee30e01be 更新说明 2024-06-10 16:18:33 +08:00
mofeng-git
9093329853 fix 2024-06-10 15:54:26 +08:00
mofeng-git
e303bc43ec 累计更新 2024-06-10 15:25:39 +08:00
mofeng-git
03d2da6c13 累计更新 2024-06-10 15:21:06 +08:00
mofeng-git
f15a18591b fix 2024-06-10 14:59:47 +08:00
mofeng-git
1c5f641ce7 修复路径 2024-06-10 14:58:07 +08:00
mofeng-git
e2126cc3ca 累积更新 2024-06-10 14:56:23 +08:00
mofeng-git
b8b6dff995 一些更新 2024-06-10 14:47:14 +08:00
SilentWind
ab3bb95bdb Update README.md 2024-03-24 22:32:45 +08:00
SilentWind
35fbf3d306 Update README.md 2024-03-06 17:58:27 +08:00
SilentWind
ffebd2b484 update ReadMe.md 2024-03-05 16:00:24 +00:00
54 changed files with 5206 additions and 1913 deletions

View File

@@ -1,6 +1,10 @@
更新日志
- V0.53: 调整dts CPU频率和版本添加H.264支持(来自群友 自.知
- V0.52[Build-Armbian]添加MSD内核补丁完全使用hzyitc的uboot减小boot分区从512M至128M引导和分区表使用UUID默认关闭线刷刷机dtb切换为外围设备模式led颜色修改红色UBoot-紫色(内核)-绿色(完全启>
- V0.57: [build]mac固定第一次启动时随机生成解决armbain源因密钥无法使用解决sd启动配置问题
- V0.56[Build-Armbian]回退5.9内核版本V0.52目前运行最稳定的内核版本支持docker;此前高内核的累积更新
- V0.55: [build]使用lanlan Uboot通电按重置键可进入线刷模式HDM可用WIFI模块已编译预装armbian-config实用工具Webterm和VNC修复tighervnc存在兼容问题可尝试使用uvnc viewer添加状态信息显示
- V0.54: [build]内核切换至xdarklight/linux/tree/meson-mx-integration-6.7-20231221系统运行更加稳定包含armbian-config等实用工具
- V0.53: [build]修复高内核OTG拔插内核崩溃问题切换至更新的Linux内核6.6.15-current-meson去除线刷镜像分区校验
- V0.52[Build-Armbian]添加MSD内核补丁完全使用hzyitc的uboot减小boot分区从512M至128M引导和分区表使用UUID默认关闭线刷刷机dtb切换为外围设备模式led颜色修改红色UBoot-紫色(内核)-绿色完全启动调整dts CPU频率和版本添加H.264支持(来自群友 自.知
- V0.51: [Build-Armbian]5.9内核构建,初步完成直刷镜像构建。
- V0.5通过锁定CPU频率修复ustreamer mjpeg视频流异常的问题屏蔽主程序找不到温度传感器的报错优化中文翻译更新VNC依赖优化安装流程。
- V0.4利用玩客云自动GPIO实现ATX开关机物理控制功能初步建立飞书使用文档制作一键安装脚本优化安装流程。

View File

@@ -4,53 +4,84 @@
### 介绍
One-KVM是基于玩客云硬件和PiKVM软件的远控设备。通过移植PiKVM该软件至玩客云设备上实现了极高的性价比不到百元功能即可接近甚至超越部分昂贵的商业设备
该软件可以帮助用户通过得到控制设备的HDMI 画面和鼠标键盘去远程管理服务器、工作站或个人PC等。 和基于软件的远程管理方式不同,无需在被控电脑安装任何软件,实现无侵入式控制。
该项目基于PiKVM和Fruity PiKVM提供了玩客云兼容PiKVM操作的自动处理脚本。
![image](https://github.com/mofeng-git/One-KVM/assets/62919083/ec7e049f-ca6c-426f-bfa4-314536965db0)
**功能特性**
![image](https://github.com/mofeng-git/One-KVM/assets/62919083/b160c03b-31c5-465b-b9f8-acf421a35f79)
One-KVM 是基于经济计算机硬件(目前为玩客云和 X64 兼容机和PiKVM软件的硬件级远程控制项目。KVM over IP 可以远程管理服务器或工作站,实现无侵入式控制,无论被控机为什么操作系统或是否安装了操作系统,具有更广泛的适用性。此项目基于 [PiKVM](https://github.com/pikvm/pikvm),和基于远控软件的远程管理方式不同,无需在被控电脑安装任何软件,实现无侵入式控制
### 快速开始
**方式一直刷One-KVM镜像**
**方式一:直刷 One-KVM 镜像**
本项目Releases页可以找到包含PiKVM预编译镜像内核版本为5.9.0-rc7。镜像名称带One-KVM前缀、burn后缀的为线刷镜像可使用USB_Burning_Tool软件线刷至玩客云。预编译线刷镜像为开箱即用刷好后启动设备就可以开始使用One-KVM。
对于玩客云设备,本项目 Releases 页可以找到适配玩客云的 One-KVM 预编译镜像。镜像名称带 One-KVM 前缀、burn 后缀的为线刷镜像,可使用 USB_Burning_Tool 软件线刷至玩客云。预编译线刷镜像为开箱即用,刷好后启动设备就可以开始使用 One-KVM。
**方式二One-KVM脚本安装**
**方式二One-KVM 脚本安装**
一键脚本适用于玩客云Armbian 22.11.0-trunk Jammy Linux onecloud 5.10.xxx(如5.10.149 5.10.158)镜像。如若使用此项目发布的Armbian基础镜像需注释掉脚本尾部的两个函数`override-uboot` `change-device-tree`
```bash
git clone https://github.com/mofeng-git/One-KVM.git
cd One-KVM && ./install.sh
cd One-KVM
bash install.sh
#第一阶段安装完成需要重启,再进行第二阶段安装
bash install.sh
#对于大陆网络环境,可以尝试使用下命令
wget https://mirror.ghproxy.com/https://github.com/mofeng-git/One-KVM/archive/refs/heads/main.zip -o One-KVM-main.zip && unzip One-KVM-main.zip
cd One-KVM-main && ./install.sh
#可选功能H.264 视频编码
bash kvmd_h264_install.sh
```
**方式三:docker镜像部署**
**方式三:Docker 镜像部署**
目前仅有pikvm-ch9329_amd64后续将支持更多控制方式和处理器架构。
目前仅有 pikvm-ch9329_amd64后续将支持更多控制方式和处理器架构。
```bash
#使用示例:
docker run -itd -p443:443 -p80:80 --name pikvm-docker --device=/dev/ttyUSB0:/dev/kvmd-hid --device=/dev/video0:/dev/kvmd-video pikvm-ch9329:0.61
```
详细内容可以参照飞书文档:[One-KVM使用文档](https://p1b237lu9xm.feishu.cn/drive/folder/IsOifWmMKlzYpRdWfcocI7jdnQA?from=from_copylink)
详细内容可以参照 [One-KVM文档](https://one-kvm.mofeng.run/)
### 功能特性
主要功能比较TinyPilot 社区版本、PiKVMv3 版本出现在这里仅做比较目的。
| 功能 | One-KVM | TinyPilot 社区版本 | PiKVMv3版本 |
| :------------: | :---------------------: | :----------------: | :----------: |
| HTML5界面语言 | 简体中文 | 英文 | 英文 |
| BIOS控制 | √ | √ | √ |
| 视频捕捉 | √ | √ | √ |
| 音频捕捉 | × | √ | √ |
| 鼠键捕获类型 | OTG CH9329 | OTG | OTG CH9329 |
| 从剪贴板粘贴 | √ | √ | √ |
| OCR识别 | √ | × | √ |
| LAN唤醒 | √ | × | √ |
| VNC支持 | √ | × | √ |
| HDMI环出 | √含HDMI设备初步支持 | × | × |
| 虚拟存储驱动器 | √仅含OTG设备支持 | × | √ |
| ATX开关机 | √仅含GPIO设备支持 | × | √ |
| 板载WiFi | × | √ | √ |
| 视频流格式 | MJPEG H.264(软编码) | MJPEG, H.264 | MJPEG, H.264 |
| 最大视频分辨率 | 1920x1080 | 1920x1080 | 1920x1080 |
### 已测试设备
- 玩客云
此脚本删除了对上游对树莓派设备的支持,如有需要请访问 [srepac/kvmd-armbian](https://github.com/srepac/kvmd-armbian/blob/master/install.sh)。
### 其他
目前此脚本基于[srepac/kvmd-armbian](https://github.com/srepac/kvmd-armbian/)项目重构了One-KVM安装脚本做了如下修改
1. 适配玩客云添加了初步CHROOT自动化支持
2. 资源本地化,减小网络原因的影响
3. 添加kvmd-ffmpeg和kvmd-display服务安装脚本
4. HTML汉化和一些微调
**赞助**
这个项目基于众多开源项目二次开发,作者为此花费了大量的时间和精力进行测试和维护。若此项目对您有用,您可以考虑通过 [为爱发电](https://afdian.net/a/silentwind) 赞助一笔小钱支持作者。作者将能够购买新的硬件(玩客云和周边设备)来测试和维护 One-KVM 的各种配置,并在项目上投入更多的时间。
**感谢名单**
<details>
浩龙的电子嵌入式之路(赞助)
Tsuki赞助
H_xiaoming
0蓝蓝0
@@ -68,10 +99,16 @@ Will
以及各位讨论交流的网友
</details>
**更新日志**
[One-KVM/ChangeLogs.txt](https://github.com/mofeng-git/One-KVM/blob/main/ChangeLogs.txt)
**Star历史**
[![Star 历史](https://api.star-history.com/svg?repos=mofeng-git/One-KVM&type=Date)](https://star-history.com/#mofeng-git/One-KVM&Date)
本项目间接或直接使用了下下列开源项目:
1. [pikvm/pikvm: Open and inexpensive DIY IP-KVM based on Raspberry Pi (github.com)](https://github.com/pikvm/pikvm)
2. [hzyitc/armbian-onecloud: Armbian for onecloud. 玩客云用armbian (github.com)](https://github.com/hzyitc/armbian-onecloud/)
3. [jacobbar/fruity-pikvm: Install Pi-KVM on debian SBCs such as Orange Pi, Banana Pi, Mango Pi, etc (github.com)](https://github.com/jacobbar/fruity-pikvm)
4. [kvmd-armbian/install.sh at master · srepac/kvmd-armbian (github.com)](https://github.com/srepac/kvmd-armbian/blob/master/install.sh)

BIN
aiofiles.tar Normal file

Binary file not shown.

24
armbian/armbian-motd Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
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/41-armbian-config ]; then /etc/update-motd.d/41-armbian-config; fi
printf " 欢迎使用 One-KVM基于开源程序 PiKVM 的 IP-KVM 应用
____________________________________________________________________________
要阻止内核消息输出到终端,可以使用命令\"dmesg -n 1\"
要修改默认账户 admin 的密码可使用命令 \"kvmd-htpasswd set admin\"
项目链接:
* https://pikvm.org
* https://github.com/srepac/kvmd-armbian
* https://github.com/mofeng-git/One-KVM
文档链接:
* https://docs.pikvm.org
* https://one-kvm.mofeng.run
"
if [ -e /etc/motd.custom ]; then cat /etc/motd.custom; fi

206
armbian/opt/armbian-sysinfo Normal file
View File

@@ -0,0 +1,206 @@
#!/bin/bash
#
# Copyright (c) Authors: http://www.armbian.com/authors
#
# This file is licensed under the terms of the GNU General Public
# License version 2. This program is licensed "as is" without any
# warranty of any kind, whether express or implied.
#
# DO NOT EDIT THIS FILE but add config options to /etc/default/armbian-motd
# generate system information
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
THIS_SCRIPT="sysinfo"
MOTD_DISABLE=""
STORAGE=/dev/sda1
SHOW_IP_PATTERN="^bond.*|^[ewr].*|^br.*|^lt.*|^umts.*|^lan.*"
CPU_TEMP_LIMIT=60
HDD_TEMP_LIMIT=60
AMB_TEMP_LIMIT=40
[[ -f /etc/default/armbian-motd ]] && . /etc/default/armbian-motd
for f in $MOTD_DISABLE; do
[[ $f == $THIS_SCRIPT ]] && exit 0
done
# don't edit below here
function display() {
# $1=name $2=value $3=red_limit $4=minimal_show_limit $5=unit $6=after $7=acs/desc{
# battery red color is opposite, lower number
if [[ "$1" == "Battery" ]]; then local great="<"; else local great=">"; fi
if [[ -n "$2" && "$2" > "0" && (( "${2%.*}" -ge "$4" )) ]]; then
printf "%-14s%s" "$1:"
if awk "BEGIN{exit ! ($2 $great $3)}"; then echo -ne "\e[0;91m $2"; else echo -ne "\e[0;92m $2"; fi
printf "%-1s%s\x1B[0m" "$5"
printf "%-11s%s\t" "$6"
return 1
fi
} # display
function getboardtemp() {
if [ -f /etc/armbianmonitor/datasources/soctemp ]; then
read raw_temp </etc/armbianmonitor/datasources/soctemp 2>/dev/null
if [ ! -z $(echo "$raw_temp" | grep -o "^[1-9][0-9]*\.\?[0-9]*$") ] && (( $(echo "${raw_temp} < 200" |bc -l) )); then
# Allwinner legacy kernels output degree C
board_temp=${raw_temp}
else
board_temp=$(awk '{printf("%d",$1/1000)}' <<<${raw_temp})
fi
elif [ -f /etc/armbianmonitor/datasources/pmictemp ]; then
# fallback to PMIC temperature
board_temp=$(awk '{printf("%d",$1/1000)}' </etc/armbianmonitor/datasources/pmictemp)
fi
} # getboardtemp
function batteryinfo() {
# Battery info for Allwinner
mainline_dir="/sys/power/axp_pmu"
legacy_dir="/sys/class/power_supply"
if [[ -e "$mainline_dir" ]]; then
read status_battery_connected < $mainline_dir/battery/connected 2>/dev/null
if [[ "$status_battery_connected" == "1" ]]; then
read status_battery_charging < $mainline_dir/charger/charging
read status_ac_connect < $mainline_dir/ac/connected
read battery_percent< $mainline_dir/battery/capacity
# dispay charging / percentage
if [[ "$status_ac_connect" == "1" && "$battery_percent" -lt "100" ]]; then
status_battery_text=" charging"
elif [[ "$status_ac_connect" == "1" && "$battery_percent" -eq "100" ]]; then
status_battery_text=" charged"
else
status_battery_text=" discharging"
fi
fi
elif [[ -e "$legacy_dir/axp813-ac" ]]; then
read status_battery_connected < $legacy_dir/axp20x-battery/present
if [[ "$status_battery_connected" == "1" ]]; then
status_battery_text=" "$(awk '{print tolower($0)}' < $legacy_dir/axp20x-battery/status)
read status_ac_connect < $legacy_dir/axp813-ac/present
read battery_percent< $legacy_dir/axp20x-battery/capacity
fi
elif [[ -e "$legacy_dir/battery" ]]; then
if [[ (("$(cat $legacy_dir/battery/voltage_now)" -gt "5" )) ]]; then
status_battery_text=" "$(awk '{print tolower($0)}' < $legacy_dir/battery/status)
read battery_percent <$legacy_dir/battery/capacity
fi
fi
} # batteryinfo
function ambienttemp() {
# define where w1 usually shows up
W1_DIR="/sys/devices/w1_bus_master1/"
if [ -f /etc/armbianmonitor/datasources/ambienttemp ]; then
read raw_temp </etc/armbianmonitor/datasources/ambienttemp 2>/dev/null
amb_temp=$(awk '{printf("%d",$1/1000)}' <<<${raw_temp})
echo $amb_temp
elif [[ -d $W1_DIR && $ONE_WIRE == yes ]]; then
device=$(ls -1 $W1_DIR | grep -Eo '^[0-9]{1,4}' | head -1)
if [[ -n $device ]]; then
if [[ -d ${W1_DIR}${device}/hwmon/hwmon0 ]]; then hwmon=0; else hwmon=1; fi
read raw_temp < ${W1_DIR}${device}/hwmon/hwmon${hwmon}/temp1_input 2>/dev/null
amb_temp=$(awk '{printf("%d",$1/1000)}' <<<${raw_temp})
echo $amb_temp
fi
else
# read ambient temperature from USB device if available
if [[ ! -f /usr/bin/temper ]]; then
echo ""
return
fi
amb_temp=$(temper -c 2>/dev/null)
case ${amb_temp} in
*"find the USB device"*)
echo ""
;;
*)
amb_temp=$(awk '{print $NF}' <<<$amb_temp | sed 's/C//g')
echo -n "scale=1;${amb_temp}/1" | grep -oE "\-?[[:digit:]]+\.[[:digit:]]"
esac
fi
} # ambienttemp
function get_ip_addresses() {
local ips=()
for f in /sys/class/net/*; do
local intf=$(basename $f)
# match only interface names starting with e (Ethernet), br (bridge), w (wireless), r (some Ralink drivers use ra<number> format)
if [[ $intf =~ $SHOW_IP_PATTERN ]]; then
local tmp=$(ip -4 addr show dev $intf | awk '/inet/ {print $2}' | cut -d'/' -f1)
# add both name and IP - can be informative but becomes ugly with long persistent/predictable device names
#[[ -n $tmp ]] && ips+=("$intf: $tmp")
# add IP only
[[ -n $tmp ]] && ips+=("$tmp")
fi
done
echo "${ips[@]}"
} # get_ip_addresses
function storage_info() {
# storage info
RootInfo=$(df -h /)
root_usage=$(awk '/\// {print $(NF-1)}' <<<${RootInfo} | sed 's/%//g')
root_total=$(awk '/\// {print $(NF-4)}' <<<${RootInfo})
StorageInfo=$(df -h $STORAGE 2>/dev/null | grep $STORAGE)
if [[ -n "${StorageInfo}" && ${RootInfo} != *$STORAGE* ]]; then
storage_usage=$(awk '/\// {print $(NF-1)}' <<<${StorageInfo} | sed 's/%//g')
storage_total=$(awk '/\// {print $(NF-4)}' <<<${StorageInfo})
if [[ -n "$(command -v smartctl)" ]]; then
DISK="${STORAGE::-1}"
storage_temp+=$(sudo smartctl -A $DISK 2> /dev/null | grep -i temperature | awk '{print $(NF-2)}')
fi
fi
} # storage_info
# query various systems and send some stuff to the background for overall faster execution.
# Works only with ambienttemp and batteryinfo since A20 is slow enough :)
amb_temp=$(ambienttemp &)
ip_address=$(get_ip_addresses &)
batteryinfo
storage_info
getboardtemp
critical_load=80
# get uptime, logged in users and load in one take
UPTIME=$(LC_ALL=C uptime)
UPT1=${UPTIME#*'up '}
UPT2=${UPT1%'user'*}
users=${UPT2//*','}
users=${users//' '}
time=${UPT2%','*}
time=${time//','}
time=$(echo $time | xargs)
load=${UPTIME#*'load average: '}
load=${load//','}
load=$(echo $load | cut -d" " -f1)
[[ $load == 0.0* ]] && load=0.10
cpucount=$(grep -c processor /proc/cpuinfo)
load=$(awk '{printf("%.0f",($1/$2) * 100)}' <<< "$load $cpucount")
# memory and swap
mem_info=$(LC_ALL=C free -w 2>/dev/null | grep "^Mem" || LC_ALL=C free | grep "^Mem")
memory_usage=$(awk '{printf("%.0f",(($2-($4+$6+$7))/$2) * 100)}' <<<${mem_info})
mem_info=$(echo $mem_info | awk '{print $2}')
memory_total=$(( mem_info / 1024 ))
swap_info=$(LC_ALL=C free -m | grep "^Swap")
swap_usage=$( (awk '/Swap/ { printf("%3.0f", $3/$2*100) }' <<<${swap_info} 2>/dev/null || echo 0) | tr -c -d '[:digit:]')
swap_total=$(awk '{print $(2)}' <<<${swap_info})
if [[ ${memory_total} -gt 1000 ]]; then
memory_total=$(awk '{printf("%.2f",$1/1024)}' <<<${memory_total})"G"
else
memory_total+="M"
fi
if [[ ${swap_total} -gt 500 ]]; then
swap_total=$(awk '{printf("%.2f",$1/1024)}' <<<${swap_total})"G"
else
swap_total+="M"
fi

70
armbian/opt/vcgencmd Normal file
View File

@@ -0,0 +1,70 @@
#!/bin/bash
cd `dirname $0`
source armbian-sysinfo
case $1 in
get_throttled) echo "throttled=0x0";;
# measure_temp) echo "temp=$board_temp'C";;
get_config)
case $2 in
total_mem)
NUM=$( free -m | grep Mem: | awk '{print $2}' )
if [ -e /var/log/dmesg ]; then
KB=$( sudo grep 'Memory:' /var/log/dmesg | awk '{print $5}' | cut -d'/' -f2 | sed 's/K//g' | head -1 )
else
KB=""
fi
if [[ "$KB" == "" ]]; then
GB=`echo "( $NUM + 256 ) / 256" | bc`
MB=$( echo "${GB} * 256" | bc )
else
GB=$( echo "($KB + 2048) / 1024 / 256" | bc )
MB=$( echo "${GB} * 256" | bc )
fi
echo "total_mem=$MB"
;;
*)
echo "invalid option";;
esac
;;
measure_clock)
case ${2} in
arm)
# awk is probably overkill....
value=`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq | awk '{print $1 * 1000}'`
echo 'frequency(45)='${value}
exit
;;
core)
value=0 # TODO / FIXME
echo 'frequency(1)='${value}
exit
;;
# TODO anything else thrown an error/debug
esac
exit
;;
measure_temp)
# awk is probably overkill....
value=`cat /sys/class/thermal/thermal_zone0/temp | awk '{print $1 / 1000}'`
echo 'temp='${value}"'C"
exit
;;
measure_volts)
case ${2} in
core)
value=1 # TODO / FIXME
echo 'volt='${value}'.0000V'
exit
;;
# TODO anything else thrown an error/debug
esac
;;
version)
echo 'Nov 4 2018 16:31:07'
echo 'Copyright (c) 2012 rock64'
echo 'version rock64_TODO (clean) (release)'
exit
;;
esac

View File

@@ -0,0 +1,6 @@
# 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[1-9]*", SUBSYSTEM=="video4linux", PROGRAM="/usr/bin/kvmd-udev-hdmiusb-check rpi4 1-1.4:1.0", 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"

View File

@@ -16,8 +16,13 @@ vnc:
vncauth:
enabled: true # Enable auth via /etc/kvmd/vncpasswd
kvmd:
hid:
mouse_alt:
device: /dev/kvmd-hid-mouse-alt # allow relative mouse mode
msd:
type: disabled
atx:
type: disabled
gpio:
drivers:
wol_server1:
@@ -25,10 +30,10 @@ kvmd:
mac: 2c:56:dc:db:7c:1e
short_press:
type: cmd
cmd: [/usr/bin/sudo, short_press_gpio420]
cmd: [/usr/bin/sudo, onecloud_gpio.sh, short]
long_press:
type: cmd
cmd: [/usr/bin/sudo, long_press_gpio420]
cmd: [/usr/bin/sudo, onecloud_gpio.sh, long]
scheme:
wol_server1:
driver: wol_server1
@@ -47,7 +52,7 @@ kvmd:
switch: false
view:
header:
title: ATX
title: 电源管理
table:
- ["#电源管理"]
- []
@@ -56,3 +61,12 @@ kvmd:
- []
- ["#网络唤醒"]
- ["#被控机设备", wol_server1|网络唤醒]
streamer:
#forever: true
cmd_append: [--slowdown]
resolution:
default: 1280x720
desired_fps:
default: 30
max: 60
min: 0

View File

@@ -1,62 +0,0 @@
# 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
hid:
type: otg
mouse_alt:
device: /dev/kvmd-hid-mouse-alt
atx:
type: disabled
msd:
type: otg
streamer:
quality: 0
resolution:
default: 1280x720
available:
- 1920x1080
- 1600x1200
- 1360x768
- 1280x1024
- 1280x960
- 1280x720
- 1024x768
- 800x600
- 720x576
- 720x480
- 640x480
cmd:
- "/usr/bin/ustreamer"
- "--device=/dev/kvmd-video"
- "--persistent"
- "--format=mjpeg"
- "--resolution={resolution}"
- "--desired-fps={desired_fps}"
- "--drop-same-frames=30"
- "--last-as-blank=0"
- "--unix={unix}"
- "--unix-rm"
- "--unix-mode=0660"
- "--exit-on-parent-death"
- "--process-name-prefix={process_name_prefix}"
- "--notify-parent"
- "--no-log-colors"
- "--sink=kvmd::ustreamer::jpeg"
- "--sink-mode=0660"
vnc:
memsink:
jpeg:
sink: "kvmd::ustreamer::jpeg"

Binary file not shown.

864
install-x86.sh Normal file
View File

@@ -0,0 +1,864 @@
#!/bin/bash
# https://github.com/srepac/kvmd-armbian
# modified by SilentWind 2024-06-17
# modified by xe5700 2021-11-04 xe5700@outlook.com
# modified by NewbieOrange 2021-11-04
# created by @srepac 08/09/2021 srepac@kvmnerds.com
# Scripted Installer of Pi-KVM on x86 (as long as it's running python 3.10 or higher)
#
# *** MSD is disabled by default ***
#
# Mass Storage Device requires the use of a USB thumbdrive or SSD and will need to be added in /etc/fstab
: '
# SAMPLE /etc/fstab entry for USB drive with only one partition formatted as ext4 for the entire drive:
/dev/sda1 /var/lib/kvmd/msd ext4 nodev,nosuid,noexec,ro,errors=remount-ro,data=journal,X-kvmd.otgmsd-root=/var/lib/kvmd/msd,X-kvmd.otgmsd-user=kvmd 0 0
'
# NOTE: This was tested on a new install of raspbian desktop and lite versions, but should also work on an existing install.
#
# Last change 20240526 2345 PDT
VER=3.4
set +x
PIKVMREPO="https://files.pikvm.org/repos/arch/rpi4"
KVMDFILE="kvmd-3.291-1-any.pkg.tar.xz"
KVMDCACHE="/var/cache/kvmd"; mkdir -p $KVMDCACHE
PKGINFO="${KVMDCACHE}/packages.txt"
APP_PATH=$(readlink -f $(dirname $0))
LOGFILE="${KVMDCACHE}/installer.log"; touch $LOGFILE; echo "==== $( date ) ====" >> $LOGFILE
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "usage: $0 [-f] where -f will force re-install new pikvm platform"
exit 1
fi
CWD=`pwd`
WHOAMI=$( whoami )
if [ "$WHOAMI" != "root" ]; then
echo "$WHOAMI, please run script as root."
exit 1
fi
PYTHONVER=$( python3 -V | cut -d' ' -f2 | cut -d'.' -f1,2 )
case $PYTHONVER in
3.10|3.11)
echo "Python $PYTHONVER is supported." | tee -a $LOGFILE
;;
*)
echo "Python $PYTHONVER is NOT supported. Please make sure you have python3.10 or higher installed. Exiting." | tee -a $LOGFILE
exit 1
;;
esac
MAKER=$(tr -d '\0' < /proc/device-tree/model | awk '{print $1}')
gen-ssl-certs() {
cd /etc/kvmd/nginx/ssl
openssl ecparam -out server.key -name prime256v1 -genkey
openssl req -new -x509 -sha256 -nodes -key server.key -out server.crt -days 3650 \
-subj "/C=US/ST=Denial/L=Denial/O=Pi-KVM/OU=Pi-KVM/CN=$(hostname)"
cp server* /etc/kvmd/vnc/ssl/
cd ${APP_PATH}
} # end gen-ssl-certs
create-override() {
if [ $( grep ^kvmd: /etc/kvmd/override.yaml | wc -l ) -eq 0 ]; then
if [[ $( echo $platform | grep usb | wc -l ) -eq 1 ]]; then
cat <<USBOVERRIDE >> /etc/kvmd/override.yaml
kvmd:
hid:
### add entries for use with the ch9329 serial HID
type: ch9329
speed: 9600 # default speed after loading ch9329 plugin is 9600
device: /dev/kvmd-hid
msd:
type: disabled
atx:
type: disabled
streamer:
#forever: true
cmd_append:
- "--slowdown" # so target doesn't have to reboot
resolution:
default: 1280x720
USBOVERRIDE
else
cat <<CSIOVERRIDE >> /etc/kvmd/override.yaml
kvmd:
hid:
### add entries for use with the ch9329 serial HID
type: ch9329
speed: 9600 # default speed after loading ch9329 plugin is 9600
device: /dev/kvmd-hid
msd:
type: disabled
streamer:
#forever: true
cmd_append:
- "--slowdown" # so target doesn't have to reboot
CSIOVERRIDE
fi
fi
} # end create-override
install-python-packages() {
echo "apt install -y python3-aiofiles python3-aiohttp python3-appdirs python3-asn1crypto python3-async-timeout
python3-bottle python3-cffi python3-chardet python3-click python3-colorama python3-cryptography python3-dateutil
python3-dbus python3-dev python3-hidapi python3-idna python3-libgpiod python3-mako python3-marshmallow
python3-more-itertools 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-build
python3-pyotp python3-qrcode python3-serial"
apt install -y python3-aiofiles python3-aiohttp python3-appdirs python3-asn1crypto python3-async-timeout\
python3-bottle python3-cffi python3-chardet python3-click python3-colorama python3-cryptography python3-dateutil\
python3-dbus python3-dev python3-hidapi python3-idna python3-libgpiod python3-mako python3-marshmallow\
python3-more-itertools 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-build\
python3-pyotp python3-qrcode python3-serial >> $LOGFILE
} # end install python-packages
otg-devices() {
modprobe libcomposite
if [ ! -e /sys/kernel/config/usb_gadget/kvmd ]; then
mkdir -p /sys/kernel/config/usb_gadget/kvmd/functions
cd /sys/kernel/config/usb_gadget/kvmd/functions
mkdir hid.usb0 hid.usb1 hid.usb2 mass_storage.usb0
fi
cd ${APP_PATH}
} # end otg-device creation
boot-files() {
# Remove OTG serial (Orange pi zero's kernel not support it)
sed -i '/^g_serial/d' /etc/modules
# /etc/modules required entries for DWC2, HID and I2C
if [[ $( grep -w dwc2 /etc/modules | wc -l ) -eq 0 ]]; then
echo "dwc2" >> /etc/modules
fi
if [[ $( grep -w libcomposite /etc/modules | wc -l ) -eq 0 ]]; then
echo "libcomposite" >> /etc/modules
fi
if [[ $( grep -w i2c-dev /etc/modules | wc -l ) -eq 0 ]]; then
echo "i2c-dev" >> /etc/modules
fi
printf "\n/etc/modules\n\n" | tee -a $LOGFILE
cat /etc/modules | tee -a $LOGFILE
} # end of necessary boot files
get-packages() {
printf "\n\n-> Getting Pi-KVM packages from ${PIKVMREPO}\n\n" | tee -a $LOGFILE
cp -f ${APP_PATH}/kvmd-packages/* ${KVMDCACHE}
} # end get-packages function
get-platform() {
platform="kvmd-platform-v0-hdmiusb-rpi3";
echo
echo "Platform selected -> $platform" | tee -a $LOGFILE
echo
} # end get-platform
install-kvmd-pkgs() {
cd /
INSTLOG="${KVMDCACHE}/installed_ver.txt"; rm -f $INSTLOG
date > $INSTLOG
# uncompress platform package first
i=$( ls ${KVMDCACHE}/${platform}*.tar.xz | grep 3.291 )
# change the log entry to show 3.291 platform installed as we'll be forcing kvmd-3.291 instead of latest/greatest kvmd
_platformver=$( echo $i | sed -e 's/3\.29[2-9]*/3.291/g' -e 's/3\.3[0-9]*/3.291/g' )
echo "-> Extracting package $_platformver into /" | tee -a $INSTLOG
tar xfJ $i
# then uncompress, kvmd-{version}, kvmd-webterm, and janus packages
for i in $( ls ${KVMDCACHE}/*.tar.xz | egrep 'kvmd-[0-9]|webterm' )
do
case $i in
*kvmd-3.29[2-9]*|*kvmd-3.[3-9]*|*kvmd-[45].[1-9]*) # if latest/greatest is 3.292 and higher, then force 3.291 install
echo "*** Force install kvmd 3.291 ***" | tee -a $LOGFILE
i=$KVMDCACHE/$KVMDFILE
;;
*)
;;
esac
echo "-> Extracting package $i into /" >> $INSTLOG
tar xfJ $i
done
# uncompress janus package if /usr/bin/janus doesn't exist
if [ ! -e /usr/bin/janus ]; then
i=$( ls ${KVMDCACHE}/*.tar.xz | egrep janus | grep -v 1x )
echo "-> Extracting package $i into /" >> $INSTLOG
tar xfJ $i
else # confirm that /usr/bin/janus actually runs properly
/usr/bin/janus --version > /dev/null 2>> $LOGFILE
if [ $? -eq 0 ]; then
echo "You have a working valid janus binary." | tee -a $LOGFILE
else # error status code, so uncompress from REPO package
i=$( ls ${KVMDCACHE}/*.tar.xz | egrep janus )
echo "-> Extracting package $i into /" >> $INSTLOG
tar xfJ $i
fi
fi
cd ${APP_PATH}
} # end install-kvmd-pkgs
fix-udevrules() {
# for hdmiusb, replace %b with 1-1.4:1.0 in /etc/udev/rules.d/99-kvmd.rules
sed -i -e 's+\%b+1-1.4:1.0+g' -e 's+ttyAMA0+ttyUSB[0-2]+g' /etc/udev/rules.d/99-kvmd.rules | tee -a $LOGFILE
echo
cat /etc/udev/rules.d/99-kvmd.rules | tee -a $LOGFILE
} # end fix-udevrules
enable-kvmd-svcs() {
# enable KVMD services but don't start them
echo "-> Enabling $SERVICES services, but do not start them." | tee -a $LOGFILE
systemctl enable $SERVICES
} # end enable-kvmd-svcs
build-ustreamer() {
printf "\n\n-> Building ustreamer\n\n" | tee -a $LOGFILE
# Install packages needed for building ustreamer source
echo "apt install -y libevent-dev libjpeg-dev libbsd-dev libgpiod-dev libsystemd-dev janus-dev janus" | tee -a $LOGFILE
apt install -y libevent-dev libjpeg-dev libbsd-dev libgpiod-dev libsystemd-dev janus-dev janus >> $LOGFILE
# fix refcount.h
sed -i -e 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h
# Download ustreamer source and build it
cd /tmp
unzip ${APP_PATH}/sources/ustreamer-6.12.zip
cd ustreamer-6.12/
#添加WITH_PYTHON=1 使kvmd-vnc正常工作
make WITH_GPIO=1 WITH_SYSTEMD=1 WITH_JANUS=1 WITH_PYTHON=1 -j
#删除 --prefix=$(PREFIX) 修复无法安装pythgon包的问题
sed -i 's/--prefix=\$(PREFIX)//g' python/Makefile
make install WITH_PYTHON=1
# kvmd service is looking for /usr/bin/ustreamer
ln -sf /usr/local/bin/ustreamer* /usr/bin/
# add janus support
mkdir -p /usr/lib/ustreamer/janus
cp /tmp/ustreamer-6.12/janus/libjanus_ustreamer.so /usr/lib/ustreamer/janus
} # end build-ustreamer
install-dependencies() {
echo
echo "-> Installing dependencies for pikvm" | tee -a $LOGFILE
echo "apt install -y make nginx python3 gcc unzip net-tools bc expect v4l-utils iptables vim dos2unix screen tmate nfs-common gpiod ffmpeg dialog iptables dnsmasq git python3-pip tesseract-ocr tesseract-ocr-eng libasound2-dev libsndfile-dev libspeexdsp-dev build-essential libssl-dev libffi-dev lm-sensors" | tee -a $LOGFILE
apt install -y make nginx python3 gcc unzip net-tools bc expect v4l-utils iptables vim dos2unix screen tmate nfs-common gpiod ffmpeg dialog iptables dnsmasq git python3-pip tesseract-ocr tesseract-ocr-eng libasound2-dev libsndfile-dev libspeexdsp-dev build-essential libssl-dev libffi-dev lm-sensors >> $LOGFILE
sed -i -e 's/#port=5353/port=5353/g' /etc/dnsmasq.conf
install-python-packages
echo "-> Make tesseract data link" | tee -a $LOGFILE
ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata
echo "-> Install TTYD" | tee -a $LOGFILE
apt install -y ttyd | tee -a $LOGFILE
/usr/bin/ttyd -v | tee -a $LOGFILE
if [ ! -e /usr/local/bin/gpio ]; then
printf "\n\n-> Building wiringpi from source\n\n" | tee -a $LOGFILE
cd /tmp; rm -rf WiringPi-3.6
unzip ${APP_PATH}/sources/WiringPi-3.6.zip
cd WiringPi-3.6
./build
else
printf "\n\n-> Wiringpi (gpio) is already installed.\n\n" | tee -a $LOGFILE
fi
gpio -v | tee -a $LOGFILE
echo "-> Install ustreamer" | tee -a $LOGFILE
if [ ! -e /usr/bin/ustreamer ]; then
cd /tmp
### required dependent packages for ustreamer ###
build-ustreamer
cd ${APP_PATH}
fi
echo -n "ustreamer version: " | tee -a $LOGFILE
ustreamer -v | tee -a $LOGFILE
ustreamer --features | tee -a $LOGFILE
} # end install-dependencies
python-pkg-dir() {
# debian system python3 no alias
# create quick python script to show where python packages need to go
cat << MYSCRIPT > /tmp/syspath.py
#!$(which python3)
import sys
print (sys.path)
MYSCRIPT
chmod +x /tmp/syspath.py
#PYTHONDIR=$( /tmp/syspath.py | awk -F, '{print $NF}' | cut -d"'" -f2 )
### hardcode path for armbian/raspbian
PYTHONDIR="/usr/lib/python3/dist-packages"
} # end python-pkg-dir
fix-nginx-symlinks() {
# disable default nginx service since we will use kvmd-nginx instead
echo
echo "-> Disabling nginx service, so that we can use kvmd-nginx instead" | tee -a $LOGFILE
systemctl disable --now nginx
# setup symlinks
echo
echo "-> Creating symlinks for use with kvmd python scripts" | tee -a $LOGFILE
if [ ! -e /usr/bin/nginx ]; then ln -sf /usr/sbin/nginx /usr/bin/; fi
if [ ! -e /usr/sbin/python ]; then ln -sf /usr/bin/python3 /usr/sbin/python; fi
if [ ! -e /usr/bin/iptables ]; then ln -sf /usr/sbin/iptables /usr/bin/iptables; fi
if [ ! -e /usr/bin/vcgencmd ]; then ln -sf /opt/vc/bin/* /usr/bin/; chmod +x /opt/vc/bin/*; fi
python-pkg-dir
if [ ! -e $PYTHONDIR/kvmd ]; then
# Debian python版本比 pikvm官方的低一些
# in case new kvmd packages are now using python 3.11
ln -sf /usr/lib/python3.1*/site-packages/kvmd* ${PYTHONDIR}
fi
} # end fix-nginx-symlinks
fix-python-symlinks(){
python-pkg-dir
if [ ! -e $PYTHONDIR/kvmd ]; then
# Debian python版本比 pikvm官方的低一些
ln -sf /usr/lib/python3.1*/site-packages/kvmd* ${PYTHONDIR}
fi
}
apply-custom-patch(){
read -p "Do you want apply old kernel msd patch? [y/n]" answer
case $answer in
n|N|no|No)
echo 'You skipped this patch.'
;;
y|Y|Yes|yes)
./patches/custom/old-kernel-msd/apply.sh
;;
*)
echo "Try again.";;
esac
}
fix-webterm() {
echo
echo "-> Creating kvmd-webterm homedir" | tee -a $LOGFILE
mkdir -p /home/kvmd-webterm
chown kvmd-webterm /home/kvmd-webterm
ls -ld /home/kvmd-webterm | tee -a $LOGFILE
# remove -W option since ttyd installed on raspbian/armbian is 1.6.3 (-W option only works with ttyd 1.7.x)
_ttydver=$( /usr/bin/ttyd -v | awk '{print $NF}' )
case $_ttydver in
1.6*)
echo "ttyd $_ttydver found. Removing -W from /lib/systemd/system/kvmd-webterm.service"
sed -i -e '/-W \\/d' /lib/systemd/system/kvmd-webterm.service
;;
1.7*)
echo "ttyd $_ttydver found. Nothing to do."
;;
esac
# add sudoers entry for kvmd-webterm user to be able to run sudo
echo "kvmd-webterm ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/kvmd-webterm; chmod 440 /etc/sudoers.d/kvmd-webterm
} # end fix-webterm
create-kvmdfix() {
# Create kvmd-fix service and script
cat <<ENDSERVICE > /lib/systemd/system/kvmd-fix.service
[Unit]
Description=KVMD Fixes
After=network.target network-online.target nss-lookup.target
Before=kvmd.service
[Service]
User=root
Type=simple
ExecStart=/usr/bin/kvmd-fix
[Install]
WantedBy=multi-user.target
ENDSERVICE
cat <<SCRIPTEND > /usr/bin/kvmd-fix
#!/bin/bash
# Written by @srepac
# 1. Properly set group ownership of /dev/gpio*
# 2. fix /dev/kvmd-video symlink to point to /dev/video1 (Amglogic Device video0 is not usb device)
#
### These fixes are required in order for kvmd service to start properly
#
set -x
chgrp gpio /dev/gpio*
chmod 660 /dev/gpio*
ls -l /dev/gpio*
udevadm trigger
ls -l /dev/kvmd-video
if [ \$( systemctl | grep kvmd-oled | grep -c activ ) -eq 0 ]; then
echo "kvmd-oled service is not enabled."
exit 0
else
echo "kvmd-oled service is enabled and activated."
fi
### kvmd-oled fix: swap i2c-0 <-> i2c-1 (code is looking for I2C oled on i2c-1)
# pins #1 - 3.3v, #3 - SDA, #5 - SCL, and #9 - GND
i2cget -y 0 0x3c
if [ \$? -eq 0 ]; then
echo "-> Found valid I2C OLED at i2c-0. Applying I2C OLED fix."
cd /dev
# rename i2c-0 -> i2c-9, move i2c-1 to i2c-0, and rename the good i2c-9 to i2c-1
mv i2c-0 i2c-9
mv i2c-1 i2c-0
mv i2c-9 i2c-1
# restart kvmd-oled service
systemctl restart kvmd-oled
else
echo "-> I2C OLED fix already applied and OLED should be showing info."
fi
SCRIPTEND
chmod +x /usr/bin/kvmd-fix
cat << CHRESET > /root/ch_reset.py
#!/usr/bin/python3
import serial
import time
device_path = "/dev/kvmd-hid"
chip = serial.Serial(device_path, 9600, timeout=1)
command = [87, 171, 0, 15, 0]
sum = sum(command) % 256
command.append(sum)
print("Resetting CH9329")
chip.write(serial.to_bytes(command))
time.sleep(2)
data = list(chip.read(5))
print("Initial data:", data)
if data[4] :
more_data = list(chip.read(data[4]))
data.extend(more_data)
print("Output: ", data)
chip.close()
CHRESET
chmod +x /root/ch_reset.py
} # end create-kvmdfix
set-ownership() {
# set proper ownership of password files and kvmd-webterm homedir
cd /etc/kvmd
chown kvmd:kvmd htpasswd
chown kvmd-ipmi:kvmd-ipmi ipmipasswd
chown kvmd-vnc:kvmd-vnc vncpasswd
chown kvmd-webterm /home/kvmd-webterm
# add kvmd user to video group (this is required in order to use CSI bridge with OMX and h264 support)
usermod -a -G video kvmd
# add kvmd user to dialout group (required for xh_hk4401 kvm switch support)
usermod -a -G dialout kvmd
} # end set-ownership
check-kvmd-works() {
echo "-> Checking kvmd -m works before continuing" | tee -a $LOGFILE
kvmd -m
invalid=1
! $NOTCHROOT || while [ $invalid -eq 1 ]; do
#kvmd -m
read -p "Did kvmd -m run properly? [y/n] " answer
case $answer in
n|N|no|No)
echo "Please install missing packages as per the kvmd -m output in another ssh/terminal."
;;
y|Y|Yes|yes)
invalid=0
;;
*)
echo "Try again.";;
esac
done
} # end check-kvmd-works
start-kvmd-svcs() {
#### start the main KVM services in order ####
# 1. nginx is the webserver
# 2. kvmd-otg is for OTG devices (keyboard/mouse, etc..)
# 3. kvmd is the main daemon
systemctl daemon-reload
systemctl restart $SERVICES
} # end start-kvmd-svcs
fix-motd() {
if [ -e /etc/motd ]; then rm /etc/motd; fi
cp armbian/armbian-motd /usr/bin/
chmod +x /usr/bin/armbian-motd
chmod +x /etc/update-motd.d/*
sed -i 's/cat \/etc\/motd/armbian-motd/g' /lib/systemd/system/kvmd-webterm.service
systemctl daemon-reload
# systemctl restart kvmd-webterm
} # end fix-motd
# 安装armbian的包
armbian-packages() {
mkdir -p /opt/vc/bin/
#cd /opt/vc/bin
if [ ! -e /usr/bin/vcgencmd ]; then
# Install vcgencmd for armbian platform
cp -rf armbian/opt/* /opt/vc/bin
else
ln -s /usr/bin/vcgencmd /opt/vc/bin/
fi
#cp -rf armbian/udev /etc/
cd ${APP_PATH}
} # end armbian-packages
fix-nfs-msd() {
NAME="aiofiles.tar"
LOCATION="/usr/lib/python3.11/site-packages"
echo "-> Extracting $NAME into $LOCATION" | tee -a $LOGFILE
tar xvf $NAME -C $LOCATION
echo "-> Renaming original aiofiles and creating symlink to correct aiofiles" | tee -a $LOGFILE
cd /usr/lib/python3/dist-packages
mv aiofiles aiofiles.$(date +%Y%m%d.%H%M)
ln -s $LOCATION/aiofiles .
ls -ld aiofiles* | tail -5
}
apply-x86-mods() {
TARBALL="${APP_PATH}/x86-mods.tar"
if [ -e $TARBALL ]; then
echo "-> Making backup of files that require modification" | tee -a $LOGFILE
for i in $( tar tf $TARBALL ); do
echo "cp $PYTHONDIR/$i $PYTHONDIR/$i.orig" | tee -a $LOGFILE
cp $PYTHONDIR/$i $PYTHONDIR/$i.orig
done
tar tvf $TARBALL
echo "tar xvf $TARBALL -C $PYTHONDIR" | tee -a $LOGFILE
tar xvf $TARBALL -C $PYTHONDIR
for i in $( tar tf $TARBALL ); do
ls -l $PYTHONDIR/$i
done
else
echo "Missing $TARBALL. Please obtain the tar file from @srepac and try again." | tee -a $LOGFILE
fi
}
fix-nginx() {
#set -x
KERNEL=$( uname -r | awk -F\- '{print $1}' )
ARCH=$( uname -r | awk -F\- '{print $NF}' )
echo "KERNEL: $KERNEL ARCH: $ARCH" | tee -a $LOGFILE
case $ARCH in
ARCH) SEARCHKEY=nginx-mainline;;
*) SEARCHKEY="nginx/";;
esac
HTTPSCONF="/etc/kvmd/nginx/listen-https.conf"
echo "HTTPSCONF BEFORE: $HTTPSCONF" | tee -a $LOGFILE
cat $HTTPSCONF | tee -a $LOGFILE
if [[ ! -e /usr/local/bin/pikvm-info || ! -e /tmp/pacmanquery ]]; then
cp -f ${APP_PATH}/pikvm-info /usr/local/bin/pikvm-info
chmod +x /usr/local/bin/pikvm-info
echo "Getting list of packages installed..." | tee -a $LOGFILE
pikvm-info > /dev/null ### this generates /tmp/pacmanquery with list of installed pkgs
fi
NGINXVER=$( grep $SEARCHKEY /tmp/pacmanquery | awk '{print $1}' | cut -d'.' -f1,2 )
echo
echo "NGINX version installed: $NGINXVER" | tee -a $LOGFILE
case $NGINXVER in
1.2[56789]|1.3*|1.4*|1.5*) # nginx version 1.25 and higher
cat << NEW_CONF > $HTTPSCONF
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
NEW_CONF
;;
1.18|*) # nginx version 1.18 and lower
cat << ORIG_CONF > $HTTPSCONF
listen 443 ssl http2;
listen [::]:443 ssl;
ORIG_CONF
;;
esac
echo "HTTPSCONF AFTER: $HTTPSCONF" | tee -a $LOGFILE
cat $HTTPSCONF | tee -a $LOGFILE
set +x
} # end fix-nginx
ocr-fix() { # create function
echo
echo "-> Apply OCR fix..." | tee -a $LOGFILE
# 1. verify that Pillow module is currently running 9.0.x
PILLOWVER=$( pip3 list | grep -i pillow | awk '{print $NF}' )
case $PILLOWVER in
9.*|8.*|7.*) # Pillow running at 9.x and lower
# 2. update Pillow to 10.0.0
pip3 install -U Pillow 2>> $LOGFILE
# 3. check that Pillow module is now running 10.0.0
pip3 list | grep -i pillow | tee -a $LOGFILE
#4. restart kvmd and confirm OCR now works.
systemctl restart kvmd
;;
10.*|11.*|12.*) # Pillow running at 10.x and higher
echo "Already running Pillow $PILLOWVER. Nothing to do." | tee -a $LOGFILE
;;
esac
set +x
echo
} # end ocr-fix
x86-fix-3.256() {
echo "-> Apply x86-fix for 3.256 and higher..." | tee -a $LOGFILE
cd /usr/lib/python3/dist-packages/kvmd/apps/
cp __init__.py __init__.py.$( date +%Y%m%d )
cp -f ${APP_PATH}/patches/__init__.py __init__.py
#mv __init__.py.1 __init__.py
cd /usr/share/kvmd/web/share/js
if [ -e session.js ]; then
cp session.js session.js.$( date +%Y%m%d )
fi
cp -f ${APP_PATH}/patches/session.js session.js
if [ -e session.js.1 ]; then
mv session.js.1 session.js
fi
cd /usr/lib/python3/dist-packages/kvmd/apps/kvmd/info/
cp hw.py hw.py.$( date +%Y%m%d )
#wget --no-check-certificate https://raw.githubusercontent.com/pikvm/kvmd/cec03c4468df87bcdc68f20c2cf51a7998c56ebd/kvmd/apps/kvmd/info/hw.py 2> /dev/null
#mv hw.py.1 hw.py
cp -f ${APP_PATH}/patches/hw.py hw.py
cp -f ${APP_PATH}patches/main.yaml /etc/kvmd/
} # end x86-fix-3.256
x86-fix-3.281() {
echo "-> Apply x86-fix for 3.281 and higher..." | tee -a $LOGFILE
cd /usr/lib/python3/dist-packages/kvmd/apps/
cp __init__.py __init__.py.$( date +%Y%m%d )
cp -f ${APP_PATH}/patches/__init__.py.2 __init__.py
cd /usr/lib/python3/dist-packages/kvmd/apps/kvmd
cp -f ${APP_PATH}/patches/streamer.py.1 streamer.py
} # end x86-fix-3.281
update-logo() {
sed -i -e 's|class="svg-gray"|class="svg-color"|g' /usr/share/kvmd/web/index.html
sed -i -e 's|target="_blank"><img class="svg-gray"|target="_blank"><img class="svg-color"|g' /usr/share/kvmd/web/kvm/index.html
### download opikvm-logo.svg and then overwrite logo.svg
cp -f ${APP_PATH}/opikvm-logo.svg /usr/share/kvmd/web/share/svg/opikvm-logo.svg
cd /usr/share/kvmd/web/share/svg
cp logo.svg logo.svg.old
cp opikvm-logo.svg logo.svg
# change some text in the main html page
#sed -i.bak -e 's/The Open Source KVM over IP/KVM over IP on non-Arch linux OS by @srepac/g' /usr/share/kvmd/web/index.html
#sed -i.bak -e 's/The Open Source KVM over IP/KVM over IP on non-Arch linux OS by @srepac/g' /usr/share/kvmd/web/kvm/index.html
#sed -i.backup -e 's|https://pikvm.org/support|https://discord.gg/YaJ87sVznc|g' /usr/share/kvmd/web/kvm/index.html
#sed -i.backup -e 's|https://pikvm.org/support|https://discord.gg/YaJ87sVznc|g' /usr/share/kvmd/web/index.html
cd
}
### MAIN STARTS HERE ###
# Install is done in two parts
# First part requires a reboot in order to create kvmd users and groups
# Second part will start the necessary kvmd services
# if /etc/kvmd/htpasswd exists, then make a backup
if [ -e /etc/kvmd/htpasswd ]; then cp /etc/kvmd/htpasswd /etc/kvmd/htpasswd.save; fi
### I uploaded all these into github on 05/22/23 -- so just copy them into correct location
cd ${APP_PATH}
cp -rf pistat /usr/local/bin/pistat
cp -rf pi-temp /usr/local/bin/pi-temp
cp -rf pikvm-info /usr/local/bin/pikvm-info
chmod +x /usr/local/bin/pi*
### fix for kvmd 3.230 and higher
ln -sf python3 /usr/bin/python
SERVICES="kvmd-nginx kvmd-webterm kvmd kvmd-fix kvmd-vnc kvmd-ipmi"
# added option to re-install by adding -f parameter (for use as platform switcher)
PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )
if [[ $( grep kvmd /etc/passwd | wc -l ) -eq 0 || "$1" == "-f" ]]; then
printf "\nRunning part 1 of PiKVM installer script v$VER by @srepac and @SilentWind\n" | tee -a $LOGFILE
get-platform
get-packages
install-kvmd-pkgs
boot-files
create-override
gen-ssl-certs
fix-udevrules
install-dependencies
! $NOTCHROOT || otg-devices
armbian-packages
systemctl disable --now janus ttyd
printf "\nEnd part 1 of PiKVM installer script v$VER by @srepac and @SilentWind\n" >> $LOGFILE
printf "\nReboot is required to create kvmd users and groups.\nPlease re-run this script after reboot to complete the install.\n" | tee -a $LOGFILE
# Fix paste-as-keys if running python 3.7
if [[ $( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 ) == "3.7" ]]; then
sed -i -e 's/reversed//g' /usr/lib/python3.1*/site-packages/kvmd/keyboard/printer.py
fi
### run these to make sure kvmd users are created ###
echo "-> Ensuring KVMD users and groups ..." | tee -a $LOGFILE
systemd-sysusers /usr/lib/sysusers.d/kvmd.conf
systemd-sysusers /usr/lib/sysusers.d/kvmd-webterm.conf
# Ask user to press CTRL+C before reboot or ENTER to proceed with reboot
echo
! $NOTCHROOT || read -p "Press ENTER to continue or CTRL+C to break out of script."
! $NOTCHROOT || reboot
else
printf "\nRunning part 2 of PiKVM installer script v$VER by @srepac and @SilentWind\n" | tee -a $LOGFILE
echo "-> Re-installing janus ..." | tee -a $LOGFILE
apt reinstall -y janus > /dev/null 2>&1
### run these to make sure kvmd users are created ###
echo "-> Ensuring KVMD users and groups ..." | tee -a $LOGFILE
systemd-sysusers /usr/lib/sysusers.d/kvmd.conf
systemd-sysusers /usr/lib/sysusers.d/kvmd-webterm.conf
fix-nginx-symlinks
fix-python-symlinks
fix-webterm
fix-motd
fix-nfs-msd
fix-nginx
ocr-fix
set-ownership
create-kvmdfix
echo "-> Install python3 modules dbus_next and zstandard" | tee -a $LOGFILE
if [[ "$PYTHONVER" == "3.11" ]]; then
apt install -y python3-dbus-next python3-zstandard
else
pip3 install dbus_next zstandard
fi
### additional python pip dependencies for kvmd 3.238 and higher
case $PYTHONVER in
3.10*|3.[987]*)
pip3 install async-lru 2> /dev/null
### Fix for kvmd 3.291 -- only applies to python 3.10 ###
sed -i -e 's|gpiod.EdgeEvent|gpiod.LineEvent|g' /usr/lib/python3/dist-packages/kvmd/aiogp.py
sed -i -e 's|gpiod.line,|gpiod.Line,|g' /usr/lib/python3/dist-packages/kvmd/aiogp.py
;;
3.11*)
pip3 install async-lru --break-system-packages 2> /dev/null
;;
esac
apply-x86-mods
x86-fix-3.256
x86-fix-3.281
check-kvmd-works
enable-kvmd-svcs
update-logo
start-kvmd-svcs
printf "\nCheck kvmd devices\n\n" | tee -a $LOGFILE
ls -l /dev/kvmd* | tee -a $LOGFILE
printf "\nYou should see devices for keyboard, mouse, and video.\n" | tee -a $LOGFILE
printf "\nPoint a browser to https://$(hostname)\nIf it doesn't work, then reboot one last time.\nPlease make sure kvmd services are running after reboot.\n" | tee -a $LOGFILE
fi
cd $CWD
cp -rf web.css /etc/kvmd/web.css
systemctl status $SERVICES | grep Loaded | tee -a $LOGFILE
### fix totp.secret file permissions for use with 2FA
chmod go+r /etc/kvmd/totp.secret
chown kvmd:kvmd /etc/kvmd/totp.secret
### create rw and ro so that /usr/bin/kvmd-bootconfig doesn't fail
touch /usr/local/bin/rw /usr/local/bin/ro
chmod +x /usr/local/bin/rw /usr/local/bin/ro
### update default hostname info in webui to reflect current hostname
sed -i -e "s/localhost.localdomain/`hostname`/g" /etc/kvmd/meta.yaml
### restore htpasswd from previous install, if applies
if [ -e /etc/kvmd/htpasswd.save ]; then cp /etc/kvmd/htpasswd.save /etc/kvmd/htpasswd; fi
### instead of showing # fps dynamic, show REDACTED fps dynamic instead; USELESS fps meter fix
#sed -i -e 's|${__fps}|REDACTED|g' /usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js
### fix kvmd-webterm 0.49 change that changed ttyd to kvmd-ttyd which broke webterm
sed -i -e 's/kvmd-ttyd/ttyd/g' /lib/systemd/system/kvmd-webterm.service
# get rid of this line, otherwise kvmd-nginx won't start properly since the nginx version is not 1.25 and higher
if [ -e /etc/kvmd/nginx/nginx.conf.mako ]; then
sed -i -e '/http2 on;/d' /etc/kvmd/nginx/nginx.conf.mako
fi
systemctl restart kvmd-nginx kvmd-webterm kvmd

942
install.sh Executable file → Normal file
View File

@@ -1,179 +1,819 @@
#!/bin/bash
# https://github.com/srepac/kvmd-armbian
# modified by SilentWind 2024-06-17
# modified by xe5700 2021-11-04 xe5700@outlook.com
# modified by NewbieOrange 2021-11-04
# created by @srepac 08/09/2021 srepac@kvmnerds.com
# Scripted Installer of Pi-KVM on Armbian 32-bit and 64-bit (as long as it's running python 3.10 or higher)
#
# *** MSD is disabled by default ***
#
# Mass Storage Device requires the use of a USB thumbdrive or SSD and will need to be added in /etc/fstab
: '
# SAMPLE /etc/fstab entry for USB drive with only one partition formatted as ext4 for the entire drive:
ARCH=$(uname -m)
MACHINE=$(uname -o -s -r -m)
PYVER=$(python3 -V)
CURRENTWD=$PWD
FIND_FILE="/etc/sudoers"
FIND_STR="short_press_gpio420"
/dev/sda1 /var/lib/kvmd/msd ext4 nodev,nosuid,noexec,ro,errors=remount-ro,data=journal,X-kvmd.otgmsd-root=/var/lib/kvmd/msd,X-kvmd.otgmsd-user=kvmd 0 0
#检查架构和Python版本
check-environment(){
echo -e "\e[0;32m设备名称$MACHINE\nPython版本$PYVER"
if [ ! $ARCH = "armv7l" ]; then
echo -e "\e[0;31m暂不支持$MACHINE架构以外的设备\n退出脚本"
exit
fi
'
# NOTE: This was tested on a new install of raspbian desktop and lite versions, but should also work on an existing install.
#
# Last change 20240526 2345 PDT
VER=3.4
set +x
PIKVMREPO="https://files.pikvm.org/repos/arch/rpi4"
KVMDFILE="kvmd-3.291-1-any.pkg.tar.xz"
KVMDCACHE="/var/cache/kvmd"; mkdir -p $KVMDCACHE
PKGINFO="${KVMDCACHE}/packages.txt"
APP_PATH=$(readlink -f $(dirname $0))
LOGFILE="${KVMDCACHE}/installer.log"; touch $LOGFILE; echo "==== $( date ) ====" >> $LOGFILE
if [[ "$PYVER" != *"3.10"* && $(which python3.10) != *"python"* ]]; then
echo -e "您似乎没有安装 Python 3.10\n退出脚本\e[0;37m"
exit
else
update-alternative
fi
}
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "usage: $0 [-f] where -f will force re-install new pikvm platform"
exit 1
fi
CWD=`pwd`
WHOAMI=$( whoami )
if [ "$WHOAMI" != "root" ]; then
echo "$WHOAMI, please run script as root."
exit 1
fi
PYTHONVER=$( python3 -V | cut -d' ' -f2 | cut -d'.' -f1,2 )
case $PYTHONVER in
3.10|3.11)
echo "Python $PYTHONVER is supported." | tee -a $LOGFILE
;;
*)
echo "Python $PYTHONVER is NOT supported. Please make sure you have python3.10 or higher installed. Exiting." | tee -a $LOGFILE
exit 1
;;
esac
MAKER=$(tr -d '\0' < /proc/device-tree/model | awk '{print $1}')
gen-ssl-certs() {
cd /etc/kvmd/nginx/ssl
openssl ecparam -out server.key -name prime256v1 -genkey
openssl req -new -x509 -sha256 -nodes -key server.key -out server.crt -days 3650 \
-subj "/C=US/ST=Denial/L=Denial/O=Pi-KVM/OU=Pi-KVM/CN=$(hostname)"
cp server* /etc/kvmd/vnc/ssl/
cd ${APP_PATH}
} # end gen-ssl-certs
create-override() {
if [ $( grep ^kvmd: /etc/kvmd/override.yaml | wc -l ) -eq 0 ]; then
if [[ $( echo $platform | grep usb | wc -l ) -eq 1 ]]; then
cat <<USBOVERRIDE >> /etc/kvmd/override.yaml
kvmd:
hid:
mouse_alt:
device: /dev/kvmd-hid-mouse-alt # allow relative mouse mode
msd:
type: disabled
atx:
type: disabled
streamer:
#forever: true
cmd_append:
- "--slowdown" # so target doesn't have to reboot
resolution:
default: 1280x720
USBOVERRIDE
else
cat <<CSIOVERRIDE >> /etc/kvmd/override.yaml
kvmd:
### disable fan socket check ###
info:
fan:
unix: ''
hid:
mouse_alt:
device: /dev/kvmd-hid-mouse-alt
msd:
type: disabled
streamer:
#forever: true
cmd_append:
- "--slowdown" # so target doesn't have to reboot
CSIOVERRIDE
#使用Python3.10版本
update-alternative(){
counter=2
for i in {1..9}
do
bindir=$(which python3.$i)
if [[ $bindir == *"bin"* ]]; then
echo $i $bindir
update-alternatives --install /usr/bin/python3 python3 $bindir $counter
let counter++
fi
fi
} # end create-override
install-python-packages() {
echo "apt install -y python3-aiofiles python3-aiohttp python3-appdirs python3-asn1crypto python3-async-timeout
python3-bottle python3-cffi python3-chardet python3-click python3-colorama python3-cryptography python3-dateutil
python3-dbus python3-dev python3-hidapi python3-idna python3-libgpiod python3-mako python3-marshmallow
python3-more-itertools 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-build
python3-pyotp python3-qrcode python3-serial"
apt install -y python3-aiofiles python3-aiohttp python3-appdirs python3-asn1crypto python3-async-timeout\
python3-bottle python3-cffi python3-chardet python3-click python3-colorama python3-cryptography python3-dateutil\
python3-dbus python3-dev python3-hidapi python3-idna python3-libgpiod python3-mako python3-marshmallow\
python3-more-itertools 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-build\
python3-pyotp python3-qrcode python3-serial >> $LOGFILE
} # end install python-packages
otg-devices() {
modprobe libcomposite
if [ ! -e /sys/kernel/config/usb_gadget/kvmd ]; then
mkdir -p /sys/kernel/config/usb_gadget/kvmd/functions
cd /sys/kernel/config/usb_gadget/kvmd/functions
mkdir hid.usb0 hid.usb1 hid.usb2 mass_storage.usb0
fi
cd ${APP_PATH}
} # end otg-device creation
boot-files() {
# Remove OTG serial (Orange pi zero's kernel not support it)
sed -i '/^g_serial/d' /etc/modules
# /etc/modules required entries for DWC2, HID and I2C
if [[ $( grep -w dwc2 /etc/modules | wc -l ) -eq 0 ]]; then
echo "dwc2" >> /etc/modules
fi
if [[ $( grep -w libcomposite /etc/modules | wc -l ) -eq 0 ]]; then
echo "libcomposite" >> /etc/modules
fi
if [[ $( grep -w i2c-dev /etc/modules | wc -l ) -eq 0 ]]; then
echo "i2c-dev" >> /etc/modules
fi
printf "\n/etc/modules\n\n" | tee -a $LOGFILE
cat /etc/modules | tee -a $LOGFILE
} # end of necessary boot files
get-packages() {
printf "\n\n-> Getting Pi-KVM packages from ${PIKVMREPO}\n\n" | tee -a $LOGFILE
cp -f ${APP_PATH}/kvmd-packages/* ${KVMDCACHE}
} # end get-packages function
get-platform() {
platform="kvmd-platform-v2-hdmiusb-rpi4";
echo
echo "Platform selected -> $platform" | tee -a $LOGFILE
echo
} # end get-platform
install-kvmd-pkgs() {
cd /
INSTLOG="${KVMDCACHE}/installed_ver.txt"; rm -f $INSTLOG
date > $INSTLOG
# uncompress platform package first
i=$( ls ${KVMDCACHE}/${platform}*.tar.xz ) ### install the most up to date kvmd-platform package
# change the log entry to show 3.291 platform installed as we'll be forcing kvmd-3.291 instead of latest/greatest kvmd
_platformver=$( echo $i | sed -e 's/3\.29[2-9]*/3.291/g' -e 's/3\.3[0-9]*/3.291/g' -e 's/3.2911/3.291/g' -e 's/4\.[0-9].*-/3.291-/g' )
echo "-> Extracting package $_platformver into /" | tee -a $INSTLOG
tar xfJ $i
# then uncompress, kvmd-{version}, kvmd-webterm, and janus packages
for i in $( ls ${KVMDCACHE}/*.tar.xz | egrep 'kvmd-[0-9]|webterm' )
do
case $i in
*kvmd-3.29[2-9]*|*kvmd-3.[3-9]*|*kvmd-[45].[1-9]*) # if latest/greatest is 3.292 and higher, then force 3.291 install
echo "*** Force install kvmd 3.291 ***" | tee -a $LOGFILE
i=$KVMDCACHE/$KVMDFILE
;;
*)
;;
esac
echo "-> Extracting package $i into /" >> $INSTLOG
tar xfJ $i
done
update-alternatives --install /usr/bin/python3 python3 $(which python3.10) 1
update-alternatives --set python3 $(which python3.10)
# uncompress janus package if /usr/bin/janus doesn't exist
if [ ! -e /usr/bin/janus ]; then
i=$( ls ${KVMDCACHE}/*.tar.xz | egrep janus | grep -v 1x )
echo "-> Extracting package $i into /" >> $INSTLOG
tar xfJ $i
else # confirm that /usr/bin/janus actually runs properly
/usr/bin/janus --version > /dev/null 2>> $LOGFILE
if [ $? -eq 0 ]; then
echo "You have a working valid janus binary." | tee -a $LOGFILE
else # error status code, so uncompress from REPO package
#i=$( ls ${KVMDCACHE}/*.tar.xz | egrep janus )
#echo "-> Extracting package $i into /" >> $INSTLOG
#tar xfJ $i
apt-get remove janus janus-dev -y >> $LOGFILE
apt-get install janus janus-dev -y >> $LOGFILE
fi
fi
cd ${APP_PATH}
} # end install-kvmd-pkgs
fix-udevrules() {
# for hdmiusb, replace %b with 1-1.4:1.0 in /etc/udev/rules.d/99-kvmd.rules
sed -i -e 's+\%b+1-1.4:1.0+g' /etc/udev/rules.d/99-kvmd.rules | tee -a $LOGFILE
echo
cat /etc/udev/rules.d/99-kvmd.rules | tee -a $LOGFILE
} # end fix-udevrules
enable-kvmd-svcs() {
# enable KVMD services but don't start them
echo "-> Enabling $SERVICES services, but do not start them." | tee -a $LOGFILE
systemctl enable $SERVICES
} # end enable-kvmd-svcs
build-ustreamer() {
printf "\n\n-> Building ustreamer\n\n" | tee -a $LOGFILE
# Install packages needed for building ustreamer source
echo "apt install -y libevent-dev libjpeg-dev libbsd-dev libgpiod-dev libsystemd-dev janus-dev janus" | tee -a $LOGFILE
apt install -y libevent-dev libjpeg-dev libbsd-dev libgpiod-dev libsystemd-dev janus-dev janus >> $LOGFILE
# fix refcount.h
sed -i -e 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h
# Download ustreamer source and build it
cd /tmp
unzip ${APP_PATH}/sources/ustreamer-6.12.zip
cd ustreamer-6.12/
#添加WITH_PYTHON=1 使kvmd-vnc正常工作
make WITH_GPIO=1 WITH_SYSTEMD=1 WITH_JANUS=1 WITH_PYTHON=1 -j
#删除 --prefix=$(PREFIX) 修复无法安装pythgon包的问题
sed -i 's/--prefix=\$(PREFIX)//g' python/Makefile
make install WITH_PYTHON=1
# kvmd service is looking for /usr/bin/ustreamer
ln -sf /usr/local/bin/ustreamer* /usr/bin/
# add janus support
mkdir -p /usr/lib/ustreamer/janus
cp /tmp/ustreamer-6.12/janus/libjanus_ustreamer.so /usr/lib/ustreamer/janus
} # end build-ustreamer
install-dependencies() {
echo
echo "-> Installing dependencies for pikvm" | tee -a $LOGFILE
echo "apt install -y make nginx python3 gcc unzip net-tools bc expect v4l-utils iptables vim dos2unix screen tmate nfs-common gpiod ffmpeg dialog iptables dnsmasq git python3-pip tesseract-ocr tesseract-ocr-eng libasound2-dev libsndfile-dev libspeexdsp-dev build-essential libssl-dev libffi-dev libevent libevent-core libevent-pthreads" | tee -a $LOGFILE
apt install -y make nginx python3 gcc unzip net-tools bc expect v4l-utils iptables vim dos2unix screen tmate nfs-common gpiod ffmpeg dialog iptables dnsmasq git python3-pip tesseract-ocr tesseract-ocr-eng libasound2-dev libsndfile-dev libspeexdsp-dev build-essential libssl-dev libffi-dev libevent libevent-core libevent-pthreads >> $LOGFILE
sed -i -e 's/#port=5353/port=5353/g' /etc/dnsmasq.conf
install-python-packages
echo "-> Make tesseract data link" | tee -a $LOGFILE
ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata
echo "-> Install TTYD" | tee -a $LOGFILE
apt install -y ttyd | tee -a $LOGFILE
/usr/bin/ttyd -v | tee -a $LOGFILE
if [ ! -e /usr/local/bin/gpio ]; then
printf "\n\n-> Building wiringpi from source\n\n" | tee -a $LOGFILE
cd /tmp; rm -rf WiringPi-3.6
unzip ${APP_PATH}/sources/WiringPi-3.6.zip
cd WiringPi-3.6
./build
else
printf "\n\n-> Wiringpi (gpio) is already installed.\n\n" | tee -a $LOGFILE
fi
gpio -v | tee -a $LOGFILE
echo "-> Install ustreamer" | tee -a $LOGFILE
if [ ! -e /usr/bin/ustreamer ]; then
cd /tmp
### required dependent packages for ustreamer ###
build-ustreamer
cd ${APP_PATH}
fi
echo -n "ustreamer version: " | tee -a $LOGFILE
ustreamer -v | tee -a $LOGFILE
ustreamer --features | tee -a $LOGFILE
} # end install-dependencies
python-pkg-dir() {
# debian system python3 no alias
# create quick python script to show where python packages need to go
cat << MYSCRIPT > /tmp/syspath.py
#!$(which python3)
import sys
print (sys.path)
MYSCRIPT
chmod +x /tmp/syspath.py
#PYTHONDIR=$( /tmp/syspath.py | awk -F, '{print $NF}' | cut -d"'" -f2 )
### hardcode path for armbian/raspbian
PYTHONDIR="/usr/lib/python3/dist-packages"
} # end python-pkg-dir
fix-nginx-symlinks() {
# disable default nginx service since we will use kvmd-nginx instead
echo
echo "-> Disabling nginx service, so that we can use kvmd-nginx instead" | tee -a $LOGFILE
systemctl disable --now nginx
# setup symlinks
echo
echo "-> Creating symlinks for use with kvmd python scripts" | tee -a $LOGFILE
if [ ! -e /usr/bin/nginx ]; then ln -sf /usr/sbin/nginx /usr/bin/; fi
if [ ! -e /usr/sbin/python ]; then ln -sf /usr/bin/python3 /usr/sbin/python; fi
if [ ! -e /usr/bin/iptables ]; then ln -sf /usr/sbin/iptables /usr/bin/iptables; fi
if [ ! -e /usr/bin/vcgencmd ]; then ln -sf /opt/vc/bin/* /usr/bin/; chmod +x /opt/vc/bin/*; fi
python-pkg-dir
if [ ! -e $PYTHONDIR/kvmd ]; then
# Debian python版本比 pikvm官方的低一些
# in case new kvmd packages are now using python 3.11
ln -sf /usr/lib/python3.1*/site-packages/kvmd* ${PYTHONDIR}
fi
} # end fix-nginx-symlinks
fix-python-symlinks(){
python-pkg-dir
if [ ! -e $PYTHONDIR/kvmd ]; then
# Debian python版本比 pikvm官方的低一些
ln -sf /usr/lib/python3.1*/site-packages/kvmd* ${PYTHONDIR}
fi
}
#修改设备树文件
change-device-tree(){
cp -f ./patch/meson8b-onecloud.dtb /boot/dtb/meson8b-onecloud.dtb
echo "设备树文件覆盖成功!"
}
#覆盖引导分区
override-uboot(){
echo -e "\e[0;31m玩客云默认启用USB线刷检测是否保存原样\e[1;32mY保持原样/N关闭此功能"
read USERYN
case $USERYN in
N | n)
gzip -dc ./patch/Boot_SkipUSBBurning.gz | dd of=/dev/mmcblk1
echo -e "\e[0;30m覆盖引导成功\e[0;37m"
;;
apply-custom-patch(){
read -p "Do you want apply old kernel msd patch? [y/n]" answer
case $answer in
n|N|no|No)
echo 'You skipped this patch.'
;;
y|Y|Yes|yes)
./patches/custom/old-kernel-msd/apply.sh
;;
*)
echo -e "\e[0;30m已跳过覆盖UBoot分区\e[0;37m"
;;
echo "Try again.";;
esac
}
#安装依赖软件
install-dependencies(){
bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh) --source mirrors.tuna.tsinghua.edu.cn --updata-software false --web-protocol http && echo "换源成功!"
echo -e "\e[0;32m正在安装依赖软件patch iptables nginx tesseract-ocr tesseract-ocr-eng janus libevent-dev libgpiod-dev tesseract-ocr-chi-sim libjpeg-dev libfreetype6-dev......"
apt install -y patch iptables nginx tesseract-ocr tesseract-ocr-eng janus libevent-dev libgpiod-dev tesseract-ocr-chi-sim libjpeg-dev libfreetype6-dev
}
#安装PiKVM
install-pikvm(){
echo "正在安装PiKVM......"
dpkg -i ./fruity-pikvm_0.2_armhf.deb >> ./log.txt
systemctl enable kvmd-vnc
echo "PiKVM安装成功"
cd $CURRENTWD
cp -f ./patch/long_press_gpio420 /usr/bin && cp -f ./patch/short_press_gpio420 /usr/bin
chmod +x /usr/bin/long_press_gpio420 && chmod +x /usr/bin/short_press_gpio420
echo "GPIO-420脚本移动成功"
cp -f ./patch/hw.py /usr/local/lib/python3.10/kvmd-packages/kvmd/apps/kvmd/info/
chmod +x /usr/local/lib/python3.10/kvmd-packages/kvmd/apps/kvmd/info/hw.py
cp -f ./config/main.yaml /etc/kvmd/ && cp -f ./config/override.yaml /etc/kvmd/
echo "配置文件替换成功!"
kvmd -m
}
fix-webterm() {
echo
echo "-> Creating kvmd-webterm homedir" | tee -a $LOGFILE
mkdir -p /home/kvmd-webterm
chown kvmd-webterm /home/kvmd-webterm
ls -ld /home/kvmd-webterm | tee -a $LOGFILE
#应用补丁
add-patches(){
if [ ! -f `grep -c "$FIND_STR" $FIND_FILE` ]; then
echo kvmd ALL=\(ALL\) NOPASSWD: /usr/bin/long_press_gpio420,/usr/bin/short_press_gpio420 >> /etc/sudoers
fi
# remove -W option since ttyd installed on raspbian/armbian is 1.6.3 (-W option only works with ttyd 1.7.x)
_ttydver=$( /usr/bin/ttyd -v | awk '{print $NF}' )
case $_ttydver in
1.6*)
echo "ttyd $_ttydver found. Removing -W from /lib/systemd/system/kvmd-webterm.service"
sed -i -e '/-W \\/d' /lib/systemd/system/kvmd-webterm.service
;;
1.7*)
echo "ttyd $_ttydver found. Nothing to do."
;;
esac
if [ ! -f "/usr/local/lib/python3.10/kvmd-packages/3.198msd.patch" ]; then
cd $CURRENTWD
cp ./patch/3.198msd.patch /usr/local/lib/python3.10/kvmd-packages/ && cd /usr/local/lib/python3.10/kvmd-packages/
patch -s -p0 < 3.198msd.patch
echo "MSD补丁应用成功"
fi
# add sudoers entry for kvmd-webterm user to be able to run sudo
echo "kvmd-webterm ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/kvmd-webterm; chmod 440 /etc/sudoers.d/kvmd-webterm
} # end fix-webterm
cd $CURRENTWD
cp -f ./patch/chinese.patch /usr/share/kvmd/web/ && cd /usr/share/kvmd/web/
patch -s -p0 < chinese.patch
echo -e "\e[0;32m中文补丁应用成功"
}
show-info(){
ipaddr=`ip addr | grep "scope global" | awk '{print $2}' |awk -F/ '{print $1}'`
echo -e "\e[0;32m内网访问地址为\nhttp://$ipaddr\nhttps://$ipaddr"
echo "机器已重启等待10秒然后拔插电源One-KVM就安装完成了"
}
#配置H.264功能
kvmd-ffmpeg-h-264(){
echo "正在配置H.264功能..."
cd $CURRENTWD
apt install -y ffmpeg
#写入ffmpeg转码推流文件和janus streaming配置文件
cp -r /etc/kvmd/janus /etc/kvmd/janus2
rm /etc/kvmd/janus2/janus.plugin.ustreamer.jcfg
cat > /etc/kvmd/janus2/janus.plugin.streaming.jcfg << EOF
kvmd-ffmpeg: {
type = "rtp"
id = 1
description = "H.264 live stream coming from ustreamer"
audio = false
video = true
videoport = 5004
videopt = 96
videocodec = "h264"
videofmtp = "profile-level-id=42e01f;packetization-mode=1"
videortpmap = "H264/90000"
}
EOF
cat > /lib/systemd/system/kvmd-ffmpeg.service << EOF
create-kvmdfix() {
# Create kvmd-fix service and script
cat <<ENDSERVICE > /lib/systemd/system/kvmd-fix.service
[Unit]
Description=PiKVM - Transcode (Static Config)
After=network.target network-online.target nss-lookup.target kvmd.service
Description=KVMD Fixes
After=network.target network-online.target nss-lookup.target
Before=kvmd.service
[Service]
User=kvmd
Group=kvmd
User=root
Type=simple
Restart=on-failure
RestartSec=3
AmbientCapabilities=CAP_NET_RAW
LimitNOFILE=65536
UMask=0117
ExecStart=/usr/share/kvmd/stream_when_ustream_exists.sh
TimeoutStopSec=10
KillMode=mixed
ExecStart=/usr/bin/kvmd-fix
[Install]
WantedBy=multi-user.target
ENDSERVICE
cat <<SCRIPTEND > /usr/bin/kvmd-fix
#!/bin/bash
# Written by @srepac
# 1. Properly set group ownership of /dev/gpio*
# 2. fix /dev/kvmd-video symlink to point to /dev/video1 (Amglogic Device video0 is not usb device)
#
### These fixes are required in order for kvmd service to start properly
#
set -x
chgrp gpio /dev/gpio*
chmod 660 /dev/gpio*
ls -l /dev/gpio*
udevadm trigger
ls -l /dev/kvmd-video
if [ \$( systemctl | grep kvmd-oled | grep -c activ ) -eq 0 ]; then
echo "kvmd-oled service is not enabled."
exit 0
else
echo "kvmd-oled service is enabled and activated."
fi
### kvmd-oled fix: swap i2c-0 <-> i2c-1 (code is looking for I2C oled on i2c-1)
# pins #1 - 3.3v, #3 - SDA, #5 - SCL, and #9 - GND
i2cget -y 0 0x3c
if [ \$? -eq 0 ]; then
echo "-> Found valid I2C OLED at i2c-0. Applying I2C OLED fix."
cd /dev
# rename i2c-0 -> i2c-9, move i2c-1 to i2c-0, and rename the good i2c-9 to i2c-1
mv i2c-0 i2c-9
mv i2c-1 i2c-0
mv i2c-9 i2c-1
# restart kvmd-oled service
systemctl restart kvmd-oled
else
echo "-> I2C OLED fix already applied and OLED should be showing info."
fi
SCRIPTEND
chmod +x /usr/bin/kvmd-fix
} # end create-kvmdfix
set-ownership() {
# set proper ownership of password files and kvmd-webterm homedir
cd /etc/kvmd
chown kvmd:kvmd htpasswd
chown kvmd-ipmi:kvmd-ipmi ipmipasswd
chown kvmd-vnc:kvmd-vnc vncpasswd
chown kvmd-webterm /home/kvmd-webterm
# add kvmd user to video group (this is required in order to use CSI bridge with OMX and h264 support)
usermod -a -G video kvmd
# add kvmd user to dialout group (required for xh_hk4401 kvm switch support)
usermod -a -G dialout kvmd
} # end set-ownership
check-kvmd-works() {
echo "-> Checking kvmd -m works before continuing" | tee -a $LOGFILE
kvmd -m
invalid=1
! $NOTCHROOT || while [ $invalid -eq 1 ]; do
#kvmd -m
read -p "Did kvmd -m run properly? [y/n] " answer
case $answer in
n|N|no|No)
echo "Please install missing packages as per the kvmd -m output in another ssh/terminal."
;;
y|Y|Yes|yes)
invalid=0
;;
*)
echo "Try again.";;
esac
done
} # end check-kvmd-works
start-kvmd-svcs() {
#### start the main KVM services in order ####
# 1. nginx is the webserver
# 2. kvmd-otg is for OTG devices (keyboard/mouse, etc..)
# 3. kvmd is the main daemon
systemctl daemon-reload
systemctl restart $SERVICES
} # end start-kvmd-svcs
fix-motd() {
if [ -e /etc/motd ]; then rm /etc/motd; fi
cp armbian/armbian-motd /usr/bin/
chmod +x /usr/bin/armbian-motd
chmod +x /etc/update-motd.d/*
sed -i 's/cat \/etc\/motd/armbian-motd/g' /lib/systemd/system/kvmd-webterm.service
systemctl daemon-reload
# systemctl restart kvmd-webterm
} # end fix-motd
# 安装armbian的包
armbian-packages() {
mkdir -p /opt/vc/bin/
#cd /opt/vc/bin
if [ ! -e /usr/bin/vcgencmd ]; then
# Install vcgencmd for armbian platform
cp -rf armbian/opt/* /opt/vc/bin
else
ln -s /usr/bin/vcgencmd /opt/vc/bin/
fi
#cp -rf armbian/udev /etc/
cd ${APP_PATH}
} # end armbian-packages
fix-nfs-msd() {
NAME="aiofiles.tar"
LOCATION="/usr/lib/python3.11/site-packages"
echo "-> Extracting $NAME into $LOCATION" | tee -a $LOGFILE
tar xvf $NAME -C $LOCATION
echo "-> Renaming original aiofiles and creating symlink to correct aiofiles" | tee -a $LOGFILE
cd /usr/lib/python3/dist-packages
mv aiofiles aiofiles.$(date +%Y%m%d.%H%M)
ln -s $LOCATION/aiofiles .
ls -ld aiofiles* | tail -5
}
fix-nginx() {
#set -x
KERNEL=$( uname -r | awk -F\- '{print $1}' )
ARCH=$( uname -r | awk -F\- '{print $NF}' )
echo "KERNEL: $KERNEL ARCH: $ARCH" | tee -a $LOGFILE
case $ARCH in
ARCH) SEARCHKEY=nginx-mainline;;
*) SEARCHKEY="nginx/";;
esac
HTTPSCONF="/etc/kvmd/nginx/listen-https.conf"
echo "HTTPSCONF BEFORE: $HTTPSCONF" | tee -a $LOGFILE
cat $HTTPSCONF | tee -a $LOGFILE
if [[ ! -e /usr/local/bin/pikvm-info || ! -e /tmp/pacmanquery ]]; then
cp -f ${APP_PATH}/pikvm-info /usr/local/bin/pikvm-info
chmod +x /usr/local/bin/pikvm-info
echo "Getting list of packages installed..." | tee -a $LOGFILE
pikvm-info > /dev/null ### this generates /tmp/pacmanquery with list of installed pkgs
fi
NGINXVER=$( grep $SEARCHKEY /tmp/pacmanquery | awk '{print $1}' | cut -d'.' -f1,2 )
echo
echo "NGINX version installed: $NGINXVER" | tee -a $LOGFILE
case $NGINXVER in
1.2[56789]|1.3*|1.4*|1.5*) # nginx version 1.25 and higher
cat << NEW_CONF > $HTTPSCONF
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
NEW_CONF
;;
1.18|*) # nginx version 1.18 and lower
cat << ORIG_CONF > $HTTPSCONF
listen 443 ssl http2;
listen [::]:443 ssl;
ORIG_CONF
;;
esac
echo "HTTPSCONF AFTER: $HTTPSCONF" | tee -a $LOGFILE
cat $HTTPSCONF | tee -a $LOGFILE
set +x
} # end fix-nginx
ocr-fix() { # create function
echo
echo "-> Apply OCR fix..." | tee -a $LOGFILE
set -x
# 1. verify that Pillow module is currently running 9.0.x
PILLOWVER=$( grep -i pillow $PIP3LIST | awk '{print $NF}' )
case $PILLOWVER in
9.*|8.*|7.*) # Pillow running at 9.x and lower
# 2. update Pillow to 10.0.0
pip3 install -U Pillow 2>> $LOGFILE
# 3. check that Pillow module is now running 10.0.0
pip3 list | grep -i pillow | tee -a $LOGFILE
#4. restart kvmd and confirm OCR now works.
systemctl restart kvmd
;;
10.*|11.*|12.*) # Pillow running at 10.x and higher
echo "Already running Pillow $PILLOWVER. Nothing to do." | tee -a $LOGFILE
;;
esac
set +x
echo
} # end ocr-fix
async-lru-fix() {
echo
echo "-> Ensuring async-lru is installed with version 2.x ..." | tee -a $LOGFILE
pip3 install async-lru 2> /dev/null
PIP3LIST="/tmp/pip3.list"; /bin/rm -f $PIP3LIST
pip3 list 2> /dev/null > $PIP3LIST
ASYNCLRUVER=$( grep -i 'async[-_]lru' $PIP3LIST | awk '{print $NF}' )
echo "ASYNC-LRU version: $ASYNCLRUVER"
case $ASYNCLRUVER in
2.*) echo "Nothing to do. aync-lru is already running $ASYNCLRUVER" | tee -a $LOFILE;;
1.*|*) pip3 install -U async_lru --break-system-packages | tee -a $LOGFILE;; # raspbian bookworm only installs 1.0.x, this forces 2.0.x
esac
} # end async-lru-fix
#fix for onecloud
onecloud_conf(){
if [ "$(hostname)" == "onecloud" ]; then
pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
apt install -y cpufrequtils
echo "-->Add /etc/rc.local and skip usbburning for onecloud"
cat <<EOF >/etc/rc.local
#!/bin/bash
echo "default-on" >/sys/class/leds/onecloud\:green\:alive/trigger
echo "none" >/sys/class/leds/onecloud\:red\:alive/trigger
echo "none" >/sys/class/leds/onecloud\:blue\:alive/trigger
cpufreq-set -d 1200MHz -u 1200MHz
echo device > /sys/class/usb_role/c9040000.usb-role-switch/role
systemctl disable kvmd && systemctl stop kvmd
systemctl start kvmd
exit 0
EOF
#修改原有kvmd代码和配置文件
sed -i '17s/.*/ExecStart=\/usr\/bin\/janus --disable-colors --configs-folder=\/etc\/kvmd\/janus2/' /lib/systemd/system/kvmd-janus-static.service
sed -i 's/janus.plugin.ustreamer/janus.plugin.streaming/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
sed -i '293c \/\/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
sed -i 's/request\": \"watch\", \"p/request\": \"watch\", \"id\" : 1, \"p/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
#补全网页JS文件并添加相应脚本
mkdir /usr/share/janus/javascript/ && cp -f ./web/adapter.js /usr/share/janus/javascript/ && cp -f ./web/janus.js /usr/share/janus/javascript/
cp -f ./patch/stream.sh /usr/share/kvmd/ && cp -f ./patch/stream_when_ustream_exists.sh /usr/share/kvmd/ && chmod +x /usr/share/kvmd/stream.sh /usr/share/kvmd/stream_when_ustream_exists.sh
#启动服务
systemctl enable kvmd-ffmpeg && systemctl enable kvmd-janus-static
#systemctl start kvmd-ffmpeg && systemctl start kvmd-janus-static
sed -i '97c #' /usr/lib/python3.11/site-packages/kvmd/apps/kvmd/info/hw.py
sed -i '106c #' /usr/lib/python3.11/site-packages/kvmd/apps/kvmd/info/hw.py
cp -f ./patches/onecloud_gpio.sh /usr/bin && chmod +x /usr/bin/onecloud_gpio.sh
cp -f ${APP_PATH}/conf/override-onecloud.yaml /etc/kvmd/override.yaml
#如果在CHROOT环境需设置NOTCHROOT=false
! $NOTCHROOT || gzip -dc ${APP_PATH}/patches/Boot_SkipUSBBurning.gz | dd of=/dev/mmcblk1 bs=512 seek=1 count=32767
echo -e "\n"
if [ ! -f `grep -c "onecloud_gpio.sh" /etc/sudoers` ]; then
echo kvmd ALL=\(ALL\) NOPASSWD: /usr/bin/onecloud_gpio.sh >> /etc/sudoers
fi
fi
}
check-environment
override-uboot
change-device-tree
install-dependencies
install-pikvm
add-patches
kvmd-ffmpeg-h-264
show-info
reboot
update-logo() {
sed -i -e 's|class="svg-gray"|class="svg-color"|g' /usr/share/kvmd/web/index.html
sed -i -e 's|target="_blank"><img class="svg-gray"|target="_blank"><img class="svg-color"|g' /usr/share/kvmd/web/kvm/index.html
### download opikvm-logo.svg and then overwrite logo.svg
cp -f ${APP_PATH}/opikvm-logo.svg /usr/share/kvmd/web/share/svg/opikvm-logo.svg
cd /usr/share/kvmd/web/share/svg
cp logo.svg logo.svg.old
cp opikvm-logo.svg logo.svg
# change some text in the main html page
#sed -i.bak -e 's/The Open Source KVM over IP/KVM over IP on non-Arch linux OS by @srepac/g' /usr/share/kvmd/web/index.html
#sed -i.bak -e 's/The Open Source KVM over IP/KVM over IP on non-Arch linux OS by @srepac/g' /usr/share/kvmd/web/kvm/index.html
#sed -i.backup -e 's|https://pikvm.org/support|https://discord.gg/YaJ87sVznc|g' /usr/share/kvmd/web/kvm/index.html
#sed -i.backup -e 's|https://pikvm.org/support|https://discord.gg/YaJ87sVznc|g' /usr/share/kvmd/web/index.html
cd
}
### MAIN STARTS HERE ###
# Install is done in two parts
# First part requires a reboot in order to create kvmd users and groups
# Second part will start the necessary kvmd services
# if /etc/kvmd/htpasswd exists, then make a backup
if [ -e /etc/kvmd/htpasswd ]; then cp /etc/kvmd/htpasswd /etc/kvmd/htpasswd.save; fi
### I uploaded all these into github on 05/22/23 -- so just copy them into correct location
cd ${APP_PATH}
cp -rf pistat /usr/local/bin/pistat
cp -rf pi-temp /usr/local/bin/pi-temp
cp -rf pikvm-info /usr/local/bin/pikvm-info
chmod +x /usr/local/bin/pi*
### fix for kvmd 3.230 and higher
ln -sf python3 /usr/bin/python
SERVICES="kvmd-nginx kvmd-webterm kvmd-otg kvmd kvmd-fix kvmd-vnc kvmd-ipmi"
# added option to re-install by adding -f parameter (for use as platform switcher)
PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )
if [[ $( grep kvmd /etc/passwd | wc -l ) -eq 0 || "$1" == "-f" ]]; then
printf "\nRunning part 1 of PiKVM installer script v$VER by @srepac and @SilentWind\n" | tee -a $LOGFILE
get-platform
get-packages
install-kvmd-pkgs
boot-files
create-override
gen-ssl-certs
fix-udevrules
install-dependencies
! $NOTCHROOT || otg-devices
armbian-packages
onecloud_conf #set for onecloud
systemctl disable --now janus ttyd
printf "\nEnd part 1 of PiKVM installer script v$VER by @srepac and @SilentWind\n" >> $LOGFILE
printf "\nReboot is required to create kvmd users and groups.\nPlease re-run this script after reboot to complete the install.\n" | tee -a $LOGFILE
# Fix paste-as-keys if running python 3.7
if [[ $( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 ) == "3.7" ]]; then
sed -i -e 's/reversed//g' /usr/lib/python3.1*/site-packages/kvmd/keyboard/printer.py
fi
### run these to make sure kvmd users are created ###
echo "-> Ensuring KVMD users and groups ..." | tee -a $LOGFILE
systemd-sysusers /usr/lib/sysusers.d/kvmd.conf
systemd-sysusers /usr/lib/sysusers.d/kvmd-webterm.conf
# Ask user to press CTRL+C before reboot or ENTER to proceed with reboot
echo
! $NOTCHROOT || read -p "Press ENTER to continue or CTRL+C to break out of script."
! $NOTCHROOT || reboot
else
printf "\nRunning part 2 of PiKVM installer script v$VER by @srepac and @SilentWind\n" | tee -a $LOGFILE
echo "-> Re-installing janus ..." | tee -a $LOGFILE
apt reinstall -y janus > /dev/null 2>&1
### run these to make sure kvmd users are created ###
echo "-> Ensuring KVMD users and groups ..." | tee -a $LOGFILE
systemd-sysusers /usr/lib/sysusers.d/kvmd.conf
systemd-sysusers /usr/lib/sysusers.d/kvmd-webterm.conf
fix-nginx-symlinks
fix-python-symlinks
fix-webterm
fix-motd
fix-nfs-msd
fix-nginx
async-lru-fix
ocr-fix
set-ownership
create-kvmdfix
echo "-> Install python3 modules dbus_next and zstandard" | tee -a $LOGFILE
if [[ "$PYTHONVER" == "3.11" ]]; then
apt install -y python3-dbus-next python3-zstandard
else
pip3 install dbus_next zstandard
fi
### additional python pip dependencies for kvmd 3.238 and higher
case $PYTHONVER in
3.10*|3.[987]*)
pip3 install async-lru 2> /dev/null
### Fix for kvmd 3.291 -- only applies to python 3.10 ###
sed -i -e 's|gpiod.EdgeEvent|gpiod.LineEvent|g' /usr/lib/python3/dist-packages/kvmd/aiogp.py
sed -i -e 's|gpiod.line,|gpiod.Line,|g' /usr/lib/python3/dist-packages/kvmd/aiogp.py
;;
3.11*)
pip3 install async-lru --break-system-packages 2> /dev/null
;;
esac
check-kvmd-works
enable-kvmd-svcs
update-logo
start-kvmd-svcs
printf "\nCheck kvmd devices\n\n" | tee -a $LOGFILE
ls -l /dev/kvmd* | tee -a $LOGFILE
printf "\nYou should see devices for keyboard, mouse, and video.\n" | tee -a $LOGFILE
printf "\nPoint a browser to https://$(hostname)\nIf it doesn't work, then reboot one last time.\nPlease make sure kvmd services are running after reboot.\n" | tee -a $LOGFILE
fi
cd $CWD
cp -rf web.css /etc/kvmd/web.css
systemctl status $SERVICES | grep Loaded | tee -a $LOGFILE
### fix totp.secret file permissions for use with 2FA
chmod go+r /etc/kvmd/totp.secret
chown kvmd:kvmd /etc/kvmd/totp.secret
### create rw and ro so that /usr/bin/kvmd-bootconfig doesn't fail
touch /usr/local/bin/rw /usr/local/bin/ro
chmod +x /usr/local/bin/rw /usr/local/bin/ro
### update default hostname info in webui to reflect current hostname
sed -i -e "s/localhost.localdomain/`hostname`/g" /etc/kvmd/meta.yaml
### restore htpasswd from previous install, if applies
if [ -e /etc/kvmd/htpasswd.save ]; then cp /etc/kvmd/htpasswd.save /etc/kvmd/htpasswd; fi
### instead of showing # fps dynamic, show REDACTED fps dynamic instead; USELESS fps meter fix
#sed -i -e 's|${__fps}|REDACTED|g' /usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js
### fix kvmd-webterm 0.49 change that changed ttyd to kvmd-ttyd which broke webterm
sed -i -e 's/kvmd-ttyd/ttyd/g' /lib/systemd/system/kvmd-webterm.service
# get rid of this line, otherwise kvmd-nginx won't start properly since the nginx version is not 1.25 and higher
if [ -e /etc/kvmd/nginx/nginx.conf.mako ]; then
sed -i -e '/http2 on;/d' /etc/kvmd/nginx/nginx.conf.mako
fi
systemctl restart kvmd-nginx kvmd-webterm kvmd

Binary file not shown.

Binary file not shown.

Binary file not shown.

39
kvmd_display_install.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
CURRENTWD=$PWD
#配置采集卡简单环出功能
kvmd_display(){
echo "正在配置采集卡简单环出功能功能..."
cd $CURRENTWD
apt install -y ffmpeg
cat > /lib/systemd/system/kvmd-display.service << EOF
[Unit]
Description=PiKVM - Transcode (Static Config)
After=network.target network-online.target nss-lookup.target kvmd.service
[Service]
User=kvmd
Group=kvmd
Type=simple
Restart=on-failure
RestartSec=3
AmbientCapabilities=CAP_NET_RAW
LimitNOFILE=65536
UMask=0117
ExecStart=/usr/share/kvmd/display.sh
TimeoutStopSec=10
KillMode=mixed
[Install]
WantedBy=multi-user.target
EOF
cp -f ./patches/display.sh /usr/share/kvmd/ && chmod +x /usr/share/kvmd/display.sh
#启动服务
systemctl daemon-reload
! $NOTCHROOT || systemctl enable kvmd-display
! $NOTCHROOT || systemctl start kvmd-display
echo "配置完成"
}
kvmd_display

67
kvmd_h264_install.sh Normal file
View File

@@ -0,0 +1,67 @@
#!/bin/bash
CURRENTWD=$PWD
#配置H.264功能
kvmd_ffmpeg_h-264(){
echo "正在配置H.264功能..."
cd $CURRENTWD
apt install -y ffmpeg
#写入ffmpeg转码推流文件和janus streaming配置文件
cp -r /etc/kvmd/janus /etc/kvmd/janus2
rm /etc/kvmd/janus2/janus.plugin.ustreamer.jcfg
cat > /etc/kvmd/janus2/janus.plugin.streaming.jcfg << EOF
kvmd-ffmpeg: {
type = "rtp"
id = 1
description = "H.264 live stream coming from ustreamer"
audio = false
video = true
videoport = 5004
videopt = 96
videocodec = "h264"
videofmtp = "profile-level-id=42e01f;packetization-mode=1"
videortpmap = "H264/90000"
}
EOF
cat > /lib/systemd/system/kvmd-ffmpeg.service << EOF
[Unit]
Description=PiKVM - Transcode (Static Config)
After=network.target network-online.target nss-lookup.target kvmd.service
[Service]
User=kvmd
Group=kvmd
Type=simple
Restart=on-failure
RestartSec=3
AmbientCapabilities=CAP_NET_RAW
LimitNOFILE=65536
UMask=0117
ExecStart=/usr/share/kvmd/stream_when_ustream_exists.sh
TimeoutStopSec=10
KillMode=mixed
[Install]
WantedBy=multi-user.target
EOF
#修改原有kvmd代码和配置文件
sed -i '17s/.*/ExecStart=\/usr\/bin\/janus --disable-colors --configs-folder=\/etc\/kvmd\/janus2/' /lib/systemd/system/kvmd-janus-static.service
sed -i 's/janus.plugin.ustreamer/janus.plugin.streaming/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
sed -i '324c \/\/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
sed -i 's/request\": \"watch\", \"p/request\": \"watch\", \"id\" : 1, \"p/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
#补全网页JS文件并添加相应脚本
if [ ! -e /usr/share/janus/javascript/adapter.js ]; then
mkdir /usr/share/janus/javascript/
cp -f ./patches/adapter.js /usr/share/janus/javascript/ && cp -f ./patches/janus.js /usr/share/janus/javascript/
fi
cp -f ./patches/stream.sh /usr/share/kvmd/ && cp -f ./patches/stream_when_ustream_exists.sh /usr/share/kvmd/ && chmod +x /usr/share/kvmd/stream.sh /usr/share/kvmd/stream_when_ustream_exists.sh
#启动服务
systemctl daemon-reload
! $NOTCHROOT || systemctl enable kvmd-ffmpeg && systemctl enable kvmd-janus-static
! $NOTCHROOT || systemctl start kvmd-ffmpeg && systemctl start kvmd-janus-static
echo "配置完成"
}
kvmd_ffmpeg_h-264

49
opikvm-logo.svg Normal file
View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by Microsoft Visio, SVG Export opikvm-logo.svg Page-1 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.44656in" height="1.88949in"
viewBox="0 0 464.153 136.043" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
<v:documentProperties v:langID="1033" v:viewMarkup="false"/>
<style type="text/css">
<![CDATA[
.st1 {fill:#dc7a0e;stroke:none;stroke-linecap:butt;stroke-width:0.0283465}
.st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
]]>
</style>
<g v:mID="0" v:index="1" v:groupContext="foregroundPage">
<title>Page-1</title>
<v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
<g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(0.0283465,-0.0283465)">
<title>path7</title>
<path d="M12.87 0.06 L451.23 0.06 C458.31 0.06 464.1 5.85 464.1 12.92 L464.1 123.18 C464.1 130.25 458.31 136.04 451.23
136.04 L12.87 136.04 C5.79 136.04 0 130.25 0 123.18 L0 12.92 C0 5.85 5.79 0.06 12.87 0.06 ZM203.52 64.5
L240.18 110.34 C240.88 111.21 241.03 112.33 240.56 113.35 C240.08 114.36 239.11 114.97 237.99 114.97 L214.83
114.97 C213.95 114.97 213.18 114.62 212.64 113.93 L204.76 103.99 L178.52 70.87 L178.52 112.14 C178.52 113.7
177.24 114.97 175.68 114.97 L157.86 114.97 C156.3 114.97 155.02 113.7 155.02 112.14 L155.02 23.96 C155.02
22.4 156.3 21.13 157.86 21.13 L175.68 21.13 C177.24 21.13 178.52 22.4 178.52 23.96 L178.52 62.1 L205.35
28.23 L210.15 22.18 C210.7 21.49 211.47 21.13 212.35 21.13 L232.26 21.13 C233.38 21.13 234.34 21.74 234.82
22.76 C235.3 23.77 235.14 24.89 234.44 25.77 L203.52 64.5 ZM245.12 30.92 L251.29 38.69 L282.84 78.38 L316.61
37.11 L328.71 22.31 C329.49 21.36 330.72 21.03 331.87 21.44 C333.02 21.86 333.74 22.91 333.74 24.13 L333.75
52.44 C333.75 53.11 333.53 53.7 333.11 54.22 L285.14 112.93 C284.58 113.61 283.81 113.98 282.93 113.97 C282.04
113.97 281.28 113.6 280.73 112.91 L229.61 48.81 C228.79 47.78 228.77 46.35 229.58 45.31 L240.66 30.95 C241.21
30.24 241.98 29.86 242.88 29.85 C243.78 29.85 244.56 30.22 245.12 30.92 ZM117.3 112.14 L117.3 56.32 C117.3
54.76 118.57 53.49 120.13 53.49 L137.96 53.49 C139.52 53.49 140.79 54.76 140.79 56.33 C140.79 96.9 140.79
73.79 140.79 112.14 C140.79 113.7 139.52 114.97 137.96 114.97 L120.13 114.97 C118.57 114.97 117.3 113.7
117.3 112.14 ZM120.13 21.13 L137.96 21.13 C139.52 21.13 140.79 22.4 140.79 23.96 L140.79 41.79 C140.79 43.35
139.52 44.62 137.96 44.62 C125.46 44.62 131.82 44.62 120.13 44.62 C118.57 44.62 117.3 43.35 117.3 41.79
L117.3 23.96 C117.3 22.4 118.57 21.13 120.13 21.13 ZM52.71 83.71 L52.71 112.14 C52.71 113.7 51.44 114.97
49.88 114.97 L31.91 114.97 C30.35 114.97 29.08 113.7 29.08 112.14 L29.08 23.96 C29.08 22.4 30.35 21.13 31.91
21.13 C52.01 21.13 74.47 21.13 94.57 21.13 C99.25 21.13 103.07 24.95 103.07 29.63 C103.07 46.41 103.07 58.43
103.07 75.2 C103.07 79.89 99.25 83.71 94.57 83.71 C81.4 83.71 65.88 83.71 52.71 83.71 ZM79.57 65.45 L79.57
38.58 L52.71 38.58 L52.71 65.45 L79.57 65.45 ZM342.63 112.14 L342.63 23.96 C342.63 22.4 343.9 21.13 345.47
21.13 L361.99 21.13 C362.88 21.13 363.65 21.5 364.2 22.2 L388.85 53.12 L413.5 22.2 C414.06 21.5 414.83 21.13
415.72 21.13 L432.24 21.13 C433.8 21.13 435.08 22.4 435.08 23.96 L435.08 112.14 C435.08 113.7 433.8 114.97
432.24 114.97 L414.69 114.97 C413.13 114.97 411.86 113.7 411.86 112.14 L411.86 56.9 L391.06 82.73 C390.5
83.42 389.74 83.79 388.85 83.79 C387.97 83.79 387.2 83.42 386.65 82.73 L365.85 56.9 L365.85 112.14 C365.85
113.7 364.58 114.97 363.01 114.97 L345.47 114.97 C343.9 114.97 342.63 113.7 342.63 112.14 Z" class="st1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
#!/bin/bash
echo 420 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio420/direction
echo 0 > /sys/class/gpio/gpio420/value
sleep 5
echo 1 > /sys/class/gpio/gpio420/value
echo 420 > /sys/class/gpio/unexport

Binary file not shown.

View File

@@ -1,7 +0,0 @@
#!/bin/bash
echo 420 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio420/direction
echo 0 > /sys/class/gpio/gpio420/value
sleep 0.5
echo 1 > /sys/class/gpio/gpio420/value
echo 420 > /sys/class/gpio/unexport

Binary file not shown.

769
patches/__init__.py Normal file
View File

@@ -0,0 +1,769 @@
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.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 os
import functools
import argparse
import logging
import logging.config
import pygments
import pygments.lexers.data
import pygments.formatters
from .. import tools
from ..mouse import MouseRange
from ..plugins import UnknownPluginError
from ..plugins.auth import get_auth_service_class
from ..plugins.hid import get_hid_class
from ..plugins.atx import get_atx_class
from ..plugins.msd import get_msd_class
from ..plugins.ugpio import UserGpioModes
from ..plugins.ugpio import BaseUserGpioDriver
from ..plugins.ugpio import get_ugpio_driver_class
from ..yamlconf import ConfigError
from ..yamlconf import manual_validated
from ..yamlconf import make_config
from ..yamlconf import Section
from ..yamlconf import Option
from ..yamlconf import build_raw_from_options
from ..yamlconf.dumper import make_config_dump
from ..yamlconf.loader import load_yaml_file
from ..yamlconf.merger import yaml_merge
from ..validators.basic import valid_stripped_string
from ..validators.basic import valid_stripped_string_not_empty
from ..validators.basic import valid_bool
from ..validators.basic import valid_number
from ..validators.basic import valid_int_f0
from ..validators.basic import valid_int_f1
from ..validators.basic import valid_float_f0
from ..validators.basic import valid_float_f01
from ..validators.basic import valid_string_list
from ..validators.auth import valid_user
from ..validators.auth import valid_users_list
from ..validators.os import valid_abs_path
from ..validators.os import valid_abs_file
from ..validators.os import valid_abs_dir
from ..validators.os import valid_unix_mode
from ..validators.os import valid_options
from ..validators.os import valid_command
from ..validators.net import valid_ip_or_host
from ..validators.net import valid_net
from ..validators.net import valid_port
from ..validators.net import valid_ports_list
from ..validators.net import valid_mac
from ..validators.net import valid_ssl_ciphers
from ..validators.hid import valid_hid_key
from ..validators.hid import valid_hid_mouse_output
from ..validators.hid import valid_hid_mouse_move
from ..validators.kvm import valid_stream_quality
from ..validators.kvm import valid_stream_fps
from ..validators.kvm import valid_stream_resolution
from ..validators.kvm import valid_stream_h264_bitrate
from ..validators.kvm import valid_stream_h264_gop
from ..validators.ugpio import valid_ugpio_driver
from ..validators.ugpio import valid_ugpio_channel
from ..validators.ugpio import valid_ugpio_mode
from ..validators.ugpio import valid_ugpio_view_title
from ..validators.ugpio import valid_ugpio_view_table
from ..validators.hw import valid_tty_speed
from ..validators.hw import valid_otg_gadget
from ..validators.hw import valid_otg_id
from ..validators.hw import valid_otg_ethernet
# =====
def init(
prog: (str | None)=None,
description: (str | None)=None,
add_help: bool=True,
check_run: bool=False,
cli_logging: bool=False,
argv: (list[str] | None)=None,
**load: bool,
) -> tuple[argparse.ArgumentParser, list[str], Section]:
argv = (argv or sys.argv)
assert len(argv) > 0
parser = argparse.ArgumentParser(
prog=(prog or argv[0]),
description=description,
add_help=add_help,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file,
help="Set config file path", metavar="<file>")
parser.add_argument("-o", "--set-options", default=[], nargs="+",
help="Override config options list (like sec/sub/opt=value)", metavar="<k=v>",)
parser.add_argument("-m", "--dump-config", action="store_true",
help="View current configuration (include all overrides)")
if check_run:
parser.add_argument("--run", dest="run", action="store_true",
help="Run the service")
(options, remaining) = parser.parse_known_args(argv)
if options.dump_config:
_dump_config(_init_config(
config_path=options.config,
override_options=options.set_options,
load_auth=True,
load_hid=True,
load_atx=True,
load_msd=True,
load_gpio=True,
))
raise SystemExit()
config = _init_config(options.config, options.set_options, **load)
logging.captureWarnings(True)
logging.config.dictConfig(config.logging)
if cli_logging:
logging.getLogger().handlers[0].setFormatter(logging.Formatter(
"-- {levelname:>7} -- {message}",
style="{",
))
if check_run and not options.run:
raise SystemExit(
"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)
# =====
def _init_config(config_path: str, override_options: list[str], **load_flags: bool) -> Section:
config_path = os.path.expanduser(config_path)
try:
raw_config: dict = load_yaml_file(config_path)
except Exception as err:
raise SystemExit(f"ConfigError: Can't read config file {config_path!r}:\n{tools.efmt(err)}")
if not isinstance(raw_config, dict):
raise SystemExit(f"ConfigError: Top-level of the file {config_path!r} must be a dictionary")
scheme = _get_config_scheme()
try:
yaml_merge(raw_config, (raw_config.pop("override", {}) or {}))
yaml_merge(raw_config, build_raw_from_options(override_options), "raw CLI options")
_patch_raw(raw_config)
config = make_config(raw_config, scheme)
if _patch_dynamic(raw_config, config, scheme, **load_flags):
config = make_config(raw_config, scheme)
return config
except (ConfigError, UnknownPluginError) as err:
raise SystemExit(f"ConfigError: {err}")
def _patch_raw(raw_config: dict) -> None: # pylint: disable=too-many-branches
if isinstance(raw_config.get("otg"), dict):
for (old, new) in [
("msd", "msd"),
("acm", "serial"),
("drives", "drives"),
]:
if old in raw_config["otg"]:
if not isinstance(raw_config["otg"].get("devices"), dict):
raw_config["otg"]["devices"] = {}
raw_config["otg"]["devices"][new] = raw_config["otg"].pop(old)
if isinstance(raw_config.get("kvmd"), dict) and isinstance(raw_config["kvmd"].get("wol"), dict):
if not isinstance(raw_config["kvmd"].get("gpio"), dict):
raw_config["kvmd"]["gpio"] = {}
for section in ["drivers", "scheme"]:
if not isinstance(raw_config["kvmd"]["gpio"].get(section), dict):
raw_config["kvmd"]["gpio"][section] = {}
raw_config["kvmd"]["gpio"]["drivers"]["__wol__"] = {
"type": "wol",
**raw_config["kvmd"].pop("wol"),
}
raw_config["kvmd"]["gpio"]["scheme"]["__wol__"] = {
"driver": "__wol__",
"pin": 0,
"mode": "output",
"switch": False,
}
if isinstance(raw_config.get("kvmd"), dict) and isinstance(raw_config["kvmd"].get("streamer"), dict):
streamer_config = raw_config["kvmd"]["streamer"]
desired_fps = streamer_config.get("desired_fps")
if desired_fps is not None and not isinstance(desired_fps, dict):
streamer_config["desired_fps"] = {"default": desired_fps}
max_fps = streamer_config.get("max_fps")
if max_fps is not None:
if not isinstance(streamer_config.get("desired_fps"), dict):
streamer_config["desired_fps"] = {}
streamer_config["desired_fps"]["max"] = max_fps
del streamer_config["max_fps"]
resolution = streamer_config.get("resolution")
if resolution is not None and not isinstance(resolution, dict):
streamer_config["resolution"] = {"default": resolution}
available_resolutions = streamer_config.get("available_resolutions")
if available_resolutions is not None:
if not isinstance(streamer_config.get("resolution"), dict):
streamer_config["resolution"] = {}
streamer_config["resolution"]["available"] = available_resolutions
del streamer_config["available_resolutions"]
def _patch_dynamic( # pylint: disable=too-many-locals
raw_config: dict,
config: Section,
scheme: dict,
load_auth: bool=False,
load_hid: bool=False,
load_atx: bool=False,
load_msd: bool=False,
load_gpio: bool=False,
) -> bool:
rebuild = False
if load_auth:
scheme["kvmd"]["auth"]["internal"].update(get_auth_service_class(config.kvmd.auth.internal.type).get_plugin_options())
if config.kvmd.auth.external.type:
scheme["kvmd"]["auth"]["external"].update(get_auth_service_class(config.kvmd.auth.external.type).get_plugin_options())
rebuild = True
for (load, section, get_class) in [
(load_hid, "hid", get_hid_class),
(load_atx, "atx", get_atx_class),
(load_msd, "msd", get_msd_class),
]:
if load:
scheme["kvmd"][section].update(get_class(getattr(config.kvmd, section).type).get_plugin_options())
rebuild = True
if load_gpio:
driver: str
drivers: dict[str, type[BaseUserGpioDriver]] = {} # Name to drivers
for (driver, params) in { # type: ignore
"__gpio__": {},
**tools.rget(raw_config, "kvmd", "gpio", "drivers"),
}.items():
with manual_validated(driver, "kvmd", "gpio", "drivers", "<key>"):
driver = valid_ugpio_driver(driver)
driver_type = valid_stripped_string_not_empty(params.get("type", "gpio"))
driver_class = get_ugpio_driver_class(driver_type)
drivers[driver] = driver_class
scheme["kvmd"]["gpio"]["drivers"][driver] = {
"type": Option(driver_type, type=valid_stripped_string_not_empty),
**driver_class.get_plugin_options()
}
path = ("kvmd", "gpio", "scheme")
for (channel, params) in tools.rget(raw_config, *path).items():
with manual_validated(channel, *path, "<key>"):
channel = valid_ugpio_channel(channel)
driver = params.get("driver", "__gpio__")
with manual_validated(driver, *path, channel, "driver"):
driver = valid_ugpio_driver(driver, set(drivers))
mode: str = params.get("mode", "")
with manual_validated(mode, *path, channel, "mode"):
mode = valid_ugpio_mode(mode, drivers[driver].get_modes())
if params.get("pulse") == False: # noqa: E712 # pylint: disable=singleton-comparison
params["pulse"] = {"delay": 0}
scheme["kvmd"]["gpio"]["scheme"][channel] = {
"driver": Option("__gpio__", type=functools.partial(valid_ugpio_driver, variants=set(drivers))),
"pin": Option(None, type=drivers[driver].get_pin_validator()),
"mode": Option("", type=functools.partial(valid_ugpio_mode, variants=drivers[driver].get_modes())),
"inverted": Option(False, type=valid_bool),
**({
"busy_delay": Option(0.2, type=valid_float_f01),
"initial": Option(False, type=(lambda arg: (valid_bool(arg) if arg is not None else None))),
"switch": Option(True, type=valid_bool),
"pulse": { # type: ignore
"delay": Option(0.1, type=valid_float_f0),
"min_delay": Option(0.1, type=valid_float_f01),
"max_delay": Option(0.1, type=valid_float_f01),
},
} if mode == UserGpioModes.OUTPUT else { # input
"debounce": Option(0.1, type=valid_float_f0),
})
}
rebuild = True
return rebuild
def _dump_config(config: Section) -> None:
dump = make_config_dump(config)
if sys.stdout.isatty():
dump = pygments.highlight(
dump,
pygments.lexers.data.YamlLexer(),
pygments.formatters.TerminalFormatter(bg="dark"), # pylint: disable=no-member
)
print(dump)
def _get_config_scheme() -> dict:
return {
"logging": Option({}),
"kvmd": {
"server": {
"unix": Option("/run/kvmd/kvmd.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'"),
},
"auth": {
"enabled": Option(True, type=valid_bool),
"internal": {
"type": Option("htpasswd"),
"force_users": Option([], type=valid_users_list),
# Dynamic content
},
"external": {
"type": Option("", type=valid_stripped_string),
# Dynamic content
},
"totp": {
"secret": {
"file": Option("/etc/kvmd/totp.secret", type=valid_abs_path, if_empty=""),
},
},
},
"info": { # Accessed via global config, see kvmd/info for details
"meta": Option("/etc/kvmd/meta.yaml", type=valid_abs_file),
"extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir),
"hw": {
"vcgencmd_cmd": Option(["/opt/vc/bin/vcgencmd"], type=valid_command),
"ignore_past": Option(False, type=valid_bool),
"state_poll": Option(10.0, type=valid_float_f01),
},
"fan": {
"daemon": Option("kvmd-fan", type=valid_stripped_string),
"unix": Option("", type=valid_abs_path, if_empty="", unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
"state_poll": Option(5.0, type=valid_float_f01),
},
},
"log_reader": {
"enabled": Option(True, type=valid_bool),
},
"prometheus": {
"auth": {
"enabled": Option(True, type=valid_bool),
},
},
"hid": {
"type": Option("", type=valid_stripped_string_not_empty),
"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
},
"atx": {
"type": Option("", type=valid_stripped_string_not_empty),
# Dynamic content
},
"msd": {
"type": Option("", type=valid_stripped_string_not_empty),
# Dynamic content
},
"streamer": {
"forever": Option(False, type=valid_bool),
"reset_delay": Option(1.0, type=valid_float_f0),
"shutdown_delay": Option(10.0, type=valid_float_f01),
"state_poll": Option(1.0, type=valid_float_f01),
"quality": Option(80, type=valid_stream_quality, if_empty=0),
"resolution": {
"default": Option("", type=valid_stream_resolution, if_empty="", unpack_as="resolution"),
"available": Option(
[],
type=functools.partial(valid_string_list, subval=valid_stream_resolution),
unpack_as="available_resolutions",
),
},
"desired_fps": {
"default": Option(40, type=valid_stream_fps, unpack_as="desired_fps"),
"min": Option(0, type=valid_stream_fps, unpack_as="desired_fps_min"),
"max": Option(70, type=valid_stream_fps, unpack_as="desired_fps_max"),
},
"h264_bitrate": {
"default": Option(0, type=valid_stream_h264_bitrate, if_empty=0, unpack_as="h264_bitrate"),
"min": Option(25, type=valid_stream_h264_bitrate, unpack_as="h264_bitrate_min"),
"max": Option(20000, type=valid_stream_h264_bitrate, unpack_as="h264_bitrate_max"),
},
"h264_gop": {
"default": Option(30, type=valid_stream_h264_gop, unpack_as="h264_gop"),
"min": Option(0, type=valid_stream_h264_gop, unpack_as="h264_gop_min"),
"max": Option(60, type=valid_stream_h264_gop, unpack_as="h264_gop_max"),
},
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(2.0, type=valid_float_f01),
"process_name_prefix": Option("kvmd/streamer"),
"cmd": Option(["/bin/true"], type=valid_command),
"cmd_remove": Option([], type=valid_options),
"cmd_append": Option([], type=valid_options),
},
"ocr": {
"langs": Option(["eng"], type=valid_string_list, unpack_as="default_langs"),
"tessdata": Option("/usr/share/tessdata", type=valid_stripped_string_not_empty, unpack_as="data_dir_path")
},
"snapshot": {
"idle_interval": Option(0.0, type=valid_float_f0),
"live_interval": Option(0.0, type=valid_float_f0),
"wakeup_key": Option("", type=valid_hid_key, if_empty=""),
"wakeup_move": Option(0, type=valid_hid_mouse_move),
"online_delay": Option(5.0, type=valid_float_f0),
"retries": Option(10, type=valid_int_f1),
"retries_delay": Option(3.0, type=valid_float_f01),
},
"gpio": {
"state_poll": Option(0.1, type=valid_float_f01),
"drivers": {}, # Dynamic content
"scheme": {}, # Dymanic content
"view": {
"header": {
"title": Option("GPIO", type=valid_ugpio_view_title),
},
"table": Option([], type=valid_ugpio_view_table),
},
},
},
"pst": {
"server": {
"unix": Option("/run/kvmd/pst.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'"),
},
"ro_retries_delay": Option(10.0, type=valid_float_f01),
"ro_cleanup_delay": Option(3.0, type=valid_float_f01),
"remount_cmd": Option([
"/usr/bin/sudo", "--non-interactive",
"/usr/bin/kvmd-helper-pst-remount", "{mode}",
], type=valid_command),
},
"otg": {
"vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation
"product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget
"manufacturer": Option("PiKVM", type=valid_stripped_string),
"product": Option("Composite KVM Device", type=valid_stripped_string),
"serial": Option("CAFEBABE", type=valid_stripped_string, if_none=None),
"device_version": Option(-1, type=functools.partial(valid_number, min=-1, max=0xFFFF)),
"usb_version": Option(0x0200, type=valid_otg_id),
"max_power": Option(250, type=functools.partial(valid_number, min=50, max=500)),
"remote_wakeup": Option(False, type=valid_bool),
"gadget": Option("kvmd", type=valid_otg_gadget),
"config": Option("PiKVM device", type=valid_stripped_string_not_empty),
"udc": Option("", type=valid_stripped_string),
"init_delay": Option(3.0, type=valid_float_f01),
"user": Option("kvmd", type=valid_user),
"meta": Option("/run/kvmd/otg", type=valid_abs_path),
"devices": {
"hid": {
"keyboard": {
"start": Option(True, type=valid_bool),
},
"mouse": {
"start": Option(True, type=valid_bool),
},
},
"msd": {
"start": Option(True, type=valid_bool),
"default": {
"stall": Option(False, type=valid_bool),
"cdrom": Option(True, type=valid_bool),
"rw": Option(False, type=valid_bool),
"removable": Option(True, type=valid_bool),
"fua": Option(True, type=valid_bool),
},
},
"serial": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
},
"ethernet": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
"driver": Option("ecm", type=valid_otg_ethernet),
"host_mac": Option("", type=valid_mac, if_empty=""),
"kvm_mac": Option("", type=valid_mac, if_empty=""),
},
"drives": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
"count": Option(1, type=valid_int_f1),
"default": {
"stall": Option(False, type=valid_bool),
"cdrom": Option(False, type=valid_bool),
"rw": Option(True, type=valid_bool),
"removable": Option(True, type=valid_bool),
"fua": Option(True, type=valid_bool),
},
},
},
},
"otgnet": {
"iface": {
"net": Option("172.30.30.0/24", type=functools.partial(valid_net, v6=False)),
"ip_cmd": Option(["/usr/bin/ip"], type=valid_command),
},
"firewall": {
"allow_icmp": Option(True, type=valid_bool),
"allow_tcp": Option([], type=valid_ports_list),
"allow_udp": Option([67], type=valid_ports_list),
"forward_iface": Option("", type=valid_stripped_string),
"iptables_cmd": Option(["/usr/sbin/iptables", "--wait=5"], type=valid_command),
},
"commands": {
"pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command),
"pre_start_cmd_remove": Option([], type=valid_options),
"pre_start_cmd_append": Option([], type=valid_options),
"post_start_cmd": Option([
"/usr/bin/systemd-run",
"--unit=kvmd-otgnet-dnsmasq",
"/usr/sbin/dnsmasq",
"--conf-file=/dev/null",
"--pid-file",
"--user=dnsmasq",
"--interface={iface}",
"--port=0",
"--dhcp-range={dhcp_ip_begin},{dhcp_ip_end},24h",
"--dhcp-leasefile=/run/kvmd/dnsmasq.lease",
"--dhcp-option={dhcp_option_3}",
"--dhcp-option=6",
"--keep-in-foreground",
], type=valid_command),
"post_start_cmd_remove": Option([], type=valid_options),
"post_start_cmd_append": Option([], type=valid_options),
"pre_stop_cmd": Option([
"/usr/bin/systemctl",
"stop",
"kvmd-otgnet-dnsmasq",
], type=valid_command),
"pre_stop_cmd_remove": Option([], type=valid_options),
"pre_stop_cmd_append": Option([], type=valid_options),
"post_stop_cmd": Option(["/bin/true", "post-stop"], type=valid_command),
"post_stop_cmd_remove": Option([], type=valid_options),
"post_stop_cmd_append": Option([], type=valid_options),
},
},
"ipmi": {
"server": {
"host": Option("::", type=valid_ip_or_host),
"port": Option(623, type=valid_port),
"timeout": Option(10.0, type=valid_float_f01),
},
"kvmd": {
"unix": Option("/run/kvmd/kvmd.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
"auth": {
"file": Option("/etc/kvmd/ipmipasswd", type=valid_abs_file, unpack_as="path"),
},
"sol": {
"device": Option("", type=valid_abs_path, if_empty="", unpack_as="sol_device_path"),
"speed": Option(115200, type=valid_tty_speed, unpack_as="sol_speed"),
"select_timeout": Option(0.1, type=valid_float_f01, unpack_as="sol_select_timeout"),
"proxy_port": Option(0, type=valid_port, unpack_as="sol_proxy_port"),
},
},
"vnc": {
"desired_fps": Option(30, type=valid_stream_fps),
"mouse_output": Option("usb", type=valid_hid_mouse_output),
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
"server": {
"host": Option("::", type=valid_ip_or_host),
"port": Option(5900, type=valid_port),
"max_clients": Option(10, type=valid_int_f1),
"no_delay": Option(True, type=valid_bool),
"keepalive": {
"enabled": Option(True, type=valid_bool, unpack_as="keepalive_enabled"),
"idle": Option(10, type=functools.partial(valid_number, min=1, max=3600), unpack_as="keepalive_idle"),
"interval": Option(3, type=functools.partial(valid_number, min=1, max=60), unpack_as="keepalive_interval"),
"count": Option(3, type=functools.partial(valid_number, min=1, max=10), unpack_as="keepalive_count"),
},
"tls": {
"ciphers": Option("ALL:@SECLEVEL=0", type=valid_ssl_ciphers, if_empty=""),
"timeout": Option(30.0, type=valid_float_f01),
"x509": {
"cert": Option("/etc/kvmd/vnc/ssl/server.crt", type=valid_abs_file, if_empty=""),
"key": Option("/etc/kvmd/vnc/ssl/server.key", type=valid_abs_file, if_empty=""),
},
},
},
"kvmd": {
"unix": Option("/run/kvmd/kvmd.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
"streamer": {
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
"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(1.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),
},
},
"auth": {
"vncauth": {
"enabled": Option(False, type=valid_bool),
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="path"),
},
"vencrypt": {
"enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"),
},
},
},
"janus": {
"stun": {
"host": Option("stun.l.google.com", type=valid_ip_or_host, unpack_as="stun_host"),
"port": Option(19302, type=valid_port, unpack_as="stun_port"),
"timeout": Option(5.0, type=valid_float_f01, unpack_as="stun_timeout"),
"retries": Option(5, type=valid_int_f1, unpack_as="stun_retries"),
"retries_delay": Option(5.0, type=valid_float_f01, unpack_as="stun_retries_delay"),
},
"check": {
"interval": Option(10.0, type=valid_float_f01, unpack_as="check_interval"),
"retries": Option(5, type=valid_int_f1, unpack_as="check_retries"),
"retries_delay": Option(5.0, type=valid_float_f01, unpack_as="check_retries_delay"),
},
"cmd": Option([
"/usr/bin/janus",
"--disable-colors",
"--plugins-folder=/usr/lib/ustreamer/janus",
"--configs-folder=/etc/kvmd/janus",
"--interface={src_ip}",
"{o_stun_server}",
], type=valid_command),
"cmd_remove": Option([], type=valid_options),
"cmd_append": Option([], type=valid_options),
},
"watchdog": {
"rtc": Option(0, type=valid_int_f0),
"timeout": Option(300, type=valid_int_f1),
"interval": Option(30, type=valid_int_f1),
},
}

777
patches/__init__.py.2 Normal file
View File

@@ -0,0 +1,777 @@
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.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 os
import functools
import argparse
import logging
import logging.config
import pygments
import pygments.lexers.data
import pygments.formatters
from .. import tools
from ..mouse import MouseRange
from ..plugins import UnknownPluginError
from ..plugins.auth import get_auth_service_class
from ..plugins.hid import get_hid_class
from ..plugins.atx import get_atx_class
from ..plugins.msd import get_msd_class
from ..plugins.ugpio import UserGpioModes
from ..plugins.ugpio import BaseUserGpioDriver
from ..plugins.ugpio import get_ugpio_driver_class
from ..yamlconf import ConfigError
from ..yamlconf import manual_validated
from ..yamlconf import make_config
from ..yamlconf import Section
from ..yamlconf import Option
from ..yamlconf import build_raw_from_options
from ..yamlconf.dumper import make_config_dump
from ..yamlconf.loader import load_yaml_file
from ..yamlconf.merger import yaml_merge
from ..validators.basic import valid_stripped_string
from ..validators.basic import valid_stripped_string_not_empty
from ..validators.basic import valid_bool
from ..validators.basic import valid_number
from ..validators.basic import valid_int_f0
from ..validators.basic import valid_int_f1
from ..validators.basic import valid_float_f0
from ..validators.basic import valid_float_f01
from ..validators.basic import valid_string_list
from ..validators.auth import valid_user
from ..validators.auth import valid_users_list
from ..validators.os import valid_abs_path
from ..validators.os import valid_abs_file
from ..validators.os import valid_abs_dir
from ..validators.os import valid_unix_mode
from ..validators.os import valid_options
from ..validators.os import valid_command
from ..validators.net import valid_ip_or_host
from ..validators.net import valid_net
from ..validators.net import valid_port
from ..validators.net import valid_ports_list
from ..validators.net import valid_mac
from ..validators.net import valid_ssl_ciphers
from ..validators.hid import valid_hid_key
from ..validators.hid import valid_hid_mouse_output
from ..validators.hid import valid_hid_mouse_move
from ..validators.kvm import valid_stream_quality
from ..validators.kvm import valid_stream_fps
from ..validators.kvm import valid_stream_resolution
from ..validators.kvm import valid_stream_h264_bitrate
from ..validators.kvm import valid_stream_h264_gop
from ..validators.ugpio import valid_ugpio_driver
from ..validators.ugpio import valid_ugpio_channel
from ..validators.ugpio import valid_ugpio_mode
from ..validators.ugpio import valid_ugpio_view_title
from ..validators.ugpio import valid_ugpio_view_table
from ..validators.hw import valid_tty_speed
from ..validators.hw import valid_otg_gadget
from ..validators.hw import valid_otg_id
from ..validators.hw import valid_otg_ethernet
# =====
def init(
prog: (str | None)=None,
description: (str | None)=None,
add_help: bool=True,
check_run: bool=False,
cli_logging: bool=False,
argv: (list[str] | None)=None,
**load: bool,
) -> tuple[argparse.ArgumentParser, list[str], Section]:
argv = (argv or sys.argv)
assert len(argv) > 0
parser = argparse.ArgumentParser(
prog=(prog or argv[0]),
description=description,
add_help=add_help,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file,
help="Set config file path", metavar="<file>")
parser.add_argument("-o", "--set-options", default=[], nargs="+",
help="Override config options list (like sec/sub/opt=value)", metavar="<k=v>",)
parser.add_argument("-m", "--dump-config", action="store_true",
help="View current configuration (include all overrides)")
if check_run:
parser.add_argument("--run", dest="run", action="store_true",
help="Run the service")
(options, remaining) = parser.parse_known_args(argv)
if options.dump_config:
_dump_config(_init_config(
config_path=options.config,
override_options=options.set_options,
load_auth=True,
load_hid=True,
load_atx=True,
load_msd=True,
load_gpio=True,
))
raise SystemExit()
config = _init_config(options.config, options.set_options, **load)
logging.captureWarnings(True)
logging.config.dictConfig(config.logging)
if cli_logging:
logging.getLogger().handlers[0].setFormatter(logging.Formatter(
"-- {levelname:>7} -- {message}",
style="{",
))
if check_run and not options.run:
raise SystemExit(
"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)
# =====
def _init_config(config_path: str, override_options: list[str], **load_flags: bool) -> Section:
config_path = os.path.expanduser(config_path)
try:
raw_config: dict = load_yaml_file(config_path)
except Exception as err:
raise SystemExit(f"ConfigError: Can't read config file {config_path!r}:\n{tools.efmt(err)}")
if not isinstance(raw_config, dict):
raise SystemExit(f"ConfigError: Top-level of the file {config_path!r} must be a dictionary")
scheme = _get_config_scheme()
try:
yaml_merge(raw_config, (raw_config.pop("override", {}) or {}))
yaml_merge(raw_config, build_raw_from_options(override_options), "raw CLI options")
_patch_raw(raw_config)
config = make_config(raw_config, scheme)
if _patch_dynamic(raw_config, config, scheme, **load_flags):
config = make_config(raw_config, scheme)
return config
except (ConfigError, UnknownPluginError) as err:
raise SystemExit(f"ConfigError: {err}")
def _patch_raw(raw_config: dict) -> None: # pylint: disable=too-many-branches
if isinstance(raw_config.get("otg"), dict):
for (old, new) in [
("msd", "msd"),
("acm", "serial"),
("drives", "drives"),
]:
if old in raw_config["otg"]:
if not isinstance(raw_config["otg"].get("devices"), dict):
raw_config["otg"]["devices"] = {}
raw_config["otg"]["devices"][new] = raw_config["otg"].pop(old)
if isinstance(raw_config.get("kvmd"), dict) and isinstance(raw_config["kvmd"].get("wol"), dict):
if not isinstance(raw_config["kvmd"].get("gpio"), dict):
raw_config["kvmd"]["gpio"] = {}
for section in ["drivers", "scheme"]:
if not isinstance(raw_config["kvmd"]["gpio"].get(section), dict):
raw_config["kvmd"]["gpio"][section] = {}
raw_config["kvmd"]["gpio"]["drivers"]["__wol__"] = {
"type": "wol",
**raw_config["kvmd"].pop("wol"),
}
raw_config["kvmd"]["gpio"]["scheme"]["__wol__"] = {
"driver": "__wol__",
"pin": 0,
"mode": "output",
"switch": False,
}
if isinstance(raw_config.get("kvmd"), dict) and isinstance(raw_config["kvmd"].get("streamer"), dict):
streamer_config = raw_config["kvmd"]["streamer"]
desired_fps = streamer_config.get("desired_fps")
if desired_fps is not None and not isinstance(desired_fps, dict):
streamer_config["desired_fps"] = {"default": desired_fps}
max_fps = streamer_config.get("max_fps")
if max_fps is not None:
if not isinstance(streamer_config.get("desired_fps"), dict):
streamer_config["desired_fps"] = {}
streamer_config["desired_fps"]["max"] = max_fps
del streamer_config["max_fps"]
resolution = streamer_config.get("resolution")
if resolution is not None and not isinstance(resolution, dict):
streamer_config["resolution"] = {"default": resolution}
available_resolutions = streamer_config.get("available_resolutions")
if available_resolutions is not None:
if not isinstance(streamer_config.get("resolution"), dict):
streamer_config["resolution"] = {}
streamer_config["resolution"]["available"] = available_resolutions
del streamer_config["available_resolutions"]
def _patch_dynamic( # pylint: disable=too-many-locals
raw_config: dict,
config: Section,
scheme: dict,
load_auth: bool=False,
load_hid: bool=False,
load_atx: bool=False,
load_msd: bool=False,
load_gpio: bool=False,
) -> bool:
rebuild = False
if load_auth:
scheme["kvmd"]["auth"]["internal"].update(get_auth_service_class(config.kvmd.auth.internal.type).get_plugin_options())
if config.kvmd.auth.external.type:
scheme["kvmd"]["auth"]["external"].update(get_auth_service_class(config.kvmd.auth.external.type).get_plugin_options())
rebuild = True
for (load, section, get_class) in [
(load_hid, "hid", get_hid_class),
(load_atx, "atx", get_atx_class),
(load_msd, "msd", get_msd_class),
]:
if load:
scheme["kvmd"][section].update(get_class(getattr(config.kvmd, section).type).get_plugin_options())
rebuild = True
if load_gpio:
driver: str
drivers: dict[str, type[BaseUserGpioDriver]] = {} # Name to drivers
for (driver, params) in { # type: ignore
"__gpio__": {},
**tools.rget(raw_config, "kvmd", "gpio", "drivers"),
}.items():
with manual_validated(driver, "kvmd", "gpio", "drivers", "<key>"):
driver = valid_ugpio_driver(driver)
driver_type = valid_stripped_string_not_empty(params.get("type", "gpio"))
driver_class = get_ugpio_driver_class(driver_type)
drivers[driver] = driver_class
scheme["kvmd"]["gpio"]["drivers"][driver] = {
"type": Option(driver_type, type=valid_stripped_string_not_empty),
**driver_class.get_plugin_options()
}
path = ("kvmd", "gpio", "scheme")
for (channel, params) in tools.rget(raw_config, *path).items():
with manual_validated(channel, *path, "<key>"):
channel = valid_ugpio_channel(channel)
driver = params.get("driver", "__gpio__")
with manual_validated(driver, *path, channel, "driver"):
driver = valid_ugpio_driver(driver, set(drivers))
mode: str = params.get("mode", "")
with manual_validated(mode, *path, channel, "mode"):
mode = valid_ugpio_mode(mode, drivers[driver].get_modes())
if params.get("pulse") == False: # noqa: E712 # pylint: disable=singleton-comparison
params["pulse"] = {"delay": 0}
scheme["kvmd"]["gpio"]["scheme"][channel] = {
"driver": Option("__gpio__", type=functools.partial(valid_ugpio_driver, variants=set(drivers))),
"pin": Option(None, type=drivers[driver].get_pin_validator()),
"mode": Option("", type=functools.partial(valid_ugpio_mode, variants=drivers[driver].get_modes())),
"inverted": Option(False, type=valid_bool),
**({
"busy_delay": Option(0.2, type=valid_float_f01),
"initial": Option(False, type=(lambda arg: (valid_bool(arg) if arg is not None else None))),
"switch": Option(True, type=valid_bool),
"pulse": { # type: ignore
"delay": Option(0.1, type=valid_float_f0),
"min_delay": Option(0.1, type=valid_float_f01),
"max_delay": Option(0.1, type=valid_float_f01),
},
} if mode == UserGpioModes.OUTPUT else { # input
"debounce": Option(0.1, type=valid_float_f0),
})
}
rebuild = True
return rebuild
def _dump_config(config: Section) -> None:
dump = make_config_dump(config)
if sys.stdout.isatty():
dump = pygments.highlight(
dump,
pygments.lexers.data.YamlLexer(),
pygments.formatters.TerminalFormatter(bg="dark"), # pylint: disable=no-member
)
print(dump)
def _get_config_scheme() -> dict:
return {
"logging": Option({}),
"kvmd": {
"server": {
"unix": Option("/run/kvmd/kvmd.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'"),
},
"auth": {
"enabled": Option(True, type=valid_bool),
"internal": {
"type": Option("htpasswd"),
"force_users": Option([], type=valid_users_list),
# Dynamic content
},
"external": {
"type": Option("", type=valid_stripped_string),
# Dynamic content
},
"totp": {
"secret": {
"file": Option("/etc/kvmd/totp.secret", type=valid_abs_path, if_empty=""),
},
},
},
"info": { # Accessed via global config, see kvmd/info for details
"meta": Option("/etc/kvmd/meta.yaml", type=valid_abs_file),
"extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir),
"hw": {
"vcgencmd_cmd": Option(["/usr/bin/vcgencmd"], type=valid_command),
"ignore_past": Option(False, type=valid_bool),
"state_poll": Option(10.0, type=valid_float_f01),
},
"fan": {
"daemon": Option("kvmd-fan", type=valid_stripped_string),
"unix": Option("", type=valid_abs_path, if_empty="", unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
"state_poll": Option(5.0, type=valid_float_f01),
},
},
"log_reader": {
"enabled": Option(True, type=valid_bool),
},
"prometheus": {
"auth": {
"enabled": Option(True, type=valid_bool),
},
},
"hid": {
"type": Option("", type=valid_stripped_string_not_empty),
"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
},
"atx": {
"type": Option("", type=valid_stripped_string_not_empty),
# Dynamic content
},
"msd": {
"type": Option("", type=valid_stripped_string_not_empty),
# Dynamic content
},
"streamer": {
"forever": Option(False, type=valid_bool),
"reset_delay": Option(1.0, type=valid_float_f0),
"shutdown_delay": Option(10.0, type=valid_float_f01),
"state_poll": Option(1.0, type=valid_float_f01),
"quality": Option(80, type=valid_stream_quality, if_empty=0),
"resolution": {
"default": Option("", type=valid_stream_resolution, if_empty="", unpack_as="resolution"),
"available": Option(
[],
type=functools.partial(valid_string_list, subval=valid_stream_resolution),
unpack_as="available_resolutions",
),
},
"desired_fps": {
"default": Option(40, type=valid_stream_fps, unpack_as="desired_fps"),
"min": Option(0, type=valid_stream_fps, unpack_as="desired_fps_min"),
"max": Option(70, type=valid_stream_fps, unpack_as="desired_fps_max"),
},
"h264_bitrate": {
"default": Option(0, type=valid_stream_h264_bitrate, if_empty=0, unpack_as="h264_bitrate"),
"min": Option(25, type=valid_stream_h264_bitrate, unpack_as="h264_bitrate_min"),
"max": Option(20000, type=valid_stream_h264_bitrate, unpack_as="h264_bitrate_max"),
},
"h264_gop": {
"default": Option(30, type=valid_stream_h264_gop, unpack_as="h264_gop"),
"min": Option(0, type=valid_stream_h264_gop, unpack_as="h264_gop_min"),
"max": Option(60, type=valid_stream_h264_gop, unpack_as="h264_gop_max"),
},
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(2.0, type=valid_float_f01),
"process_name_prefix": Option("kvmd/streamer"),
"pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command),
"pre_start_cmd_remove": Option([], type=valid_options),
"pre_start_cmd_append": Option([], type=valid_options),
"cmd": Option(["/bin/true"], type=valid_command),
"cmd_remove": Option([], type=valid_options),
"cmd_append": Option([], type=valid_options),
"post_stop_cmd": Option(["/bin/true", "post-stop"], type=valid_command),
"post_stop_cmd_remove": Option([], type=valid_options),
"post_stop_cmd_append": Option([], type=valid_options),
},
"ocr": {
"langs": Option(["eng"], type=valid_string_list, unpack_as="default_langs"),
"tessdata": Option("/usr/share/tessdata", type=valid_stripped_string_not_empty, unpack_as="data_dir_path")
},
"snapshot": {
"idle_interval": Option(0.0, type=valid_float_f0),
"live_interval": Option(0.0, type=valid_float_f0),
"wakeup_key": Option("", type=valid_hid_key, if_empty=""),
"wakeup_move": Option(0, type=valid_hid_mouse_move),
"online_delay": Option(5.0, type=valid_float_f0),
"retries": Option(10, type=valid_int_f1),
"retries_delay": Option(3.0, type=valid_float_f01),
},
"gpio": {
"state_poll": Option(0.1, type=valid_float_f01),
"drivers": {}, # Dynamic content
"scheme": {}, # Dymanic content
"view": {
"header": {
"title": Option("GPIO", type=valid_ugpio_view_title),
},
"table": Option([], type=valid_ugpio_view_table),
},
},
},
"pst": {
"server": {
"unix": Option("/run/kvmd/pst.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'"),
},
"ro_retries_delay": Option(10.0, type=valid_float_f01),
"ro_cleanup_delay": Option(3.0, type=valid_float_f01),
"remount_cmd": Option([
"/usr/bin/sudo", "--non-interactive",
"/usr/bin/kvmd-helper-pst-remount", "{mode}",
], type=valid_command),
},
"otg": {
"vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation
"product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget
"manufacturer": Option("PiKVM", type=valid_stripped_string),
"product": Option("Composite KVM Device", type=valid_stripped_string),
"serial": Option("CAFEBABE", type=valid_stripped_string, if_none=None),
"device_version": Option(-1, type=functools.partial(valid_number, min=-1, max=0xFFFF)),
"usb_version": Option(0x0200, type=valid_otg_id),
"max_power": Option(250, type=functools.partial(valid_number, min=50, max=500)),
"remote_wakeup": Option(False, type=valid_bool),
"gadget": Option("kvmd", type=valid_otg_gadget),
"config": Option("PiKVM device", type=valid_stripped_string_not_empty),
"udc": Option("", type=valid_stripped_string),
"init_delay": Option(3.0, type=valid_float_f01),
"user": Option("kvmd", type=valid_user),
"meta": Option("/run/kvmd/otg", type=valid_abs_path),
"devices": {
"hid": {
"keyboard": {
"start": Option(True, type=valid_bool),
},
"mouse": {
"start": Option(True, type=valid_bool),
},
},
"msd": {
"start": Option(True, type=valid_bool),
"default": {
"stall": Option(False, type=valid_bool),
"cdrom": Option(True, type=valid_bool),
"rw": Option(False, type=valid_bool),
"removable": Option(True, type=valid_bool),
"fua": Option(True, type=valid_bool),
},
},
"serial": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
},
"ethernet": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
"driver": Option("ecm", type=valid_otg_ethernet),
"host_mac": Option("", type=valid_mac, if_empty=""),
"kvm_mac": Option("", type=valid_mac, if_empty=""),
},
"drives": {
"enabled": Option(False, type=valid_bool),
"start": Option(True, type=valid_bool),
"count": Option(1, type=valid_int_f1),
"default": {
"stall": Option(False, type=valid_bool),
"cdrom": Option(False, type=valid_bool),
"rw": Option(True, type=valid_bool),
"removable": Option(True, type=valid_bool),
"fua": Option(True, type=valid_bool),
},
},
},
},
"otgnet": {
"iface": {
"net": Option("172.30.30.0/24", type=functools.partial(valid_net, v6=False)),
"ip_cmd": Option(["/usr/bin/ip"], type=valid_command),
},
"firewall": {
"allow_icmp": Option(True, type=valid_bool),
"allow_tcp": Option([], type=valid_ports_list),
"allow_udp": Option([67], type=valid_ports_list),
"forward_iface": Option("", type=valid_stripped_string),
"iptables_cmd": Option(["/usr/sbin/iptables", "--wait=5"], type=valid_command),
},
"commands": {
"pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command),
"pre_start_cmd_remove": Option([], type=valid_options),
"pre_start_cmd_append": Option([], type=valid_options),
"post_start_cmd": Option([
"/usr/bin/systemd-run",
"--unit=kvmd-otgnet-dnsmasq",
"/usr/sbin/dnsmasq",
"--conf-file=/dev/null",
"--pid-file",
"--user=dnsmasq",
"--interface={iface}",
"--port=0",
"--dhcp-range={dhcp_ip_begin},{dhcp_ip_end},24h",
"--dhcp-leasefile=/run/kvmd/dnsmasq.lease",
"--dhcp-option={dhcp_option_3}",
"--dhcp-option=6",
"--keep-in-foreground",
], type=valid_command),
"post_start_cmd_remove": Option([], type=valid_options),
"post_start_cmd_append": Option([], type=valid_options),
"pre_stop_cmd": Option([
"/usr/bin/systemctl",
"stop",
"kvmd-otgnet-dnsmasq",
], type=valid_command),
"pre_stop_cmd_remove": Option([], type=valid_options),
"pre_stop_cmd_append": Option([], type=valid_options),
"post_stop_cmd": Option(["/bin/true", "post-stop"], type=valid_command),
"post_stop_cmd_remove": Option([], type=valid_options),
"post_stop_cmd_append": Option([], type=valid_options),
},
},
"ipmi": {
"server": {
"host": Option("::", type=valid_ip_or_host),
"port": Option(623, type=valid_port),
"timeout": Option(10.0, type=valid_float_f01),
},
"kvmd": {
"unix": Option("/run/kvmd/kvmd.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
"auth": {
"file": Option("/etc/kvmd/ipmipasswd", type=valid_abs_file, unpack_as="path"),
},
"sol": {
"device": Option("", type=valid_abs_path, if_empty="", unpack_as="sol_device_path"),
"speed": Option(115200, type=valid_tty_speed, unpack_as="sol_speed"),
"select_timeout": Option(0.1, type=valid_float_f01, unpack_as="sol_select_timeout"),
"proxy_port": Option(0, type=valid_port, unpack_as="sol_proxy_port"),
},
},
"vnc": {
"desired_fps": Option(30, type=valid_stream_fps),
"mouse_output": Option("usb", type=valid_hid_mouse_output),
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
"server": {
"host": Option("::", type=valid_ip_or_host),
"port": Option(5900, type=valid_port),
"max_clients": Option(10, type=valid_int_f1),
"no_delay": Option(True, type=valid_bool),
"keepalive": {
"enabled": Option(True, type=valid_bool, unpack_as="keepalive_enabled"),
"idle": Option(10, type=functools.partial(valid_number, min=1, max=3600), unpack_as="keepalive_idle"),
"interval": Option(3, type=functools.partial(valid_number, min=1, max=60), unpack_as="keepalive_interval"),
"count": Option(3, type=functools.partial(valid_number, min=1, max=10), unpack_as="keepalive_count"),
},
"tls": {
"ciphers": Option("ALL:@SECLEVEL=0", type=valid_ssl_ciphers, if_empty=""),
"timeout": Option(30.0, type=valid_float_f01),
"x509": {
"cert": Option("/etc/kvmd/vnc/ssl/server.crt", type=valid_abs_file, if_empty=""),
"key": Option("/etc/kvmd/vnc/ssl/server.key", type=valid_abs_file, if_empty=""),
},
},
},
"kvmd": {
"unix": Option("/run/kvmd/kvmd.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
"streamer": {
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
"timeout": Option(5.0, type=valid_float_f01),
},
"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(1.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),
},
},
"auth": {
"vncauth": {
"enabled": Option(False, type=valid_bool),
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="path"),
},
"vencrypt": {
"enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"),
},
},
},
"janus": {
"stun": {
"host": Option("stun.l.google.com", type=valid_ip_or_host, unpack_as="stun_host"),
"port": Option(19302, type=valid_port, unpack_as="stun_port"),
"timeout": Option(5.0, type=valid_float_f01, unpack_as="stun_timeout"),
"retries": Option(5, type=valid_int_f1, unpack_as="stun_retries"),
"retries_delay": Option(5.0, type=valid_float_f01, unpack_as="stun_retries_delay"),
},
"check": {
"interval": Option(10.0, type=valid_float_f01, unpack_as="check_interval"),
"retries": Option(5, type=valid_int_f1, unpack_as="check_retries"),
"retries_delay": Option(5.0, type=valid_float_f01, unpack_as="check_retries_delay"),
},
"cmd": Option([
"/usr/bin/janus",
"--disable-colors",
"--plugins-folder=/usr/lib/ustreamer/janus",
"--configs-folder=/etc/kvmd/janus",
"--interface={src_ip}",
"{o_stun_server}",
], type=valid_command),
"cmd_remove": Option([], type=valid_options),
"cmd_append": Option([], type=valid_options),
},
"watchdog": {
"rtc": Option(0, type=valid_int_f0),
"timeout": Option(300, type=valid_int_f1),
"interval": Option(30, type=valid_int_f1),
},
}

View File

@@ -1,9 +1,27 @@
diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
--- kvmd/aiohelpers.py 2023-01-30 03:25:23.556804000 +0700
+++ kvmd/aiohelpers.py 2023-01-30 08:12:21.773899000 +0700
@@ -38,11 +38,26 @@
From 3d137882ac38ac046b7d09cada1883b304b04319 Mon Sep 17 00:00:00 2001
From: xe5700 <9338143+xe5700@users.noreply.github.com>
Date: Fri, 20 May 2022 18:34:21 +0800
Subject: [PATCH] Revert force eject feature to unlock helper
---
kvmd/aiohelpers.py | 31 ++++++++++++-----
kvmd/apps/otg/__init__.py | 3 +-
kvmd/apps/otgmsd/__init__.py | 25 +++++++++++++-
kvmd/helpers/unlock/__init__.py | 58 ++++++++++++++++++++++++++++++++
kvmd/helpers/unlock/__main__.py | 24 +++++++++++++
kvmd/plugins/msd/otg/__init__.py | 19 ++++++++---
kvmd/plugins/msd/otg/drive.py | 5 +--
7 files changed, 145 insertions(+), 20 deletions(-)
create mode 100644 kvmd/helpers/unlock/__init__.py
create mode 100644 kvmd/helpers/unlock/__main__.py
diff --git a/kvmd/aiohelpers.py b/kvmd/aiohelpers.py
index 6357764c..37a5d4b9 100644
--- a/kvmd/aiohelpers.py
+++ b/kvmd/aiohelpers.py
@@ -40,11 +40,26 @@ async def remount(name: str, base_cmd: List[str], rw: bool) -> bool:
]
logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), tools.cmdfmt(cmd))
logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), cmd)
try:
- proc = await aioproc.log_process(cmd, logger)
- if proc.returncode != 0:
@@ -19,7 +37,7 @@ diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
+ raise
+
+
+async def unlock_drive(base_cmd: list[str]) -> None:
+async def unlock_drive(base_cmd: List[str]) -> None:
+ logger = get_logger(0)
+ logger.info("Unlocking the drive ...")
+ try:
@@ -30,16 +48,17 @@ diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
+
+
+# =====
+async def _run_helper(cmd: list[str]) -> None:
+async def _run_helper(cmd: List[str]) -> None:
+ logger = get_logger(0)
+ logger.info("Executing helper %s ...", cmd)
+ proc = await aioproc.log_process(cmd, logger)
+ if proc.returncode != 0:
+ logger.error(f"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}")
diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
--- kvmd/apps/otg/__init__.py 2022-12-22 10:01:48.000000000 +0700
+++ kvmd/apps/otg/__init__.py 2023-01-30 03:51:51.331539000 +0700
@@ -182,7 +182,6 @@
+ raise MsdError(f"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}")
diff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py
index cbf7a197..d0ed0554 100644
--- a/kvmd/apps/otg/__init__.py
+++ b/kvmd/apps/otg/__init__.py
@@ -182,7 +182,6 @@ class _GadgetConfig:
_chown(join(func_path, "lun.0/cdrom"), user)
_chown(join(func_path, "lun.0/ro"), user)
_chown(join(func_path, "lun.0/file"), user)
@@ -47,33 +66,39 @@ diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
_symlink(func_path, join(self.__profile_path, func))
name = ("Mass Storage Drive" if self.__msd_instance == 0 else f"Extra Drive #{self.__msd_instance}")
self.__create_meta(func, name)
@@ -291,7 +290,7 @@
@@ -291,7 +290,7 @@ def _cmd_stop(config: Section) -> None:
logger.info("Disabling gadget %r ...", config.otg.gadget)
_write(join(gadget_path, "UDC"), "\n")
- _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True)
+ _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), True)
profile_path = join(gadget_path, usb.G_PROFILE)
for func in os.listdir(profile_path):
diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py
--- kvmd/apps/otgmsd/__init__.py 2022-12-22 10:01:48.000000000 +0700
+++ kvmd/apps/otgmsd/__init__.py 2023-01-30 04:35:09.702576000 +0700
@@ -21,8 +21,10 @@
diff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py
index f57b3107..78f8e3c7 100644
--- a/kvmd/apps/otgmsd/__init__.py
+++ b/kvmd/apps/otgmsd/__init__.py
@@ -21,12 +21,15 @@
import os
+import signal
import errno
import argparse
from typing import List
from typing import Optional
+import psutil
+
from ...validators.basic import valid_bool
from ...validators.basic import valid_int_f0
@@ -53,6 +55,21 @@
from ...validators.os import valid_abs_file
@@ -56,6 +59,21 @@ def _set_param(gadget: str, instance: int, param: str, value: str) -> None:
raise
+def _unlock() -> None:
+ # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924
+ found = False
@@ -90,33 +115,35 @@ diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py
+
+
# =====
def main(argv: (list[str] | None)=None) -> None:
def main(argv: Optional[List[str]]=None) -> None:
(parent_parser, argv, config) = init(
@@ -77,7 +94,7 @@
parser.add_argument("--eject", action="store_true",
help="Eject the image")
parser.add_argument("--unlock", action="store_true",
- help="Does nothing, just for backward compatibility")
@@ -70,6 +88,8 @@ def main(argv: Optional[List[str]]=None) -> None:
)
parser.add_argument("-i", "--instance", default=0, type=valid_int_f0,
metavar="<N>", help="Drive instance (0 for KVMD drive)")
+ parser.add_argument("--unlock", action="store_true",
+ help="Send SIGUSR1 to MSD kernel thread")
options = parser.parse_args(argv[1:])
if config.kvmd.msd.type != "otg":
@@ -87,8 +104,11 @@
parser.add_argument("--set-cdrom", default=None, type=valid_bool,
metavar="<1|0|yes|no>", help="Set CD-ROM flag")
parser.add_argument("--set-rw", default=None, type=valid_bool,
@@ -89,8 +109,11 @@ def main(argv: Optional[List[str]]=None) -> None:
set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value))
get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))
+ if options.unlock:
+ _unlock()
+
if options.eject:
- set_param("forced_eject", "")
+ set_param("file", "")
if options.set_cdrom is not None:
set_param("cdrom", str(int(options.set_cdrom)))
diff -ruN kvmd/helpers/unlock/__init__.py kvmd/helpers/unlock/__init__.py
--- kvmd/helpers/unlock/__init__.py 1970-01-01 07:00:00.000000000 +0700
+++ kvmd/helpers/unlock/__init__.py 2023-01-30 04:04:07.000000000 +0700
diff --git a/kvmd/helpers/unlock/__init__.py b/kvmd/helpers/unlock/__init__.py
new file mode 100644
index 00000000..140e0e7c
--- /dev/null
+++ b/kvmd/helpers/unlock/__init__.py
@@ -0,0 +1,58 @@
+# ========================================================================== #
+# #
@@ -176,9 +203,11 @@ diff -ruN kvmd/helpers/unlock/__init__.py kvmd/helpers/unlock/__init__.py
+ if len(sys.argv) != 2 or sys.argv[1] != "unlock":
+ raise SystemExit(f"Usage: {sys.argv[0]} [unlock]")
+ _unlock()
diff -ruN kvmd/helpers/unlock/__main__.py kvmd/helpers/unlock/__main__.py
--- kvmd/helpers/unlock/__main__.py 1970-01-01 07:00:00.000000000 +0700
+++ kvmd/helpers/unlock/__main__.py 2023-01-30 04:04:07.000000000 +0700
diff --git a/kvmd/helpers/unlock/__main__.py b/kvmd/helpers/unlock/__main__.py
new file mode 100644
index 00000000..3849d1b9
--- /dev/null
+++ b/kvmd/helpers/unlock/__main__.py
@@ -0,0 +1,24 @@
+# ========================================================================== #
+# #
@@ -204,93 +233,98 @@ diff -ruN kvmd/helpers/unlock/__main__.py kvmd/helpers/unlock/__main__.py
+
+from . import main
+main()
diff -ruN kvmd/plugins/msd/otg/drive.py kvmd/plugins/msd/otg/drive.py
--- kvmd/plugins/msd/otg/drive.py 2022-12-22 10:01:48.000000000 +0700
+++ kvmd/plugins/msd/otg/drive.py 2023-01-30 06:31:13.923959000 +0700
@@ -51,10 +51,7 @@
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
index 409b899a..1342c6b4 100644
--- a/kvmd/plugins/msd/otg/__init__.py
+++ b/kvmd/plugins/msd/otg/__init__.py
@@ -140,6 +140,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
storage_path: str,
remount_cmd: List[str],
+ unlock_cmd: List[str],
initial: Dict,
@@ -154,6 +155,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__meta_path = os.path.join(self.__storage_path, "meta")
self.__remount_cmd = remount_cmd
+ self.__unlock_cmd = unlock_cmd
self.__initial_image: str = initial["image"]
self.__initial_cdrom: bool = initial["cdrom"]
@@ -178,10 +180,8 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
"storage": Option("/var/lib/kvmd/msd", type=valid_abs_dir, unpack_as="storage_path"),
- "remount_cmd": Option([
- "/usr/bin/sudo", "--non-interactive",
- "/usr/bin/kvmd-helper-otgmsd-remount", "{mode}",
- ], type=valid_command),
+ "remount_cmd": Option([*sudo, "/usr/bin/kvmd-helper-otgmsd-remount", "{mode}"], type=valid_command),
+ "unlock_cmd": Option([*sudo, "/usr/bin/kvmd-helper-otgmsd-unlock", "unlock"], type=valid_command),
"initial": {
"image": Option("", type=valid_printable_filename, if_empty=""),
@@ -241,6 +241,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
async def reset(self) -> None:
async with self.__state.busy(check_online=False):
try:
+ await self.__unlock_drive()
self.__drive.set_image_path("")
self.__drive.set_rw_flag(False)
self.__drive.set_cdrom_flag(False)
@@ -290,12 +291,15 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
if not os.path.exists(self.__state.vd.image.path):
raise MsdUnknownImageError()
+ await self.__unlock_drive()
self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
self.__drive.set_image_path(self.__state.vd.image.path)
else:
if not (self.__state.vd.connected or self.__drive.get_image_path()):
raise MsdDisconnectedError()
+
+ await self.__unlock_drive()
self.__drive.set_image_path("")
self.__state.vd.connected = connected
@@ -474,6 +478,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
if os.path.exists(path):
logger.info("Setting up initial image %r ...", self.__initial_image)
try:
+ await self.__unlock_drive()
self.__drive.set_cdrom_flag(self.__initial_cdrom)
self.__drive.set_image_path(path)
except Exception:
@@ -541,4 +546,8 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
async def __remount_storage(self, rw: bool) -> None:
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
- raise MsdError("Can't execute remount helper")
+ pass
+ #raise MsdError("Can't execute remount helper")
+
+ async def __unlock_drive(self) -> None:
+ await helpers.unlock_drive(self.__unlock_cmd)
\ No newline at end of file
diff --git a/kvmd/plugins/msd/otg/drive.py b/kvmd/plugins/msd/otg/drive.py
index 11af7f81..ee54e5e9 100644
--- a/kvmd/plugins/msd/otg/drive.py
+++ b/kvmd/plugins/msd/otg/drive.py
@@ -53,10 +53,7 @@ class Drive:
# =====
def set_image_path(self, path: str) -> None:
- if path:
- self.__set_param("file", path)
- else:
- self.__set_param("forced_eject", "")
+ self.__set_param("file", path)
def get_image_path(self) -> str:
return self.__get_param("file")
diff -ruN kvmd/plugins/msd/otg/__init__.py kvmd/plugins/msd/otg/__init__.py
--- kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:42:28.021418683 +0700
+++ kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:50:38.774955045 +0700
@@ -129,6 +129,7 @@
sync_chunk_size: int,
--
2.34.1.windows.1
remount_cmd: list[str],
+ unlock_cmd: list[str],
initial: dict,
@@ -140,6 +141,7 @@
self.__sync_chunk_size = sync_chunk_size
self.__remount_cmd = remount_cmd
+ self.__unlock_cmd = unlock_cmd
self.__initial_image: str = initial["image"]
self.__initial_cdrom: bool = initial["cdrom"]
@@ -169,6 +171,11 @@
"/usr/bin/kvmd-helper-otgmsd-remount", "{mode}",
], type=valid_command),
+ "unlock_cmd": Option([
+ "/usr/bin/sudo", "--non-interactive",
+ "/usr/bin/kvmd-helper-otgmsd-unlock", "unlock",
+ ], type=valid_command),
+
"initial": {
"image": Option("", type=valid_printable_filename, if_empty=""),
"cdrom": Option(False, type=valid_bool),
@@ -230,6 +237,7 @@
async def reset(self) -> None:
async with self.__state.busy(check_online=False):
try:
+ await self.__unlock_drive()
self.__drive.set_image_path("")
self.__drive.set_cdrom_flag(False)
self.__drive.set_rw_flag(False)
@@ -286,7 +294,7 @@
raise MsdUnknownImageError()
assert self.__state.vd.image.in_storage
-
+ await self.__unlock_drive()
self.__drive.set_rw_flag(self.__state.vd.rw)
self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
if self.__state.vd.rw:
@@ -294,6 +302,7 @@
self.__drive.set_image_path(self.__state.vd.image.path)
else:
+ await self.__unlock_drive()
self.__state_check_connected()
self.__drive.set_image_path("")
await self.__remount_rw(False, fatal=False)
@@ -499,6 +508,7 @@
if image.exists():
logger.info("Setting up initial image %r ...", self.__initial_image)
try:
+ await self.__unlock_drive()
self.__drive.set_rw_flag(False)
self.__drive.set_cdrom_flag(self.__initial_cdrom)
self.__drive.set_image_path(image.path)
@@ -531,5 +541,8 @@
async def __remount_rw(self, rw: bool, fatal: bool=True) -> None:
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
- if fatal:
- raise MsdError("Can't execute remount helper")
+ pass
+ #raise MsdError("Can't execute remount helper")
+
+ async def __unlock_drive(self) -> None:
+ await aiohelpers.unlock_drive(self.__unlock_cmd)

View File

@@ -0,0 +1,11 @@
#!/bin/bash
APP_PATH=$(readlink -f $(dirname $0))
echo "-> Apply patches"
cd /usr/lib/python3.10/site-packages/
git apply ${APP_PATH}/*.patch
cd ${APP_PATH}
echo "-> Add otgmsd unlock link"
cp kvmd-helper-otgmsd-unlock /usr/bin/
echo "-> Add sudoer"
echo "kvmd ALL=(ALL) NOPASSWD: /usr/bin/kvmd-helper-otgmsd-unlock" >> /etc/sudoers.d/99_kvmd
echo "-> Apply old kernel msd patch done."

View File

@@ -0,0 +1,7 @@
#!/usr/sbin/python
# KVMD-ARMBIAN
from kvmd.helpers.unlock import main
if __name__ == "__main__":
main()

10
patches/display.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
#tty清屏
echo -e "\033c" > /dev/tty1
#隐藏光标
echo -e "\033[?25l" > /dev/tty1
#在HDMI显示器输出采集卡画面
ustreamer-dump --sink=kvmd::ustreamer::jpeg --output - | ffmpeg -use_wallclock_as_timestamps 1 -i pipe:c:v -an -pix_fmt bgr24 -f fbdev /dev/fb0

View File

@@ -2,7 +2,7 @@
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.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 #
@@ -31,7 +31,7 @@ from ....logging import get_logger
from .... import env
from .... import tools
from .... import aiofs
from .... import aiotools
from .... import aioproc
from .base import BaseInfoSubmanager
@@ -46,33 +46,36 @@ class HwInfoSubmanager(BaseInfoSubmanager):
def __init__(
self,
vcgencmd_cmd: list[str],
ignore_past: bool,
state_poll: float,
) -> None:
self.__vcgencmd_cmd = vcgencmd_cmd
self.__ignore_past = ignore_past
self.__state_poll = state_poll
self.__dt_cache: dict[str, str] = {}
async def get_state(self) -> dict:
(model, cpu_temp, throttling) = await asyncio.gather(
self.__read_dt_file("model"),
self.__get_cpu_temp(),
self.__get_throttling(),
)
return {
"platform": {
"type": "rpi",
"base": model,
"serial": "0000000000000000",
},
"health": {
"temp": {
"cpu": cpu_temp,
},
"throttling": throttling,
},
}
#async def get_state(self) -> dict:
# (model, serial, cpu_temp, throttling) = await asyncio.gather(
# self.__read_dt_file("model"),
# self.__read_dt_file("serial-number"),
# self.__get_cpu_temp(),
# self.__get_throttling(),
# )
# return {
# "platform": {
# "type": "rpi",
# "base": model,
# "serial": serial,
# },
# "health": {
# "temp": {
# "cpu": cpu_temp,
# },
# "throttling": throttling,
# },
# }
async def poll_state(self) -> AsyncGenerator[dict, None]:
prev_state: dict = {}
@@ -89,7 +92,7 @@ class HwInfoSubmanager(BaseInfoSubmanager):
if name not in self.__dt_cache:
path = os.path.join(f"{env.PROCFS_PREFIX}/proc/device-tree", name)
try:
self.__dt_cache[name] = (await aiofs.read(path)).strip(" \t\r\n\0")
self.__dt_cache[name] = (await aiotools.read_file(path)).strip(" \t\r\n\0")
except Exception as err:
get_logger(0).error("Can't read DT %s from %s: %s", name, path, err)
return None
@@ -98,8 +101,9 @@ class HwInfoSubmanager(BaseInfoSubmanager):
async def __get_cpu_temp(self) -> (float | None):
temp_path = f"{env.SYSFS_PREFIX}/sys/class/thermal/thermal_zone0/temp"
try:
return int((await aiofs.read(temp_path)).strip()) / 1000
return int((await aiotools.read_file(temp_path)).strip()) / 1000
except Exception as err:
get_logger(0).error("Can't read CPU temp from %s: %s", temp_path, err)
return None
async def __get_throttling(self) -> (dict | None):
@@ -125,6 +129,7 @@ class HwInfoSubmanager(BaseInfoSubmanager):
"past": bool(flags & (1 << 18)),
},
},
"ignore_past": self.__ignore_past,
}
return None

13
patches/onecloud_gpio.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
case $1 in
short)
gpioset -m time -s 1 gpiochip1 7=0
gpioset gpiochip1 7=1
;;
long)
gpioset -m time -s 5 gpiochip1 7=0
gpioset gpiochip1 7=1
;;
*)
echo "No thing."
esac

417
patches/session.js Normal file
View File

@@ -0,0 +1,417 @@
/*****************************************************************************
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.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/>. #
# #
*****************************************************************************/
"use strict";
import {tools, $} from "../tools.js";
import {wm} from "../wm.js";
import {Recorder} from "./recorder.js";
import {Hid} from "./hid.js";
import {Atx} from "./atx.js";
import {Msd} from "./msd.js";
import {Streamer} from "./stream.js";
import {Gpio} from "./gpio.js";
import {Ocr} from "./ocr.js";
export function Session() {
// var self = this;
/************************************************************************/
var __ws = null;
var __ping_timer = null;
var __missed_heartbeats = 0;
var __streamer = new Streamer();
var __recorder = new Recorder();
var __hid = new Hid(__streamer.getGeometry, __recorder);
var __atx = new Atx(__recorder);
var __msd = new Msd();
var __gpio = new Gpio(__recorder);
var __ocr = new Ocr(__streamer.getGeometry);
var __info_hw_state = null;
var __info_fan_state = null;
var __init__ = function() {
__startSession();
};
/************************************************************************/
var __setAboutInfoMeta = function(state) {
if (state !== null) {
let text = JSON.stringify(state, undefined, 4).replace(/ /g, "&nbsp;").replace(/\n/g, "<br>");
$("about-meta").innerHTML = `
<span class="code-comment">// The PiKVM metadata.<br>
// You can get this JSON using handle <a target="_blank" href="/api/info?fields=meta">/api/info?fields=meta</a>.<br>
// In the standard configuration this data<br>
// is specified in the file /etc/kvmd/meta.yaml.</span><br>
<br>
${text}
`;
if (state.server && state.server.host) {
$("kvmd-meta-server-host").innerHTML = `Server: ${state.server.host}`;
document.title = `PiKVM Session: ${state.server.host}`;
} else {
$("kvmd-meta-server-host").innerHTML = "";
document.title = "PiKVM Session";
}
// Don't use this option, it may be removed in any time
if (state.web && state.web.confirm_session_exit === false) {
window.onbeforeunload = null; // See main.js
}
}
};
var __setAboutInfoHw = function(state) {
if (state.health.throttling !== null) {
let flags = state.health.throttling.parsed_flags;
let ignore_past = state.health.throttling.ignore_past;
let undervoltage = (flags.undervoltage.now || (flags.undervoltage.past && !ignore_past));
let freq_capped = (flags.freq_capped.now || (flags.freq_capped.past && !ignore_past));
tools.hidden.setVisible($("hw-health-dropdown"), (undervoltage || freq_capped));
$("hw-health-undervoltage-led").className = (undervoltage ? (flags.undervoltage.now ? "led-red" : "led-yellow") : "hidden");
$("hw-health-overheating-led").className = (freq_capped ? (flags.freq_capped.now ? "led-red" : "led-yellow") : "hidden");
tools.hidden.setVisible($("hw-health-message-undervoltage"), undervoltage);
tools.hidden.setVisible($("hw-health-message-overheating"), freq_capped);
}
__info_hw_state = state;
__renderAboutInfoHardware();
};
var __setAboutInfoFan = function(state) {
let failed = false;
let failed_past = false;
if (state.monitored) {
if (state.state === null) {
failed = true;
} else {
if (!state.state.fan.ok) {
failed = true;
} else if (state.state.fan.last_fail_ts >= 0) {
failed = true;
failed_past = true;
}
}
}
tools.hidden.setVisible($("fan-health-dropdown"), failed);
$("fan-health-led").className = (failed ? (failed_past ? "led-yellow" : "led-red") : "hidden");
__info_fan_state = state;
__renderAboutInfoHardware();
};
var __renderAboutInfoHardware = function() {
let html = "";
if (__info_hw_state !== null) {
html += `
Platform:
${__formatPlatform(__info_hw_state.platform)}
<hr>
Temperature:
${__formatTemp(__info_hw_state.health.temp)}
<hr>
Throttling:
${__formatThrottling(__info_hw_state.health.throttling)}
`;
}
if (__info_fan_state !== null) {
if (html.length > 0) {
html += "<hr>";
}
html += `
Fan:
${__formatFan(__info_fan_state)}
`;
}
$("about-hardware").innerHTML = html;
};
var __formatPlatform = function(state) {
return __formatUl([["Base", state.base], ["Serial", state.serial]]);
};
var __formatFan = function(state) {
if (!state.monitored) {
return __formatUl([["Status", "Not monitored"]]);
} else if (state.state === null) {
return __formatUl([["Status", __colored("red", "Not available")]]);
} else {
state = state.state;
let pairs = [
["Status", (state.fan.ok ? __colored("green", "Ok") : __colored("red", "Failed"))],
["Desired speed", `${state.fan.speed}%`],
["PWM", `${state.fan.pwm}`],
];
if (state.hall.available) {
pairs.push(["RPM", __colored((state.fan.ok ? "green" : "red"), state.hall.rpm)]);
}
return __formatUl(pairs);
}
};
var __formatTemp = function(temp) {
let pairs = [];
for (let field of Object.keys(temp).sort()) {
pairs.push([field.toUpperCase(), `${temp[field]}&deg;C`]);
}
return __formatUl(pairs);
};
var __formatThrottling = function(throttling) {
if (throttling !== null) {
let pairs = [];
for (let field of Object.keys(throttling.parsed_flags).sort()) {
let flags = throttling.parsed_flags[field];
let key = tools.upperFirst(field).replace("_", " ");
let value = (flags["now"] ? __colored("red", "RIGHT NOW") : __colored("green", "No"));
if (!throttling.ignore_past) {
value += "; " + (flags["past"] ? __colored("red", "In the past") : __colored("green", "Never"));
}
pairs.push([key, value]);
}
return __formatUl(pairs);
} else {
return "NO DATA";
}
};
var __colored = function(color, text) {
return `<font color="${color}">${text}</font>`;
};
var __setAboutInfoSystem = function(state) {
$("about-version").innerHTML = `
KVMD: <span class="code-comment">${state.kvmd.version}</span><br>
<hr>
Streamer: <span class="code-comment">${state.streamer.version} (${state.streamer.app})</span>
${__formatStreamerFeatures(state.streamer.features)}
<hr>
${state.kernel.system} kernel:
${__formatUname(state.kernel)}
`;
$("kvmd-version-kvmd").innerHTML = state.kvmd.version;
$("kvmd-version-streamer").innerHTML = state.streamer.version;
};
var __formatStreamerFeatures = function(features) {
let pairs = [];
for (let field of Object.keys(features).sort()) {
pairs.push([field, (features[field] ? "Yes" : "No")]);
}
return __formatUl(pairs);
};
var __formatUname = function(kernel) {
let pairs = [];
for (let field of Object.keys(kernel).sort()) {
if (field !== "system") {
pairs.push([tools.upperFirst(field), kernel[field]]);
}
}
return __formatUl(pairs);
};
var __formatUl = function(pairs) {
let text = "<ul>";
for (let pair of pairs) {
text += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`;
}
return text + "</ul>";
};
var __setExtras = function(state) {
let show_hook = null;
let close_hook = null;
let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started));
if (has_webterm) {
let path = "/" + state.webterm.path;
show_hook = function() {
tools.info("Terminal opened: ", path);
$("webterm-iframe").src = path;
};
close_hook = function() {
tools.info("Terminal closed");
$("webterm-iframe").src = "";
};
}
tools.feature.setEnabled($("system-tool-webterm"), has_webterm);
$("webterm-window").show_hook = show_hook;
$("webterm-window").close_hook = close_hook;
__streamer.setJanusEnabled(
(state.janus && (state.janus.enabled || state.janus.started))
|| (state.janus_static && (state.janus_static.enabled || state.janus_static.started))
);
};
var __startSession = function() {
$("link-led").className = "led-yellow";
$("link-led").title = "Connecting...";
let http = tools.makeRequest("GET", "/api/auth/check", function() {
if (http.readyState === 4) {
if (http.status === 200) {
__ws = new WebSocket(`${tools.is_https ? "wss" : "ws"}://${location.host}/api/ws`);
__ws.sendHidEvent = (event) => __sendHidEvent(__ws, event.event_type, event.event);
__ws.onopen = __wsOpenHandler;
__ws.onmessage = __wsMessageHandler;
__ws.onerror = __wsErrorHandler;
__ws.onclose = __wsCloseHandler;
} else if (http.status === 401 || http.status === 403) {
window.onbeforeunload = () => null;
wm.error("Unexpected logout occured, please login again").then(function() {
document.location.href = "/login";
});
} else {
__wsCloseHandler(null);
}
}
});
};
var __ascii_encoder = new TextEncoder("ascii");
var __sendHidEvent = function(ws, event_type, event) {
if (event_type == "key") {
let data = __ascii_encoder.encode("\x01\x00" + event.key);
data[1] = (event.state ? 1 : 0);
ws.send(data);
} else if (event_type == "mouse_button") {
let data = __ascii_encoder.encode("\x02\x00" + event.button);
data[1] = (event.state ? 1 : 0);
ws.send(data);
} else if (event_type == "mouse_move") {
let data = new Uint8Array([
3,
(event.to.x >> 8) & 0xFF, event.to.x & 0xFF,
(event.to.y >> 8) & 0xFF, event.to.y & 0xFF,
]);
ws.send(data);
} else if (event_type == "mouse_relative" || event_type == "mouse_wheel") {
let data;
if (Array.isArray(event.delta)) {
data = new Int8Array(2 + event.delta.length * 2);
let index = 0;
for (let delta of event.delta) {
data[index + 2] = delta["x"];
data[index + 3] = delta["y"];
index += 2;
}
} else {
data = new Int8Array([0, 0, event.delta.x, event.delta.y]);
}
data[0] = (event_type == "mouse_relative" ? 4 : 5);
data[1] = (event.squash ? 1 : 0);
ws.send(data);
}
};
var __wsOpenHandler = function(event) {
tools.debug("Session: socket opened:", event);
$("link-led").className = "led-green";
$("link-led").title = "Connected";
__recorder.setSocket(__ws);
__hid.setSocket(__ws);
__missed_heartbeats = 0;
__ping_timer = setInterval(__pingServer, 1000);
};
var __wsMessageHandler = function(event) {
// tools.debug("Session: received socket data:", event.data);
let data = JSON.parse(event.data);
switch (data.event_type) {
case "pong": __missed_heartbeats = 0; break;
case "info_meta_state": __setAboutInfoMeta(data.event); break;
case "info_hw_state": __setAboutInfoHw(data.event); break;
case "info_fan_state": __setAboutInfoFan(data.event); break;
case "info_system_state": __setAboutInfoSystem(data.event); break;
case "info_extras_state": __setExtras(data.event); break;
case "gpio_model_state": __gpio.setModel(data.event); break;
case "gpio_state": __gpio.setState(data.event); break;
case "hid_keymaps_state": __hid.setKeymaps(data.event); break;
case "hid_state": __hid.setState(data.event); break;
case "atx_state": __atx.setState(data.event); break;
case "msd_state": __msd.setState(data.event); break;
case "streamer_state": __streamer.setState(data.event); break;
case "streamer_ocr_state": __ocr.setState(data.event); break;
}
};
var __wsErrorHandler = function(event) {
tools.error("Session: socket error:", event);
if (__ws) {
__ws.onclose = null;
__ws.close();
__wsCloseHandler(null);
}
};
var __wsCloseHandler = function(event) {
tools.debug("Session: socket closed:", event);
$("link-led").className = "led-gray";
if (__ping_timer) {
clearInterval(__ping_timer);
__ping_timer = null;
}
__ocr.setState(null);
__gpio.setState(null);
__hid.setSocket(null);
__recorder.setSocket(null);
__atx.setState(null);
__msd.setState(null);
__streamer.setState(null);
__ws = null;
setTimeout(function() {
$("link-led").className = "led-yellow";
setTimeout(__startSession, 500);
}, 500);
};
var __pingServer = function() {
try {
__missed_heartbeats += 1;
if (__missed_heartbeats >= 15) {
throw new Error("Too many missed heartbeats");
}
__ws.send("{\"event_type\": \"ping\", \"event\": {}}");
} catch (err) {
__wsErrorHandler(err.message);
}
};
__init__();
}

482
patches/streamer.py.1 Normal file
View File

@@ -0,0 +1,482 @@
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.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 io
import signal
import asyncio
import asyncio.subprocess
import dataclasses
import functools
from typing import AsyncGenerator
from typing import Any
import aiohttp
from PIL import Image as PilImage
from ...logging import get_logger
from ... import tools
from ... import aiotools
from ... import aioproc
from ... import htclient
# =====
@dataclasses.dataclass(frozen=True)
class StreamerSnapshot:
online: bool
width: int
height: int
headers: tuple[tuple[str, str], ...]
data: bytes
async def make_preview(self, max_width: int, max_height: int, quality: int) -> bytes:
assert max_width >= 0
assert max_height >= 0
assert quality > 0
if max_width == 0 and max_height == 0:
max_width = self.width // 5
max_height = self.height // 5
else:
max_width = min((max_width or self.width), self.width)
max_height = min((max_height or self.height), self.height)
if (max_width, max_height) == (self.width, self.height):
return self.data
return (await aiotools.run_async(self.__inner_make_preview, max_width, max_height, quality))
@functools.lru_cache(maxsize=1)
def __inner_make_preview(self, max_width: int, max_height: int, quality: int) -> bytes:
with io.BytesIO(self.data) as snapshot_bio:
with io.BytesIO() as preview_bio:
with PilImage.open(snapshot_bio) as image:
image.thumbnail((max_width, max_height), PilImage.Resampling.LANCZOS)
image.save(preview_bio, format="jpeg", quality=quality)
return preview_bio.getvalue()
class _StreamerParams:
__DESIRED_FPS = "desired_fps"
__QUALITY = "quality"
__RESOLUTION = "resolution"
__AVAILABLE_RESOLUTIONS = "available_resolutions"
__H264_BITRATE = "h264_bitrate"
__H264_GOP = "h264_gop"
def __init__( # pylint: disable=too-many-arguments
self,
quality: int,
resolution: str,
available_resolutions: list[str],
desired_fps: int,
desired_fps_min: int,
desired_fps_max: int,
h264_bitrate: int,
h264_bitrate_min: int,
h264_bitrate_max: int,
h264_gop: int,
h264_gop_min: int,
h264_gop_max: int,
) -> None:
self.__has_quality = bool(quality)
self.__has_resolution = bool(resolution)
self.__has_h264 = bool(h264_bitrate)
self.__params: dict = {self.__DESIRED_FPS: min(max(desired_fps, desired_fps_min), desired_fps_max)}
self.__limits: dict = {self.__DESIRED_FPS: {"min": desired_fps_min, "max": desired_fps_max}}
if self.__has_quality:
self.__params[self.__QUALITY] = quality
if self.__has_resolution:
self.__params[self.__RESOLUTION] = resolution
self.__limits[self.__AVAILABLE_RESOLUTIONS] = available_resolutions
if self.__has_h264:
self.__params[self.__H264_BITRATE] = min(max(h264_bitrate, h264_bitrate_min), h264_bitrate_max)
self.__limits[self.__H264_BITRATE] = {"min": h264_bitrate_min, "max": h264_bitrate_max}
self.__params[self.__H264_GOP] = min(max(h264_gop, h264_gop_min), h264_gop_max)
self.__limits[self.__H264_GOP] = {"min": h264_gop_min, "max": h264_gop_max}
def get_features(self) -> dict:
return {
self.__QUALITY: self.__has_quality,
self.__RESOLUTION: self.__has_resolution,
"h264": self.__has_h264,
}
def get_limits(self) -> dict:
limits = dict(self.__limits)
if self.__has_resolution:
limits[self.__AVAILABLE_RESOLUTIONS] = list(limits[self.__AVAILABLE_RESOLUTIONS])
return limits
def get_params(self) -> dict:
return dict(self.__params)
def set_params(self, params: dict) -> None:
new_params = dict(self.__params)
if self.__QUALITY in params and self.__has_quality:
new_params[self.__QUALITY] = min(max(params[self.__QUALITY], 1), 100)
if self.__RESOLUTION in params and self.__has_resolution:
if params[self.__RESOLUTION] in self.__limits[self.__AVAILABLE_RESOLUTIONS]:
new_params[self.__RESOLUTION] = params[self.__RESOLUTION]
for (key, enabled) in [
(self.__DESIRED_FPS, True),
(self.__H264_BITRATE, self.__has_h264),
(self.__H264_GOP, self.__has_h264),
]:
if key in params and enabled:
if self.__check_limits_min_max(key, params[key]):
new_params[key] = params[key]
self.__params = new_params
def __check_limits_min_max(self, key: str, value: int) -> bool:
return (self.__limits[key]["min"] <= value <= self.__limits[key]["max"])
class Streamer: # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=too-many-arguments,too-many-locals
self,
reset_delay: float,
shutdown_delay: float,
state_poll: float,
unix_path: str,
timeout: float,
process_name_prefix: str,
pre_start_cmd: list[str],
pre_start_cmd_remove: list[str],
pre_start_cmd_append: list[str],
cmd: list[str],
cmd_remove: list[str],
cmd_append: list[str],
post_stop_cmd: list[str],
post_stop_cmd_remove: list[str],
post_stop_cmd_append: list[str],
**params_kwargs: Any,
) -> None:
self.__reset_delay = reset_delay
self.__shutdown_delay = shutdown_delay
self.__state_poll = state_poll
self.__unix_path = unix_path
self.__timeout = timeout
self.__process_name_prefix = process_name_prefix
self.__pre_start_cmd = tools.build_cmd(pre_start_cmd, pre_start_cmd_remove, pre_start_cmd_append)
self.__cmd = tools.build_cmd(cmd, cmd_remove, cmd_append)
self.__post_stop_cmd = tools.build_cmd(post_stop_cmd, post_stop_cmd_remove, post_stop_cmd_append)
self.__params = _StreamerParams(**params_kwargs)
self.__stop_task: (asyncio.Task | None) = None
self.__stop_wip = False
self.__streamer_task: (asyncio.Task | None) = None
self.__streamer_proc: (asyncio.subprocess.Process | None) = None # pylint: disable=no-member
self.__http_session: (aiohttp.ClientSession | None) = None
self.__snapshot: (StreamerSnapshot | None) = None
self.__notifier = aiotools.AioNotifier()
# =====
@aiotools.atomic_fg
async def ensure_start(self, reset: bool) -> None:
if not self.__streamer_task or self.__stop_task:
logger = get_logger(0)
if self.__stop_task:
if not self.__stop_wip:
self.__stop_task.cancel()
await asyncio.gather(self.__stop_task, return_exceptions=True)
logger.info("Streamer stop cancelled")
return
else:
await asyncio.gather(self.__stop_task, return_exceptions=True)
if reset and self.__reset_delay > 0:
logger.info("Waiting %.2f seconds for reset delay ...", self.__reset_delay)
await asyncio.sleep(self.__reset_delay)
logger.info("Starting streamer ...")
await self.__inner_start()
@aiotools.atomic_fg
async def ensure_stop(self, immediately: bool) -> None:
if self.__streamer_task:
logger = get_logger(0)
if immediately:
if self.__stop_task:
if not self.__stop_wip:
self.__stop_task.cancel()
await asyncio.gather(self.__stop_task, return_exceptions=True)
logger.info("Stopping streamer immediately ...")
await self.__inner_stop()
else:
await asyncio.gather(self.__stop_task, return_exceptions=True)
else:
logger.info("Stopping streamer immediately ...")
await self.__inner_stop()
elif not self.__stop_task:
async def delayed_stop() -> None:
try:
await asyncio.sleep(self.__shutdown_delay)
self.__stop_wip = True
logger.info("Stopping streamer after delay ...")
await self.__inner_stop()
finally:
self.__stop_task = None
self.__stop_wip = False
logger.info("Planning to stop streamer in %.2f seconds ...", self.__shutdown_delay)
self.__stop_task = asyncio.create_task(delayed_stop())
def is_working(self) -> bool:
# Запущено и не планирует останавливаться
return bool(self.__streamer_task and not self.__stop_task)
# =====
def set_params(self, params: dict) -> None:
assert not self.__streamer_task
return self.__params.set_params(params)
def get_params(self) -> dict:
return self.__params.get_params()
# =====
async def get_state(self) -> dict:
streamer_state = None
if self.__streamer_task:
session = self.__ensure_http_session()
try:
async with session.get(self.__make_url("state")) as response:
htclient.raise_not_200(response)
streamer_state = (await response.json())["result"]
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError):
pass
except Exception:
get_logger().exception("Invalid streamer response from /state")
snapshot: (dict | None) = None
if self.__snapshot:
snapshot = dataclasses.asdict(self.__snapshot)
del snapshot["headers"]
del snapshot["data"]
return {
"limits": self.__params.get_limits(),
"params": self.__params.get_params(),
"snapshot": {"saved": snapshot},
"streamer": streamer_state,
"features": self.__params.get_features(),
}
async def poll_state(self) -> AsyncGenerator[dict, None]:
def signal_handler(*_: Any) -> None:
get_logger(0).info("Got SIGUSR2, checking the stream state ...")
self.__notifier.notify()
get_logger(0).info("Installing SIGUSR2 streamer handler ...")
asyncio.get_event_loop().add_signal_handler(signal.SIGUSR2, signal_handler)
waiter_task: (asyncio.Task | None) = None
prev_state: dict = {}
while True:
state = await self.get_state()
if state != prev_state:
yield state
prev_state = state
if waiter_task is None:
waiter_task = asyncio.create_task(self.__notifier.wait())
if waiter_task in (await aiotools.wait_first(
asyncio.ensure_future(asyncio.sleep(self.__state_poll)),
waiter_task,
))[0]:
waiter_task = None
# =====
async def take_snapshot(self, save: bool, load: bool, allow_offline: bool) -> (StreamerSnapshot | None):
if load:
return self.__snapshot
else:
logger = get_logger()
session = self.__ensure_http_session()
try:
async with session.get(self.__make_url("snapshot")) as response:
htclient.raise_not_200(response)
online = (response.headers["X-UStreamer-Online"] == "true")
if online or allow_offline:
snapshot = StreamerSnapshot(
online=online,
width=int(response.headers["X-UStreamer-Width"]),
height=int(response.headers["X-UStreamer-Height"]),
headers=tuple(
(key, value)
for (key, value) in tools.sorted_kvs(dict(response.headers))
if key.lower().startswith("x-ustreamer-") or key.lower() in [
"x-timestamp",
"access-control-allow-origin",
"cache-control",
"pragma",
"expires",
]
),
data=bytes(await response.read()),
)
if save:
self.__snapshot = snapshot
self.__notifier.notify()
return snapshot
logger.error("Stream is offline, no signal or so")
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError) as err:
logger.error("Can't connect to streamer: %s", tools.efmt(err))
except Exception:
logger.exception("Invalid streamer response from /snapshot")
return None
def remove_snapshot(self) -> None:
self.__snapshot = None
# =====
@aiotools.atomic_fg
async def cleanup(self) -> None:
await self.ensure_stop(immediately=True)
if self.__http_session:
await self.__http_session.close()
self.__http_session = None
# =====
def __ensure_http_session(self) -> aiohttp.ClientSession:
if not self.__http_session:
kwargs: dict = {
"headers": {"User-Agent": htclient.make_user_agent("KVMD")},
"connector": aiohttp.UnixConnector(path=self.__unix_path),
"timeout": aiohttp.ClientTimeout(total=self.__timeout),
}
self.__http_session = aiohttp.ClientSession(**kwargs)
return self.__http_session
def __make_url(self, handle: str) -> str:
assert not handle.startswith("/"), handle
return f"http://localhost:0/{handle}"
# =====
@aiotools.atomic_fg
async def __inner_start(self) -> None:
assert not self.__streamer_task
await self.__run_hook("PRE-START-CMD", self.__pre_start_cmd)
self.__streamer_task = asyncio.create_task(self.__streamer_task_loop())
@aiotools.atomic_fg
async def __inner_stop(self) -> None:
assert self.__streamer_task
self.__streamer_task.cancel()
await asyncio.gather(self.__streamer_task, return_exceptions=True)
await self.__kill_streamer_proc()
await self.__run_hook("POST-STOP-CMD", self.__post_stop_cmd)
self.__streamer_task = None
# =====
async def __streamer_task_loop(self) -> None: # pylint: disable=too-many-branches
logger = get_logger(0)
while True: # pylint: disable=too-many-nested-blocks
try:
await self.__start_streamer_proc()
assert self.__streamer_proc is not None
await aioproc.log_stdout_infinite(self.__streamer_proc, logger)
raise RuntimeError("Streamer unexpectedly died")
except asyncio.CancelledError:
break
except Exception:
if self.__streamer_proc:
logger.exception("Unexpected streamer error: pid=%d", self.__streamer_proc.pid)
else:
logger.exception("Can't start streamer")
await self.__kill_streamer_proc()
await asyncio.sleep(1)
def __make_cmd(self, cmd: list[str]) -> list[str]:
return [
part.format(
unix=self.__unix_path,
process_name_prefix=self.__process_name_prefix,
**self.__params.get_params(),
)
for part in cmd
]
async def __run_hook(self, name: str, cmd: list[str]) -> None:
logger = get_logger()
cmd = self.__make_cmd(cmd)
logger.info("%s: %s", name, tools.cmdfmt(cmd))
try:
await aioproc.log_process(cmd, logger, prefix=name)
except Exception as err:
logger.exception("Can't execute command: %s", err)
async def __start_streamer_proc(self) -> None:
assert self.__streamer_proc is None
cmd = self.__make_cmd(self.__cmd)
self.__streamer_proc = await aioproc.run_process(cmd)
get_logger(0).info("Started streamer pid=%d: %s", self.__streamer_proc.pid, tools.cmdfmt(cmd))
async def __kill_streamer_proc(self) -> None:
if self.__streamer_proc:
await aioproc.kill_process(self.__streamer_proc, 1, get_logger(0))
self.__streamer_proc = None

47
pi-temp Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Script: pi-temp.sh
# This should work on both arch and ubuntu/debian
# Purpose: Display the ARM CPU and GPU temperatures of Raspberry Pi 2/3/4/Zero
# Edited by: srepac <srepac@kvmnerds.com>
# convert C to F and show both temps in output
# -------------------------------------------------------
# install basic calculator for conversions
if [ `which bc | wc -l` -le 0 ]; then
sudo apt-get install bc -y
fi
MODEL=$(tr -d '\0' </proc/device-tree/model)
# code section to get Pi RAM size
RAMGB=$( sudo vcgencmd get_config total_mem | awk -F= '{NUM = $2} END {
if ( NUM < 1024 ) print NUM "MB";
else print (NUM / 1024) "GB"; }' )
AVAIL=$( free -m | grep Mem | awk '{print $NF}' )
printf "$MODEL ${RAMGB} Hostname: $(hostname)\n$(date) Load average:$(uptime | awk -F: '{print $NF}') Avail RAM: ${AVAIL}MB\n"
GPUTEMP=`sudo $(which vcgencmd) measure_temp | awk -F= '{print $2}' | sed "s/'C//g"`
CPU=$(</sys/class/thermal/thermal_zone0/temp)
cpuC=`echo "scale=3; $CPU / 1000" | bc`
cpuF=`echo "scale=2; 9/5 * ${cpuC} + 32" | bc`
gpuF=`echo "scale=2; 9/5 * ${GPUTEMP} + 32" | bc`
#echo "$(date) @ $(hostname)"
echo "-------------------------------------------"
printf "CPU => ${cpuC}'C\t${cpuF}'F\n"
printf "GPU => ${GPUTEMP}'C\t${gpuF}'F\n"
MHZ=`sudo cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq`
#echo "CPU0 MHZ: $((MHZ/1000))"
#echo "CPU (All) MHz: "
count=0
for i in `sudo cat /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq`; do
cpu=`echo "scale=2; $i / 1000" | bc`
echo "CPU${count} MHz: $cpu"
count=`expr $count + 1`
done
sudo vcgencmd measure_volts | sed 's/volt=/vCore /g'
sudo vcgencmd get_throttled

51
pikvm-info Normal file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# Put this file into /usr/local/bin then you can run pikvm-info anywhere while logged in to Pi-KVM terminal/ssh
#
uptime
NAME=$( egrep ^NAME= /etc/os-release | cut -d'"' -f2 )
echo "Host OS: $NAME `uname -r` `uname -m`"
# pistat is built-in to pi-kvm (/usr/local/bin)
pistat && echo
printf "%-32s\t%s\n" "Version" "Package-Name" "---------------------------------" "-----------------------------"
get-packages() {
if [[ $( echo $NAME | cut -d' ' -f1 ) == "Arch" ]]; then
if [ ! -e $TMPFILE ]; then
pacman -Q | awk '{print $2, $1}' > $TMPFILE
fi
else
KVMDPLAT=$( grep kvmd-platform /var/cache/kvmd/installed_ver.txt | awk '{print $4}' | awk -F\/ '{print $NF}' | tail -1 )
if [[ "$KVMDPLAT" != "" ]]; then
KVMDVER=$( echo $KVMDPLAT | cut -d'-' -f6 )
PLATFORM=$( echo $KVMDPLAT | cut -d'-' -f1,2,3,4,5 )
printf "%-32s\t%s\n" $KVMDVER $PLATFORM
fi
USTREAMVER=$( /usr/bin/ustreamer -v )
printf "%-32s\t%s\n" $USTREAMVER "ustreamer"
#JANUSVER=$( /usr/bin/janus -V | tail -1 | awk '{print $NF}' )
JANUSVER=$( /usr/bin/janus -V | egrep 'version|janus' | awk '{print $NF}' | sed -e 's|(||g' -e 's|)||g' )
printf "%-32s\t%s\n" $JANUSVER "janus"
TTYDVER=$( ttyd -v | awk '{print $NF}' )
printf "%-32s\t%s\n" $TTYDVER "ttyd"
if [ ! -e $TMPFILE ]; then
apt list 2> /dev/null | egrep 'upgradable|installed' | grep -v ^$ | awk '{print $2, $1}' > $TMPFILE
fi
fi
} # end get-packages
TMPFILE="/tmp/pacmanquery"
get-packages
PACKAGES="janus pikvm kvmd ustreamer nginx wpa wireless python3/stable python$ firmware raspberrypi tailscale"
for PKG in $( echo $PACKAGES ); do
if [ $(grep -w $PKG $TMPFILE | wc -l) -gt 0 ]; then
printf "%-32s\t%s\n" $(grep -w $PKG $TMPFILE | sed 's/-[1-9]+//g' | awk -F\/ '{print $1}' )
fi
done | sort -u | sort -k2

110
pistat Normal file
View File

@@ -0,0 +1,110 @@
#!/bin/bash
# Copy this into /usr/local/bin/pistat
# This version will show Pi RAM size and will work in Raspbian also
# sample output: Raspberry Pi 4 Model B 1.2 4GB
if [[ $( whoami ) == "root" ]]; then
VCGENCMD="/usr/bin/vcgencmd"
else
VCGENCMD="sudo /usr/bin/vcgencmd"
fi
if [ -t 1 ]; then
color_comment=$(echo -e '\e[1;30m')
color_value=$(echo -e '\e[1;35m')
color_ok=$(echo -e '\e[1;32m')
color_fail=$(echo -e '\e[1;31m')
color_reset=$(echo -e '\e[0m')
else
color_comment=""
color_value=""
color_ok=""
color_fail=""
color_reset=""
fi
no="${color_ok}no${color_reset}"
yes="${color_fail}yes${color_reset}"
# code section to get Pi RAM size
RAMGB=$( $VCGENCMD get_config total_mem | awk -F= '{NUM = $2} END { if ( NUM < 1024 ) print NUM "MB"; else print (NUM / 1024) "GB"; }' )
if [ -f /proc/device-tree/model ]; then
echo "${color_reset}# $(tr -d '\0' < /proc/device-tree/model) ${RAMGB}${color_reset}"
else
RAM=$( echo "( $( free -m | grep Mem: | awk '{print $2}' ) + 340 ) / 1024" | bc )
TMPFILE="/tmp/dmidecode.system"; rm -f $TMPFILE
dmidecode -t system > $TMPFILE
echo "${color_reset}# $(grep -i version $TMPFILE | cut -d':' -f2 | sed 's/^ //g') $(grep Product $TMPFILE | cut -d: -f2 | cut -d\( -f1 | sed 's/^ //g') ${RAM}GB${color_reset}"
fi
if [ -e /proc/device-tree/serial-number ]; then
echo "${color_reset}Serial number: $(tr -d '\0' < /proc/device-tree/serial-number) ${color_reset}"
fi
echo
if [ -e /sys/class/thermal/thermal_zone0/temp ]; then
echo "CPU temp: ${color_value}$(echo "scale=2; $(</sys/class/thermal/thermal_zone0/temp) / 1000" | bc -l)'C${color_reset}"
gpu_temp=$($VCGENCMD measure_temp | cut -d'=' -f2 | cut -d"'" -f1 )
else
CPUTEMP=$(sensors 2> /dev/null | egrep -A 2 'coretemp|k10temp' | egrep '^temp|^Core' | awk '{print $2}' | sed -e 's/°C//g' -e 's/+//g')
echo "CPU temp: ${color_value}${CPUTEMP}'C${color_reset}"
gpu_temp=$(sensors 2> /dev/null | grep -A 2 radeon | grep ^temp | awk '{print $2}' | sed -e 's/°C//g' -e 's/+//g')
fi
echo "GPU temp: ${color_value}${gpu_temp}'C${color_reset}"
flags=$($VCGENCMD get_throttled)
flags="${flags#*=}"
echo
echo "Throttled flags: ${color_value}${flags}${color_reset}"
echo
echo -n "Throttled now: "
((($flags&0x4)!=0)) && echo "${yes}" || echo "${no}"
echo -n "Throttled past: "
((($flags&0x40000)!=0)) && echo "${yes}" || echo "${no}"
echo
echo -n "Undervoltage now: "
((($flags&0x1)!=0)) && echo "${yes}" || echo "${no}"
echo -n "Undervoltage past: "
((($flags&0x10000)!=0)) && echo "${yes}" || echo "${no}"
echo
echo -n "Frequency capped now: "
((($flags&0x2)!=0)) && echo "${yes}" || echo "${no}"
echo -n "Frequency capped past: "
((($flags&0x20000)!=0)) && echo "${yes}" || echo "${no}"
# =====
# https://stackoverflow.com/questions/13889659/read-a-file-by-bytes-in-bash
read8() {
local _r8_var=${1:-OUTBIN} _r8_car LANG=C IFS=
read -r -d '' -n 1 _r8_car
printf -v $_r8_var %d "'"$_r8_car
}
read16() {
local _r16_var=${1:-OUTBIN} _r16_lb _r16_hb
read8 _r16_hb && read8 _r16_lb
printf -v $_r16_var %d $(( _r16_hb<<8 | _r16_lb ))
}
read32() {
local _r32_var=${1:-OUTBIN} _r32_lw _r32_hw
read16 _r32_hw && read16 _r32_lw
printf -v $_r32_var %d $(( _r32_hw<<16| _r32_lw ))
}
power=/proc/device-tree/chosen/power
if [ -f $power/max_current ]; then
read32 value < $power/max_current
echo
echo "Max power supply current:" ${color_value}$(bc <<< "scale=1; $value / 1000")A${color_reset}
fi
if [ -f $power/usb_max_current_enable ]; then
read32 value < $power/usb_max_current_enable
echo "USB max current enabled: " ${color_value}$(test $value -ne 0 && echo yes || echo no)${color_reset}
fi
if [ -f $power/usb_over_current_detected ]; then
read32 value < $power/usb_over_current_detected
echo "USB overcurrent detected:" $(test $value -ne 0 && echo $yes || echo $no)
fi

BIN
sources/WiringPi-3.6.zip Normal file

Binary file not shown.

BIN
sources/ustreamer-6.12.zip Normal file

Binary file not shown.

112
uninstall-pikvm.sh Normal file
View File

@@ -0,0 +1,112 @@
#!/bin/bash
# Script written by @srepac as requested by @Mark Jim
# This script perform uninstall of pikvm from raspbian. This performs the following:
#
# 1. Stop/disable kvmd services
# 2. Remove the main kvmd package files based on what was installed (see /var/cache/kvmd/installed_ver.txt file)
#
# CAVEATS:
# 1. Script does not remove /usr/bin/ttyd (webterm), /usr/bin/ustreamer, /usr/bin/ustreamer-dump
# 2. Script does not remove /usr/bin/janus (webrtc) and all its dependent files also
# 3. Script does not remove any directories; you may end up with empty directories
###
# CHANGELOG:
# 1.0 20220218 created script
# 1.1 20220220 confirm uninstall and add -f option to perform destructive commands
# 1.2 20220225 restore original /etc/motd
# 2.0 20220225 save custom configs for possible restores during re-install later
VER=2.0
save-configs() { ### save config files inside /etc/kvmd in case user re-installs pikvm later
if [[ $f_flag -eq 1 ]]; then
printf "\n-> Saving config files\n"
# Save passwd files used by PiKVM
cp /etc/kvmd/htpasswd /etc/kvmd/htpasswd.save
cp /etc/kvmd/ipmipasswd /etc/kvmd/ipmipasswd.save
cp /etc/kvmd/vncpasswd /etc/kvmd/vncpasswd.save
# Save webUI name and overrides
cp /etc/kvmd/meta.yaml /etc/kvmd/meta.yaml.save
cp /etc/kvmd/override.yaml /etc/kvmd/override.yaml.save
cp /etc/kvmd/web.css /etc/kvmd/web.css.save
# Save Janus configs
#cp /etc/kvmd/janus/janus.cfg /etc/kvmd/janus/janus.cfg.save
# Save sudoers.d/99_kvmd
cp /etc/sudoers.d/99_kvmd /etc/sudoers.d/99_kvmd.save
cp /etc/sudoers.d/custom_commands /etc/sudoers.d/custom_commands.save
fi
}
stop-disable-kvmd() {
#for i in $( systemctl | grep kvmd | grep -v var | awk '{print $1}')
for i in $( systemctl | grep kvmd | grep -v var | awk '$1||$2 ~ /kvmd/ {print $2, $1}' | sed 's/loaded //g' | cut -d' ' -f1 )
do
echo "-> Stopping/disabling ${i} ..."
if [[ $f_flag -eq 1 ]]; then systemctl disable --now $i; fi
done
} # end stop-disable-kvmd
# Determine what kvmd version was installed last
remove-kvmd-package() {
printf "\nProceeding to remove kvmd package files\n" | tee -a $LOGFILE
#KVMDVER=$( egrep 'kvmd-[0-9]' $INSTLOG | awk '{print $4}' | cut -d'-' -f2 | tail -1 )
KVMDVER=$( pikvm-info | grep kvmd-platform | awk '{print $1}' )
KVMDPKG="kvmd-${KVMDVER}"
echo "Uninstalling ${KVMDPKG} from this system."
for file in $( tar tvfJ /var/cache/kvmd/${KVMDPKG}* | awk '{print $NF}' | grep -v '/$' )
do
echo "-> Deleting /$file ..."
if [[ $f_flag -eq 1 ]]; then rm /$file; fi
done
} # end remove-kvmd-package
restore-motd() {
if [[ $f_flag -eq 1 ]]; then
if [ -e /etc/motd.orig ]; then cp -f /etc/motd.orig /etc/motd; fi
fi
cat /etc/motd
} # end restore-motd
are-you-sure() {
invalidinput=1
while [ $invalidinput -eq 1 ]; do
read -p "Uninstall PiKVM from this system. Are you sure? [y/n] " SURE
case $SURE in
Y|y) invalidinput=0 ;;
N|n) echo "Exiting."; exit 0 ;;
*) echo "Invalid input. try again."; invalidinput=1 ;;
esac
done
} # end are-you-sure fn
### MAIN STARTS HERE ###
if [ -e /usr/local/bin/rw ]; then rw; fi
mkdir -p /var/cache/kvmd # create directory in case it hasn't been created yet (e.g. installer hasn't been run)
export INSTLOG="/var/cache/kvmd/installed_ver.txt"
export LOGFILE="/var/cache/kvmd/uninstall.log"; rm -f $LOGFILE
if [ ! -e $INSTLOG ]; then
echo "Install log missing. Nothing to do." | tee -a $LOGFILE
exit 1
fi
if [[ "$1" == "-f" ]]; then
printf "\n*** Actually perform destructive commands option set.\n\n"
f_flag=1
else
printf "\n*** Only SHOWING what will be performed. Re-run with -f to actually perform destructive commands.\n\n"
f_flag=0
fi
are-you-sure
save-configs | tee -a $LOGFILE
stop-disable-kvmd | tee -a $LOGFILE
restore-motd | tee -a $LOGFILE
remove-kvmd-package | tee -a $LOGFILE
if [ -e /usr/local/bin/ro ]; then ro; fi

13
web.css Normal file
View File

@@ -0,0 +1,13 @@
/* Here you can customize the Web UI */
div.stream-box-mouse-dot { /* required for kvmd 3.305+ */
cursor: crosshair !important; /* crosshair instead of blue blob */
}
div.stream-box-mouse-enabled {
cursor: crosshair !important; /* crosshair instead of blue blob */
}
div.window.window-full-tab {
border: 0px !important; /* removes window-full-tab border by adding !important to override priority, the border cause jiggling again due to window-full-tab getting overrided by window-active border styling */
}
div#stream-window.window-active:fullscreen div#stream-box div#stream-fullscreen-active {
box-shadow: none; /* removes fullscreen ~2px border */
}

BIN
x86-mods.tar Normal file

Binary file not shown.