mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
重构更新
This commit is contained in:
parent
fdf58ea6f7
commit
99f2a1b09a
93
README.MD
Normal file
93
README.MD
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# KVMD-ARMBIAN
|
||||||
|
This project supports non-Raspberry Pi device to run pikvm on armbian or Raspberry Pi boards on Raspbian Bookworm
|
||||||
|
- It also supports x86 pikvm with USB uart + ch9329 serial HID if you use the install-x86.sh script and follow the How to Install PiKVM x86.pdf
|
||||||
|
- As of 04/21/2024, I have left the PiKVM Discord so if you need assistance, please email me: srepac@kvmnerds.com
|
||||||
|
- You can also try and see if I'm on my own Discord at https://discord.gg/YaJ87sVznc
|
||||||
|
|
||||||
|
# Install
|
||||||
|
KVMD Install for armbian/raspbian
|
||||||
|
|
||||||
|
It supports Allwinner, Amlogic and Rockchip based tv box, tested on phicomm n1, mxq pro 4k, tqc a01.
|
||||||
|
Chipset needs to support USB OTG feature, lots of old amglogic chipset does not support otg feature, such as s805 and s905.
|
||||||
|
You should install armbian with debian jammy as jammy has python 3.10 or python 3.11 (starting with kvmd 3.217) which the newer kvmd code is based on.
|
||||||
|
Then run this script (2x) to install pikvm.
|
||||||
|
|
||||||
|
This also works on rpi boards running Raspbian Bookworm.
|
||||||
|
|
||||||
|
Install script is a fork from @srepac raspbian pikvm install script.
|
||||||
|
|
||||||
|
**NOTE: all commands need to be run as root user.**
|
||||||
|
|
||||||
|
Original Script [http://148.135.104.55/RPiKVM/install-pikvm-raspbian.sh]
|
||||||
|
|
||||||
|
# Hardware for kvmd-armbian project
|
||||||
|
* A tv box/arm board that supports otg feature:
|
||||||
|
- Tests on phicomm n1(Amlogic s905d), mxq pro 4k (rk322x), tqc a01(Allwinner H6).
|
||||||
|
- If you are using arm board you can remove gpio patch to enable gpio feature.
|
||||||
|
* Video capture device:
|
||||||
|
- HDMI to USB dongle (30 RMB On taobao, 10$ on aliexpress.) or USB HDMI loop capture dongle
|
||||||
|
- cheap hdmi to usb dongle all use physics USB2.0 port, but fake USB3.0(USB 5GBPS, USB3.2GEN1) version supports 720P 60FPS,
|
||||||
|
usb 2.0 version only supports 720P 30FPS.
|
||||||
|
* USB-A to USB-A cable or micro-USB to USB-A cable:
|
||||||
|
- Recommended to cut off usb cable's power line, otherwise it might cause otg disconnect.
|
||||||
|
|
||||||
|
## Step 1
|
||||||
|
- Flash armbian debian [Recommended jammy] for your tv box (If kernel not support otg you should build a kernel enable otg features)
|
||||||
|
- Flash armbian debian jammy for all others
|
||||||
|
|
||||||
|
## Step 2 (skip this step if running on Orange Pi zero and one boards)
|
||||||
|
- Modify your dtb file to enable otg feature. Change dr_mode from host to peripheral for otg usb port.
|
||||||
|
- If you use rk322x (rk3228A rk3228B rk3229) series chipset, you can use dtb/4.4/rk332x-box.dtb
|
||||||
|
- Add the following to your /boot/armbianEnv.txt file
|
||||||
|
```
|
||||||
|
overlays=usbhost0 usbhost1 usbhost2 usbhost3
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE: Skip steps 1 and 2 if you are running raspbian bookworm on rpi boards**
|
||||||
|
## Step 3 - Perform part 1 of install
|
||||||
|
```
|
||||||
|
apt update && apt upgrade -y
|
||||||
|
apt install -y git vim make python3-dev gcc
|
||||||
|
git clone https://github.com/srepac/kvmd-armbian.git
|
||||||
|
cd kvmd-armbian
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
This will ask you to press ENTER to reboot after part 1 completes.
|
||||||
|
|
||||||
|
## Step 4 - Perform part 2 of install
|
||||||
|
- run install.sh again after reboot os to perform part 2 of install. **NOTE: May require one more reboot if missing /dev/kvmd-hid-[keyboard|mouse]**
|
||||||
|
```
|
||||||
|
cd kvmd-armbian
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
- Enjoy
|
||||||
|
|
||||||
|
|
||||||
|
# Updating RPiKVM
|
||||||
|
Update armbian pikvm anytime in order to take advantage of new features/updates.
|
||||||
|
|
||||||
|
- run the update-rpikvm.sh script to perform update
|
||||||
|
```
|
||||||
|
wget -O /usr/local/bin/update-rpikvm.sh http://148.135.104.55/RPiKVM/update-rpikvm.sh
|
||||||
|
update-rpikvm.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Tested device
|
||||||
|
- Phicomm N1
|
||||||
|
- TQC A01 (Ethernet port not working, only support wireless.)
|
||||||
|
- RK322x based tvbox (MXQ, V88)
|
||||||
|
- S905L2 based tvbox
|
||||||
|
- Orange pi zero (tested by @MrSuicideParrot)
|
||||||
|
- Orange Pi Zero and One (tested by @srepac)
|
||||||
|
- Nano Pi Neo, Rock64, and Orange Pi Zero Plus (tested by @srepac)
|
||||||
|
- Libre Computer Le Potato, La Frite 1GB, Renegade ROC-RK3328-CC and ALL-H3-CC H5 2GB (tested by @srepac)
|
||||||
|
- Inovato Quadra tv box (tested by @srepac)
|
||||||
|
- Big Tree Tech CB1+Rpi4 board (WIP by @srepac)
|
||||||
|
- Orange Pi PC+ and orange pi 3 (tested by @ducs4rs)
|
||||||
|
- RPi4B + PiKVM V3 HAT on Raspberry Pi OS bookworm (tested by @srepac on 10/19/23)
|
||||||
|
- RPi4B + BliKVM V3 HAT on Raspberry Pi OS bookworm (tested by @srepac)
|
||||||
|
- RPiCM4 + BliKVM V2 pcie HAT on Raspberry Pi OS bookworm (tested by @srepac)
|
||||||
|
- RPiCM4 + Geekworm A8 pcie HAT on Raspberry Pi OS bookworm (tested by @srepac)
|
||||||
|
- RPiCM4 + Geekworm X650 pcie HAT on Raspberry Pi OS bookworm (tested by @srepac)
|
||||||
|
- RPiCM4 + Geekworm X635 HAT on Raspberry Pi OS bookworm (tested by @srepac)
|
||||||
45
README.md
45
README.md
@ -6,29 +6,6 @@
|
|||||||
|
|
||||||
One-KVM 是基于经济计算机硬件(目前为玩客云和 X64 兼容机)和PiKVM软件的硬件级远程控制项目。KVM over IP 可以远程管理服务器或工作站,实现无侵入式控制,无论被控机为什么操作系统或是否安装了操作系统,具有更广泛的适用性。此项目基于 [PiKVM](https://github.com/pikvm/pikvm),和基于远控软件的远程管理方式不同,无需在被控电脑安装任何软件,实现无侵入式控制。
|
One-KVM 是基于经济计算机硬件(目前为玩客云和 X64 兼容机)和PiKVM软件的硬件级远程控制项目。KVM over IP 可以远程管理服务器或工作站,实现无侵入式控制,无论被控机为什么操作系统或是否安装了操作系统,具有更广泛的适用性。此项目基于 [PiKVM](https://github.com/pikvm/pikvm),和基于远控软件的远程管理方式不同,无需在被控电脑安装任何软件,实现无侵入式控制。
|
||||||
|
|
||||||
|
|
||||||
**功能特性**
|
|
||||||
|
|
||||||
主要功能比较,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 |
|
|
||||||
|
|
||||||
|
|
||||||
### 快速开始
|
### 快速开始
|
||||||
|
|
||||||
**方式一:直刷 One-KVM 镜像**
|
**方式一:直刷 One-KVM 镜像**
|
||||||
@ -57,6 +34,27 @@ docker run -itd -p443:443 -p80:80 --name pikvm-docker --device=/dev/ttyUSB0:/dev
|
|||||||
|
|
||||||
详细内容可以参照 [One-KVM文档](https://one-kvm.mofeng.run/)。
|
详细内容可以参照 [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 |
|
||||||
|
|
||||||
### 其他
|
### 其他
|
||||||
|
|
||||||
|
|
||||||
@ -101,3 +99,4 @@ Will
|
|||||||
1. [pikvm/pikvm: Open and inexpensive DIY IP-KVM based on Raspberry Pi (github.com)](https://github.com/pikvm/pikvm)
|
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/)
|
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)
|
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
BIN
aiofiles.tar
Normal file
Binary file not shown.
24
armbian/armbian-motd
Normal file
24
armbian/armbian-motd
Normal 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
206
armbian/opt/armbian-sysinfo
Normal 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
70
armbian/opt/vcgencmd
Normal 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
|
||||||
6
armbian/udev/rules.d/99-kvmd.rules
Normal file
6
armbian/udev/rules.d/99-kvmd.rules
Normal 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"
|
||||||
@ -16,8 +16,13 @@ vnc:
|
|||||||
vncauth:
|
vncauth:
|
||||||
enabled: true # Enable auth via /etc/kvmd/vncpasswd
|
enabled: true # Enable auth via /etc/kvmd/vncpasswd
|
||||||
kvmd:
|
kvmd:
|
||||||
|
hid:
|
||||||
|
mouse_alt:
|
||||||
|
device: /dev/kvmd-hid-mouse-alt # allow relative mouse mode
|
||||||
msd:
|
msd:
|
||||||
type: disabled
|
type: disabled
|
||||||
|
atx:
|
||||||
|
type: disabled
|
||||||
gpio:
|
gpio:
|
||||||
drivers:
|
drivers:
|
||||||
wol_server1:
|
wol_server1:
|
||||||
@ -47,7 +52,7 @@ kvmd:
|
|||||||
switch: false
|
switch: false
|
||||||
view:
|
view:
|
||||||
header:
|
header:
|
||||||
title: ATX
|
title: 电源管理
|
||||||
table:
|
table:
|
||||||
- ["#电源管理"]
|
- ["#电源管理"]
|
||||||
- []
|
- []
|
||||||
@ -57,8 +62,10 @@ kvmd:
|
|||||||
- ["#网络唤醒"]
|
- ["#网络唤醒"]
|
||||||
- ["#被控机设备", wol_server1|网络唤醒]
|
- ["#被控机设备", wol_server1|网络唤醒]
|
||||||
streamer:
|
streamer:
|
||||||
forever: true
|
#forever: true
|
||||||
cmd_append: [--slowdown]
|
cmd_append: [--slowdown]
|
||||||
|
resolution:
|
||||||
|
default: 1280x720
|
||||||
desired_fps:
|
desired_fps:
|
||||||
default: 30
|
default: 30
|
||||||
max: 60
|
max: 60
|
||||||
@ -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
864
install-x86.sh
Normal 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
|
||||||
890
install.sh
Executable file → Normal file
890
install.sh
Executable file → Normal file
@ -1,108 +1,640 @@
|
|||||||
#!/bin/bash
|
#!/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 -n)
|
/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
|
||||||
MACHINE=$(uname -o -s -r -m)
|
|
||||||
PYVER=$(python3 -V)
|
|
||||||
CURRENTWD=$PWD
|
|
||||||
FIND_FILE="/etc/sudoers"
|
|
||||||
FIND_STR="onecloud_gpio.sh"
|
|
||||||
|
|
||||||
#检查架构和Python版本
|
'
|
||||||
check_environment(){
|
# NOTE: This was tested on a new install of raspbian desktop and lite versions, but should also work on an existing install.
|
||||||
echo -e "设备名称:$MACHINE\nPython版本:$PYVER"
|
#
|
||||||
if [ ! $ARCH = "onecloud" ]; then
|
# Last change 20240526 2345 PDT
|
||||||
echo -e "此脚本暂不支持armv7l架构以外的设备!\n退出脚本!"
|
VER=3.4
|
||||||
exit
|
set +x
|
||||||
fi
|
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
|
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
||||||
echo -e "您似乎没有安装 Python 3.10!\n退出脚本!"
|
echo "usage: $0 [-f] where -f will force re-install new pikvm platform"
|
||||||
exit
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
#安装依赖软件
|
CWD=`pwd`
|
||||||
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 "正在安装依赖软件......"
|
|
||||||
apt install -y python3.10 python3-pip python3-dev patch iptables nginx \
|
|
||||||
tesseract-ocr tesseract-ocr-eng janus libevent-dev libgpiod-dev \
|
|
||||||
tesseract-ocr-chi-sim libjpeg-dev libfreetype6-dev gcc
|
|
||||||
}
|
|
||||||
|
|
||||||
#安装PiKVM
|
WHOAMI=$( whoami )
|
||||||
install_pikvm(){
|
if [ "$WHOAMI" != "root" ]; then
|
||||||
echo "正在安装PiKVM......"
|
echo "$WHOAMI, please run script as root."
|
||||||
dpkg -i ./fruity-pikvm_0.2_armhf.deb
|
exit 1
|
||||||
systemctl enable kvmd-vnc
|
fi
|
||||||
systemctl disable nginx kvmd-janus
|
|
||||||
#rm -f /lib/systemd/system/nginx.service
|
|
||||||
#rm -f /lib/systemd/system/kvmd-janus.service && systemctl daemon-reload
|
|
||||||
echo "PiKVM安装成功"
|
|
||||||
cd $CURRENTWD
|
|
||||||
cp -f ./patch/onecloud_gpio.sh /usr/bin
|
|
||||||
chmod +x /usr/bin/onecloud_gpio.sh
|
|
||||||
echo "GPIO脚本移动成功"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
#应用补丁
|
PYTHONVER=$( python3 -V | cut -d' ' -f2 | cut -d'.' -f1,2 )
|
||||||
add_patches(){
|
case $PYTHONVER in
|
||||||
if [ ! -f `grep -c "$FIND_STR" $FIND_FILE` ]; then
|
3.10|3.11)
|
||||||
echo kvmd ALL=\(ALL\) NOPASSWD: /usr/bin/onecloud_gpio.sh >> /etc/sudoers
|
echo "Python $PYTHONVER is supported." | tee -a $LOGFILE
|
||||||
fi
|
;;
|
||||||
|
*)
|
||||||
|
echo "Python $PYTHONVER is NOT supported. Please make sure you have python3.10 or higher installed. Exiting." | tee -a $LOGFILE
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
if [ ! -f "/usr/local/lib/python3.10/kvmd-packages/3.198msd.patch" ]; then
|
MAKER=$(tr -d '\0' < /proc/device-tree/model | awk '{print $1}')
|
||||||
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
|
|
||||||
|
|
||||||
cd $CURRENTWD
|
|
||||||
cp -f ./patch/chinese.patch /usr/share/kvmd/web/ && cd /usr/share/kvmd/web/
|
|
||||||
patch -s -p0 < chinese.patch
|
|
||||||
echo -e "中文补丁应用成功"
|
|
||||||
pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/
|
|
||||||
pip3 install -U Pillow
|
|
||||||
|
|
||||||
}
|
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
|
||||||
|
|
||||||
#设置网页终端欢迎语
|
|
||||||
fix_motd(){
|
|
||||||
#cd $CURRENTWD
|
|
||||||
if [ -e /etc/motd ]; then rm /etc/motd; fi
|
|
||||||
cat > /usr/bin/armbian-motd << EOF
|
|
||||||
#!/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
|
|
||||||
|
|
||||||
printf " 欢迎使用 One-KVM,基于开源程序 PiKVM 的 IP-KVM 应用
|
create-override() {
|
||||||
____________________________________________________________________________
|
if [ $( grep ^kvmd: /etc/kvmd/override.yaml | wc -l ) -eq 0 ]; then
|
||||||
|
|
||||||
要修改默认账户 admin 密码可使用 \"kvmd-htpasswd set admin\"
|
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
|
||||||
|
|
||||||
帮助链接:
|
|
||||||
* https://docs.pikvm.org
|
|
||||||
* https://one-kvm.mofeng.run/
|
|
||||||
* https://github.com/mofeng-git/One-KVM
|
|
||||||
"
|
|
||||||
EOF
|
|
||||||
chmod +x /usr/bin/armbian-motd /etc/update-motd.d/10-armbian-header /etc/update-motd.d/30-armbian-sysinfo
|
|
||||||
sed -i 's/cat \/etc\/motd/armbian-motd/g' /lib/systemd/system/kvmd-webterm.service
|
|
||||||
echo "fixed motd"
|
|
||||||
}
|
|
||||||
|
|
||||||
#玩客云特定配置
|
|
||||||
onecloud_conf(){
|
|
||||||
if [ ! $ARCH = "onecloud" ]; then
|
|
||||||
echo -e "\n"
|
|
||||||
else
|
else
|
||||||
echo "为玩客云配置开机脚本"
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} # 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
|
cat <<EOF >/etc/rc.local
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "default-on" >/sys/class/leds/onecloud\:green\:alive/trigger
|
echo "default-on" >/sys/class/leds/onecloud\:green\:alive/trigger
|
||||||
@ -110,26 +642,178 @@ echo "none" >/sys/class/leds/onecloud\:red\:alive/trigger
|
|||||||
echo "none" >/sys/class/leds/onecloud\:blue\:alive/trigger
|
echo "none" >/sys/class/leds/onecloud\:blue\:alive/trigger
|
||||||
cpufreq-set -d 1200MHz -u 1200MHz
|
cpufreq-set -d 1200MHz -u 1200MHz
|
||||||
echo device > /sys/class/usb_role/c9040000.usb-role-switch/role
|
echo device > /sys/class/usb_role/c9040000.usb-role-switch/role
|
||||||
systemctl disable kvmd
|
systemctl disable kvmd && systemctl stop kvmd
|
||||||
systemctl start kvmd
|
systemctl start kvmd
|
||||||
exit 0
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
|
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
|
#如果在CHROOT环境需设置NOTCHROOT=false
|
||||||
! $NOTCHROOT || gzip -dc ./patch/Boot_SkipUSBBurning.gz | dd of=/dev/mmcblk1 bs=512 seek=1 count=32767
|
! $NOTCHROOT || gzip -dc ${APP_PATH}/patches/Boot_SkipUSBBurning.gz | dd of=/dev/mmcblk1 bs=512 seek=1 count=32767
|
||||||
echo -e "\n"
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
#打印完成信息
|
|
||||||
show_info(){
|
update-logo() {
|
||||||
echo -e "安装结束,重启之后即可开始使用One-KVM"
|
sed -i -e 's|class="svg-gray"|class="svg-color"|g' /usr/share/kvmd/web/index.html
|
||||||
/usr/bin/armbian-motd
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
check_environment
|
|
||||||
install_dependencies
|
### MAIN STARTS HERE ###
|
||||||
install_pikvm
|
# Install is done in two parts
|
||||||
add_patches
|
# First part requires a reboot in order to create kvmd users and groups
|
||||||
fix_motd
|
# Second part will start the necessary kvmd services
|
||||||
onecloud_conf
|
|
||||||
show_info
|
# 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
|
||||||
|
|||||||
BIN
kvmd-packages/janus-gateway-pikvm-0.14.3-1-armv7h.pkg.tar.xz
Normal file
BIN
kvmd-packages/janus-gateway-pikvm-0.14.3-1-armv7h.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/janus-gateway-pikvm-1x-1.2.1-1-armv7h.pkg.tar.xz
Normal file
BIN
kvmd-packages/janus-gateway-pikvm-1x-1.2.1-1-armv7h.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-3.291-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-3.291-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-platform-v0-hdmi-rpi3-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-platform-v0-hdmi-rpi3-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-platform-v0-hdmiusb-rpi3-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-platform-v0-hdmiusb-rpi3-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-platform-v1-hdmi-rpi3-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-platform-v1-hdmi-rpi3-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-platform-v1-hdmiusb-rpi3-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-platform-v1-hdmiusb-rpi3-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-platform-v2-hdmi-rpi3-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-platform-v2-hdmi-rpi3-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-platform-v2-hdmiusb-rpi4-4.2-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-platform-v2-hdmiusb-rpi4-4.2-1-any.pkg.tar.xz
Normal file
Binary file not shown.
BIN
kvmd-packages/kvmd-webterm-0.50-1-any.pkg.tar.xz
Normal file
BIN
kvmd-packages/kvmd-webterm-0.50-1-any.pkg.tar.xz
Normal file
Binary file not shown.
9
kvmd_display_install.sh
Executable file → Normal file
9
kvmd_display_install.sh
Executable file → Normal file
@ -21,16 +21,19 @@ RestartSec=3
|
|||||||
AmbientCapabilities=CAP_NET_RAW
|
AmbientCapabilities=CAP_NET_RAW
|
||||||
LimitNOFILE=65536
|
LimitNOFILE=65536
|
||||||
UMask=0117
|
UMask=0117
|
||||||
ExecStart=/usr/share/kvmd/display_when_ustream_exists.sh
|
ExecStart=/usr/share/kvmd/display.sh
|
||||||
TimeoutStopSec=10
|
TimeoutStopSec=10
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
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
|
cp -f ./patches/display.sh /usr/share/kvmd/ && chmod +x /usr/share/kvmd/display.sh
|
||||||
#启动服务
|
#启动服务
|
||||||
systemctl enable kvmd-display && systemctl start kvmd-display
|
systemctl daemon-reload
|
||||||
|
! $NOTCHROOT || systemctl enable kvmd-display
|
||||||
|
! $NOTCHROOT || systemctl start kvmd-display
|
||||||
|
echo "配置完成"
|
||||||
}
|
}
|
||||||
|
|
||||||
kvmd_display
|
kvmd_display
|
||||||
14
kvmd_h264_install.sh
Executable file → Normal file
14
kvmd_h264_install.sh
Executable file → Normal file
@ -49,15 +49,19 @@ EOF
|
|||||||
#修改原有kvmd代码和配置文件
|
#修改原有kvmd代码和配置文件
|
||||||
sed -i '17s/.*/ExecStart=\/usr\/bin\/janus --disable-colors --configs-folder=\/etc\/kvmd\/janus2/' /lib/systemd/system/kvmd-janus-static.service
|
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 '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 '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
|
sed -i 's/request\": \"watch\", \"p/request\": \"watch\", \"id\" : 1, \"p/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
|
||||||
#补全网页JS文件并添加相应脚本
|
#补全网页JS文件并添加相应脚本
|
||||||
|
if [ ! -e /usr/share/janus/javascript/adapter.js ]; then
|
||||||
mkdir /usr/share/janus/javascript/
|
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 ./patches/adapter.js /usr/share/janus/javascript/ && cp -f ./patches/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
|
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 enable kvmd-ffmpeg && systemctl enable kvmd-janus-static
|
systemctl daemon-reload
|
||||||
systemctl start kvmd-ffmpeg && systemctl start kvmd-janus-static
|
! $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
|
kvmd_ffmpeg_h-264
|
||||||
49
opikvm-logo.svg
Normal file
49
opikvm-logo.svg
Normal 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 |
1509
patch/chinese.patch
1509
patch/chinese.patch
File diff suppressed because it is too large
Load Diff
@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 存储 stream.sh 的进程 ID
|
|
||||||
b_pid=""
|
|
||||||
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
echo "Received SIGINT. Terminating the process group..."
|
|
||||||
[ -n "$b_pid" ] && pkill -9 -g $b_pid # 终止整个进程组
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 捕获 SIGINT 信号
|
|
||||||
trap cleanup SIGINT
|
|
||||||
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
# 检测是否有包含 "ustreamer" 的进程
|
|
||||||
if pgrep -f "/usr/bin/ustreamer " > /dev/null; then
|
|
||||||
# 如果存在,但是 stream.sh 进程不存在,执行 stream.sh 并记录其进程 ID
|
|
||||||
if [ -z "$b_pid" ]; then
|
|
||||||
echo "Found a process with 'ustreamer' in the command. Executing stream.sh in the background..."
|
|
||||||
setsid /usr/share/kvmd/diaplay.sh &
|
|
||||||
b_pid=$(ps -o pgid= $!)
|
|
||||||
echo "stream.sh started with PID: $b_pid"
|
|
||||||
else
|
|
||||||
echo "Process with 'ustreamer' is already running. Skipping..."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# 如果不存在 "ustreamer" 进程,但是 stream.sh 进程存在,终止 stream.sh 并清除进程 ID
|
|
||||||
if [ -n "$b_pid" ]; then
|
|
||||||
echo "No process with 'ustreamer' found. Terminating stream.sh (PID: $b_pid)..."
|
|
||||||
pkill -9 -g $b_pid
|
|
||||||
b_pid=""
|
|
||||||
else
|
|
||||||
echo "No process with 'ustreamer' found. Waiting for the next check..."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 等待一段时间,可以根据需要调整等待的时间间隔
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
done
|
|
||||||
769
patches/__init__.py
Normal file
769
patches/__init__.py
Normal 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
777
patches/__init__.py.2
Normal 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),
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,9 +1,27 @@
|
|||||||
diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
|
From 3d137882ac38ac046b7d09cada1883b304b04319 Mon Sep 17 00:00:00 2001
|
||||||
--- kvmd/aiohelpers.py 2023-01-30 03:25:23.556804000 +0700
|
From: xe5700 <9338143+xe5700@users.noreply.github.com>
|
||||||
+++ kvmd/aiohelpers.py 2023-01-30 08:12:21.773899000 +0700
|
Date: Fri, 20 May 2022 18:34:21 +0800
|
||||||
@@ -38,11 +38,26 @@
|
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:
|
try:
|
||||||
- proc = await aioproc.log_process(cmd, logger)
|
- proc = await aioproc.log_process(cmd, logger)
|
||||||
- if proc.returncode != 0:
|
- if proc.returncode != 0:
|
||||||
@ -19,7 +37,7 @@ diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
|
|||||||
+ raise
|
+ raise
|
||||||
+
|
+
|
||||||
+
|
+
|
||||||
+async def unlock_drive(base_cmd: list[str]) -> None:
|
+async def unlock_drive(base_cmd: List[str]) -> None:
|
||||||
+ logger = get_logger(0)
|
+ logger = get_logger(0)
|
||||||
+ logger.info("Unlocking the drive ...")
|
+ logger.info("Unlocking the drive ...")
|
||||||
+ try:
|
+ 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 = get_logger(0)
|
||||||
+ logger.info("Executing helper %s ...", cmd)
|
+ logger.info("Executing helper %s ...", cmd)
|
||||||
+ proc = await aioproc.log_process(cmd, logger)
|
+ proc = await aioproc.log_process(cmd, logger)
|
||||||
+ if proc.returncode != 0:
|
+ if proc.returncode != 0:
|
||||||
+ logger.error(f"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}")
|
+ raise MsdError(f"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}")
|
||||||
diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
|
diff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py
|
||||||
--- kvmd/apps/otg/__init__.py 2022-12-22 10:01:48.000000000 +0700
|
index cbf7a197..d0ed0554 100644
|
||||||
+++ kvmd/apps/otg/__init__.py 2023-01-30 03:51:51.331539000 +0700
|
--- a/kvmd/apps/otg/__init__.py
|
||||||
@@ -182,7 +182,6 @@
|
+++ 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/cdrom"), user)
|
||||||
_chown(join(func_path, "lun.0/ro"), user)
|
_chown(join(func_path, "lun.0/ro"), user)
|
||||||
_chown(join(func_path, "lun.0/file"), user)
|
_chown(join(func_path, "lun.0/file"), user)
|
||||||
@ -47,7 +66,7 @@ diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
|
|||||||
_symlink(func_path, join(self.__profile_path, func))
|
_symlink(func_path, join(self.__profile_path, func))
|
||||||
name = ("Mass Storage Drive" if self.__msd_instance == 0 else f"Extra Drive #{self.__msd_instance}")
|
name = ("Mass Storage Drive" if self.__msd_instance == 0 else f"Extra Drive #{self.__msd_instance}")
|
||||||
self.__create_meta(func, name)
|
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)
|
logger.info("Disabling gadget %r ...", config.otg.gadget)
|
||||||
_write(join(gadget_path, "UDC"), "\n")
|
_write(join(gadget_path, "UDC"), "\n")
|
||||||
|
|
||||||
@ -56,21 +75,27 @@ diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
|
|||||||
|
|
||||||
profile_path = join(gadget_path, usb.G_PROFILE)
|
profile_path = join(gadget_path, usb.G_PROFILE)
|
||||||
for func in os.listdir(profile_path):
|
for func in os.listdir(profile_path):
|
||||||
diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py
|
diff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py
|
||||||
--- kvmd/apps/otgmsd/__init__.py 2022-12-22 10:01:48.000000000 +0700
|
index f57b3107..78f8e3c7 100644
|
||||||
+++ kvmd/apps/otgmsd/__init__.py 2023-01-30 04:35:09.702576000 +0700
|
--- a/kvmd/apps/otgmsd/__init__.py
|
||||||
@@ -21,8 +21,10 @@
|
+++ b/kvmd/apps/otgmsd/__init__.py
|
||||||
|
@@ -21,12 +21,15 @@
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
+import signal
|
+import signal
|
||||||
import errno
|
import errno
|
||||||
import argparse
|
import argparse
|
||||||
+import psutil
|
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
+import psutil
|
||||||
|
+
|
||||||
from ...validators.basic import valid_bool
|
from ...validators.basic import valid_bool
|
||||||
from ...validators.basic import valid_int_f0
|
from ...validators.basic import valid_int_f0
|
||||||
@@ -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
|
raise
|
||||||
|
|
||||||
|
|
||||||
@ -90,18 +115,18 @@ 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(
|
(parent_parser, argv, config) = init(
|
||||||
@@ -77,7 +94,7 @@
|
@@ -70,6 +88,8 @@ def main(argv: Optional[List[str]]=None) -> None:
|
||||||
parser.add_argument("--eject", action="store_true",
|
)
|
||||||
help="Eject the image")
|
parser.add_argument("-i", "--instance", default=0, type=valid_int_f0,
|
||||||
parser.add_argument("--unlock", action="store_true",
|
metavar="<N>", help="Drive instance (0 for KVMD drive)")
|
||||||
- help="Does nothing, just for backward compatibility")
|
+ parser.add_argument("--unlock", action="store_true",
|
||||||
+ help="Send SIGUSR1 to MSD kernel thread")
|
+ help="Send SIGUSR1 to MSD kernel thread")
|
||||||
options = parser.parse_args(argv[1:])
|
parser.add_argument("--set-cdrom", default=None, type=valid_bool,
|
||||||
|
metavar="<1|0|yes|no>", help="Set CD-ROM flag")
|
||||||
if config.kvmd.msd.type != "otg":
|
parser.add_argument("--set-rw", default=None, type=valid_bool,
|
||||||
@@ -87,8 +104,11 @@
|
@@ -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))
|
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))
|
get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))
|
||||||
|
|
||||||
@ -114,9 +139,11 @@ diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py
|
|||||||
|
|
||||||
if options.set_cdrom is not None:
|
if options.set_cdrom is not None:
|
||||||
set_param("cdrom", str(int(options.set_cdrom)))
|
set_param("cdrom", str(int(options.set_cdrom)))
|
||||||
diff -ruN kvmd/helpers/unlock/__init__.py kvmd/helpers/unlock/__init__.py
|
diff --git a/kvmd/helpers/unlock/__init__.py b/kvmd/helpers/unlock/__init__.py
|
||||||
--- kvmd/helpers/unlock/__init__.py 1970-01-01 07:00:00.000000000 +0700
|
new file mode 100644
|
||||||
+++ kvmd/helpers/unlock/__init__.py 2023-01-30 04:04:07.000000000 +0700
|
index 00000000..140e0e7c
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/kvmd/helpers/unlock/__init__.py
|
||||||
@@ -0,0 +1,58 @@
|
@@ -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":
|
+ if len(sys.argv) != 2 or sys.argv[1] != "unlock":
|
||||||
+ raise SystemExit(f"Usage: {sys.argv[0]} [unlock]")
|
+ raise SystemExit(f"Usage: {sys.argv[0]} [unlock]")
|
||||||
+ _unlock()
|
+ _unlock()
|
||||||
diff -ruN kvmd/helpers/unlock/__main__.py kvmd/helpers/unlock/__main__.py
|
diff --git a/kvmd/helpers/unlock/__main__.py b/kvmd/helpers/unlock/__main__.py
|
||||||
--- kvmd/helpers/unlock/__main__.py 1970-01-01 07:00:00.000000000 +0700
|
new file mode 100644
|
||||||
+++ kvmd/helpers/unlock/__main__.py 2023-01-30 04:04:07.000000000 +0700
|
index 00000000..3849d1b9
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/kvmd/helpers/unlock/__main__.py
|
||||||
@@ -0,0 +1,24 @@
|
@@ -0,0 +1,24 @@
|
||||||
+# ========================================================================== #
|
+# ========================================================================== #
|
||||||
+# #
|
+# #
|
||||||
@ -204,10 +233,87 @@ diff -ruN kvmd/helpers/unlock/__main__.py kvmd/helpers/unlock/__main__.py
|
|||||||
+
|
+
|
||||||
+from . import main
|
+from . import main
|
||||||
+main()
|
+main()
|
||||||
diff -ruN kvmd/plugins/msd/otg/drive.py kvmd/plugins/msd/otg/drive.py
|
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
|
||||||
--- kvmd/plugins/msd/otg/drive.py 2022-12-22 10:01:48.000000000 +0700
|
index 409b899a..1342c6b4 100644
|
||||||
+++ kvmd/plugins/msd/otg/drive.py 2023-01-30 06:31:13.923959000 +0700
|
--- a/kvmd/plugins/msd/otg/__init__.py
|
||||||
@@ -51,10 +51,7 @@
|
+++ 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:
|
def set_image_path(self, path: str) -> None:
|
||||||
@ -219,78 +325,6 @@ diff -ruN kvmd/plugins/msd/otg/drive.py kvmd/plugins/msd/otg/drive.py
|
|||||||
|
|
||||||
def get_image_path(self) -> str:
|
def get_image_path(self) -> str:
|
||||||
return self.__get_param("file")
|
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
|
2.34.1.windows.1
|
||||||
+++ kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:50:38.774955045 +0700
|
|
||||||
@@ -129,6 +129,7 @@
|
|
||||||
sync_chunk_size: int,
|
|
||||||
|
|
||||||
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)
|
|
||||||
11
patches/custom/old-kernel-msd/apply.sh
Normal file
11
patches/custom/old-kernel-msd/apply.sh
Normal 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."
|
||||||
7
patches/custom/old-kernel-msd/kvmd-helper-otgmsd-unlock
Normal file
7
patches/custom/old-kernel-msd/kvmd-helper-otgmsd-unlock
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/sbin/python
|
||||||
|
# KVMD-ARMBIAN
|
||||||
|
|
||||||
|
from kvmd.helpers.unlock import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
0
patch/display.sh → patches/display.sh
Executable file → Normal file
0
patch/display.sh → patches/display.sh
Executable file → Normal file
@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# KVMD - The main PiKVM daemon. #
|
# 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 #
|
# 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 #
|
# 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 env
|
||||||
from .... import tools
|
from .... import tools
|
||||||
from .... import aiofs
|
from .... import aiotools
|
||||||
from .... import aioproc
|
from .... import aioproc
|
||||||
|
|
||||||
from .base import BaseInfoSubmanager
|
from .base import BaseInfoSubmanager
|
||||||
@ -46,33 +46,36 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
vcgencmd_cmd: list[str],
|
vcgencmd_cmd: list[str],
|
||||||
|
ignore_past: bool,
|
||||||
state_poll: float,
|
state_poll: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.__vcgencmd_cmd = vcgencmd_cmd
|
self.__vcgencmd_cmd = vcgencmd_cmd
|
||||||
|
self.__ignore_past = ignore_past
|
||||||
self.__state_poll = state_poll
|
self.__state_poll = state_poll
|
||||||
|
|
||||||
self.__dt_cache: dict[str, str] = {}
|
self.__dt_cache: dict[str, str] = {}
|
||||||
|
|
||||||
async def get_state(self) -> dict:
|
#async def get_state(self) -> dict:
|
||||||
(model, cpu_temp, throttling) = await asyncio.gather(
|
# (model, serial, cpu_temp, throttling) = await asyncio.gather(
|
||||||
self.__read_dt_file("model"),
|
# self.__read_dt_file("model"),
|
||||||
self.__get_cpu_temp(),
|
# self.__read_dt_file("serial-number"),
|
||||||
self.__get_throttling(),
|
# self.__get_cpu_temp(),
|
||||||
)
|
# self.__get_throttling(),
|
||||||
return {
|
# )
|
||||||
"platform": {
|
# return {
|
||||||
"type": "rpi",
|
# "platform": {
|
||||||
"base": model,
|
# "type": "rpi",
|
||||||
"serial": "0000000000000000",
|
# "base": model,
|
||||||
},
|
# "serial": serial,
|
||||||
"health": {
|
# },
|
||||||
"temp": {
|
# "health": {
|
||||||
"cpu": cpu_temp,
|
# "temp": {
|
||||||
},
|
# "cpu": cpu_temp,
|
||||||
"throttling": throttling,
|
# },
|
||||||
},
|
# "throttling": throttling,
|
||||||
}
|
# },
|
||||||
|
# }
|
||||||
|
|
||||||
async def poll_state(self) -> AsyncGenerator[dict, None]:
|
async def poll_state(self) -> AsyncGenerator[dict, None]:
|
||||||
prev_state: dict = {}
|
prev_state: dict = {}
|
||||||
@ -89,7 +92,7 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
|||||||
if name not in self.__dt_cache:
|
if name not in self.__dt_cache:
|
||||||
path = os.path.join(f"{env.PROCFS_PREFIX}/proc/device-tree", name)
|
path = os.path.join(f"{env.PROCFS_PREFIX}/proc/device-tree", name)
|
||||||
try:
|
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:
|
except Exception as err:
|
||||||
get_logger(0).error("Can't read DT %s from %s: %s", name, path, err)
|
get_logger(0).error("Can't read DT %s from %s: %s", name, path, err)
|
||||||
return None
|
return None
|
||||||
@ -98,8 +101,9 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
|||||||
async def __get_cpu_temp(self) -> (float | None):
|
async def __get_cpu_temp(self) -> (float | None):
|
||||||
temp_path = f"{env.SYSFS_PREFIX}/sys/class/thermal/thermal_zone0/temp"
|
temp_path = f"{env.SYSFS_PREFIX}/sys/class/thermal/thermal_zone0/temp"
|
||||||
try:
|
try:
|
||||||
return int((await aiofs.read(temp_path)).strip()) / 1000
|
return int((await aiotools.read_file(temp_path)).strip()) / 1000
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
get_logger(0).error("Can't read CPU temp from %s: %s", temp_path, err)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def __get_throttling(self) -> (dict | None):
|
async def __get_throttling(self) -> (dict | None):
|
||||||
@ -125,6 +129,7 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
|||||||
"past": bool(flags & (1 << 18)),
|
"past": bool(flags & (1 << 18)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"ignore_past": self.__ignore_past,
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
417
patches/session.js
Normal file
417
patches/session.js
Normal 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, " ").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]}°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
482
patches/streamer.py.1
Normal 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
47
pi-temp
Normal 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
51
pikvm-info
Normal 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
110
pistat
Normal 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
BIN
sources/WiringPi-3.6.zip
Normal file
Binary file not shown.
BIN
sources/ustreamer-6.12.zip
Normal file
BIN
sources/ustreamer-6.12.zip
Normal file
Binary file not shown.
112
uninstall-pikvm.sh
Normal file
112
uninstall-pikvm.sh
Normal 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
13
web.css
Normal 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
BIN
x86-mods.tar
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user