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),和基于远控软件的远程管理方式不同,无需在被控电脑安装任何软件,实现无侵入式控制。
|
||||
|
||||
|
||||
**功能特性**
|
||||
|
||||
主要功能比较,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 镜像**
|
||||
@ -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/)。
|
||||
|
||||
### 功能特性
|
||||
|
||||
主要功能比较,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)
|
||||
2. [hzyitc/armbian-onecloud: Armbian for onecloud. 玩客云用armbian (github.com)](https://github.com/hzyitc/armbian-onecloud/)
|
||||
3. [jacobbar/fruity-pikvm: Install Pi-KVM on debian SBCs such as Orange Pi, Banana Pi, Mango Pi, etc (github.com)](https://github.com/jacobbar/fruity-pikvm)
|
||||
4. [kvmd-armbian/install.sh at master · srepac/kvmd-armbian (github.com)](https://github.com/srepac/kvmd-armbian/blob/master/install.sh)
|
||||
BIN
aiofiles.tar
Normal file
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:
|
||||
enabled: true # Enable auth via /etc/kvmd/vncpasswd
|
||||
kvmd:
|
||||
hid:
|
||||
mouse_alt:
|
||||
device: /dev/kvmd-hid-mouse-alt # allow relative mouse mode
|
||||
msd:
|
||||
type: disabled
|
||||
atx:
|
||||
type: disabled
|
||||
gpio:
|
||||
drivers:
|
||||
wol_server1:
|
||||
@ -47,7 +52,7 @@ kvmd:
|
||||
switch: false
|
||||
view:
|
||||
header:
|
||||
title: ATX
|
||||
title: 电源管理
|
||||
table:
|
||||
- ["#电源管理"]
|
||||
- []
|
||||
@ -57,8 +62,10 @@ kvmd:
|
||||
- ["#网络唤醒"]
|
||||
- ["#被控机设备", wol_server1|网络唤醒]
|
||||
streamer:
|
||||
forever: true
|
||||
#forever: true
|
||||
cmd_append: [--slowdown]
|
||||
resolution:
|
||||
default: 1280x720
|
||||
desired_fps:
|
||||
default: 30
|
||||
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
|
||||
# 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)
|
||||
MACHINE=$(uname -o -s -r -m)
|
||||
PYVER=$(python3 -V)
|
||||
CURRENTWD=$PWD
|
||||
FIND_FILE="/etc/sudoers"
|
||||
FIND_STR="onecloud_gpio.sh"
|
||||
/dev/sda1 /var/lib/kvmd/msd ext4 nodev,nosuid,noexec,ro,errors=remount-ro,data=journal,X-kvmd.otgmsd-root=/var/lib/kvmd/msd,X-kvmd.otgmsd-user=kvmd 0 0
|
||||
|
||||
#检查架构和Python版本
|
||||
check_environment(){
|
||||
echo -e "设备名称:$MACHINE\nPython版本:$PYVER"
|
||||
if [ ! $ARCH = "onecloud" ]; then
|
||||
echo -e "此脚本暂不支持armv7l架构以外的设备!\n退出脚本!"
|
||||
exit
|
||||
fi
|
||||
'
|
||||
# NOTE: This was tested on a new install of raspbian desktop and lite versions, but should also work on an existing install.
|
||||
#
|
||||
# Last change 20240526 2345 PDT
|
||||
VER=3.4
|
||||
set +x
|
||||
PIKVMREPO="https://files.pikvm.org/repos/arch/rpi4"
|
||||
KVMDFILE="kvmd-3.291-1-any.pkg.tar.xz"
|
||||
KVMDCACHE="/var/cache/kvmd"; mkdir -p $KVMDCACHE
|
||||
PKGINFO="${KVMDCACHE}/packages.txt"
|
||||
APP_PATH=$(readlink -f $(dirname $0))
|
||||
LOGFILE="${KVMDCACHE}/installer.log"; touch $LOGFILE; echo "==== $( date ) ====" >> $LOGFILE
|
||||
|
||||
if [[ "$PYVER" != *"3.10"* && $(which python3.10) != *"python"* ]]; then
|
||||
echo -e "您似乎没有安装 Python 3.10!\n退出脚本!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
||||
echo "usage: $0 [-f] where -f will force re-install new pikvm platform"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#安装依赖软件
|
||||
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
|
||||
}
|
||||
CWD=`pwd`
|
||||
|
||||
#安装PiKVM
|
||||
install_pikvm(){
|
||||
echo "正在安装PiKVM......"
|
||||
dpkg -i ./fruity-pikvm_0.2_armhf.deb
|
||||
systemctl enable kvmd-vnc
|
||||
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
|
||||
}
|
||||
WHOAMI=$( whoami )
|
||||
if [ "$WHOAMI" != "root" ]; then
|
||||
echo "$WHOAMI, please run script as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#应用补丁
|
||||
add_patches(){
|
||||
if [ ! -f `grep -c "$FIND_STR" $FIND_FILE` ]; then
|
||||
echo kvmd ALL=\(ALL\) NOPASSWD: /usr/bin/onecloud_gpio.sh >> /etc/sudoers
|
||||
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
|
||||
|
||||
if [ ! -f "/usr/local/lib/python3.10/kvmd-packages/3.198msd.patch" ]; then
|
||||
cd $CURRENTWD
|
||||
cp ./patch/3.198msd.patch /usr/local/lib/python3.10/kvmd-packages/ && cd /usr/local/lib/python3.10/kvmd-packages/
|
||||
patch -s -p0 < 3.198msd.patch
|
||||
echo "MSD补丁应用成功"
|
||||
fi
|
||||
MAKER=$(tr -d '\0' < /proc/device-tree/model | awk '{print $1}')
|
||||
|
||||
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
|
||||
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
|
||||
#!/bin/bash
|
||||
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
|
||||
cpufreq-set -d 1200MHz -u 1200MHz
|
||||
echo device > /sys/class/usb_role/c9040000.usb-role-switch/role
|
||||
systemctl disable kvmd
|
||||
systemctl disable kvmd && systemctl stop kvmd
|
||||
systemctl start kvmd
|
||||
exit 0
|
||||
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
|
||||
! $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"
|
||||
if [ ! -f `grep -c "onecloud_gpio.sh" /etc/sudoers` ]; then
|
||||
echo kvmd ALL=\(ALL\) NOPASSWD: /usr/bin/onecloud_gpio.sh >> /etc/sudoers
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
#打印完成信息
|
||||
show_info(){
|
||||
echo -e "安装结束,重启之后即可开始使用One-KVM"
|
||||
/usr/bin/armbian-motd
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
check_environment
|
||||
install_dependencies
|
||||
install_pikvm
|
||||
add_patches
|
||||
fix_motd
|
||||
onecloud_conf
|
||||
show_info
|
||||
|
||||
### MAIN STARTS HERE ###
|
||||
# Install is done in two parts
|
||||
# First part requires a reboot in order to create kvmd users and groups
|
||||
# Second part will start the necessary kvmd services
|
||||
|
||||
# if /etc/kvmd/htpasswd exists, then make a backup
|
||||
if [ -e /etc/kvmd/htpasswd ]; then cp /etc/kvmd/htpasswd /etc/kvmd/htpasswd.save; fi
|
||||
|
||||
### I uploaded all these into github on 05/22/23 -- so just copy them into correct location
|
||||
cd ${APP_PATH}
|
||||
cp -rf pistat /usr/local/bin/pistat
|
||||
cp -rf pi-temp /usr/local/bin/pi-temp
|
||||
cp -rf pikvm-info /usr/local/bin/pikvm-info
|
||||
chmod +x /usr/local/bin/pi*
|
||||
|
||||
### fix for kvmd 3.230 and higher
|
||||
ln -sf python3 /usr/bin/python
|
||||
|
||||
SERVICES="kvmd-nginx kvmd-webterm kvmd-otg kvmd kvmd-fix kvmd-vnc kvmd-ipmi"
|
||||
|
||||
# added option to re-install by adding -f parameter (for use as platform switcher)
|
||||
PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )
|
||||
if [[ $( grep kvmd /etc/passwd | wc -l ) -eq 0 || "$1" == "-f" ]]; then
|
||||
printf "\nRunning part 1 of PiKVM installer script v$VER by @srepac and @SilentWind\n" | tee -a $LOGFILE
|
||||
get-platform
|
||||
get-packages
|
||||
install-kvmd-pkgs
|
||||
boot-files
|
||||
create-override
|
||||
gen-ssl-certs
|
||||
fix-udevrules
|
||||
install-dependencies
|
||||
! $NOTCHROOT || otg-devices
|
||||
armbian-packages
|
||||
onecloud_conf #set for onecloud
|
||||
systemctl disable --now janus ttyd
|
||||
|
||||
printf "\nEnd part 1 of PiKVM installer script v$VER by @srepac and @SilentWind\n" >> $LOGFILE
|
||||
printf "\nReboot is required to create kvmd users and groups.\nPlease re-run this script after reboot to complete the install.\n" | tee -a $LOGFILE
|
||||
|
||||
# Fix paste-as-keys if running python 3.7
|
||||
if [[ $( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 ) == "3.7" ]]; then
|
||||
sed -i -e 's/reversed//g' /usr/lib/python3.1*/site-packages/kvmd/keyboard/printer.py
|
||||
fi
|
||||
|
||||
### run these to make sure kvmd users are created ###
|
||||
echo "-> Ensuring KVMD users and groups ..." | tee -a $LOGFILE
|
||||
systemd-sysusers /usr/lib/sysusers.d/kvmd.conf
|
||||
systemd-sysusers /usr/lib/sysusers.d/kvmd-webterm.conf
|
||||
|
||||
# Ask user to press CTRL+C before reboot or ENTER to proceed with reboot
|
||||
echo
|
||||
! $NOTCHROOT || read -p "Press ENTER to continue or CTRL+C to break out of script."
|
||||
! $NOTCHROOT || reboot
|
||||
else
|
||||
printf "\nRunning part 2 of PiKVM installer script v$VER by @srepac and @SilentWind\n" | tee -a $LOGFILE
|
||||
|
||||
echo "-> Re-installing janus ..." | tee -a $LOGFILE
|
||||
apt reinstall -y janus > /dev/null 2>&1
|
||||
### run these to make sure kvmd users are created ###
|
||||
echo "-> Ensuring KVMD users and groups ..." | tee -a $LOGFILE
|
||||
systemd-sysusers /usr/lib/sysusers.d/kvmd.conf
|
||||
systemd-sysusers /usr/lib/sysusers.d/kvmd-webterm.conf
|
||||
|
||||
fix-nginx-symlinks
|
||||
fix-python-symlinks
|
||||
fix-webterm
|
||||
fix-motd
|
||||
fix-nfs-msd
|
||||
fix-nginx
|
||||
async-lru-fix
|
||||
ocr-fix
|
||||
|
||||
set-ownership
|
||||
create-kvmdfix
|
||||
|
||||
echo "-> Install python3 modules dbus_next and zstandard" | tee -a $LOGFILE
|
||||
if [[ "$PYTHONVER" == "3.11" ]]; then
|
||||
apt install -y python3-dbus-next python3-zstandard
|
||||
else
|
||||
pip3 install dbus_next zstandard
|
||||
fi
|
||||
### additional python pip dependencies for kvmd 3.238 and higher
|
||||
case $PYTHONVER in
|
||||
3.10*|3.[987]*)
|
||||
pip3 install async-lru 2> /dev/null
|
||||
### Fix for kvmd 3.291 -- only applies to python 3.10 ###
|
||||
sed -i -e 's|gpiod.EdgeEvent|gpiod.LineEvent|g' /usr/lib/python3/dist-packages/kvmd/aiogp.py
|
||||
sed -i -e 's|gpiod.line,|gpiod.Line,|g' /usr/lib/python3/dist-packages/kvmd/aiogp.py
|
||||
;;
|
||||
3.11*)
|
||||
pip3 install async-lru --break-system-packages 2> /dev/null
|
||||
;;
|
||||
esac
|
||||
|
||||
check-kvmd-works
|
||||
enable-kvmd-svcs
|
||||
update-logo
|
||||
start-kvmd-svcs
|
||||
|
||||
printf "\nCheck kvmd devices\n\n" | tee -a $LOGFILE
|
||||
ls -l /dev/kvmd* | tee -a $LOGFILE
|
||||
printf "\nYou should see devices for keyboard, mouse, and video.\n" | tee -a $LOGFILE
|
||||
|
||||
printf "\nPoint a browser to https://$(hostname)\nIf it doesn't work, then reboot one last time.\nPlease make sure kvmd services are running after reboot.\n" | tee -a $LOGFILE
|
||||
fi
|
||||
|
||||
cd $CWD
|
||||
cp -rf web.css /etc/kvmd/web.css
|
||||
|
||||
systemctl status $SERVICES | grep Loaded | tee -a $LOGFILE
|
||||
|
||||
### fix totp.secret file permissions for use with 2FA
|
||||
chmod go+r /etc/kvmd/totp.secret
|
||||
chown kvmd:kvmd /etc/kvmd/totp.secret
|
||||
|
||||
### create rw and ro so that /usr/bin/kvmd-bootconfig doesn't fail
|
||||
touch /usr/local/bin/rw /usr/local/bin/ro
|
||||
chmod +x /usr/local/bin/rw /usr/local/bin/ro
|
||||
|
||||
### update default hostname info in webui to reflect current hostname
|
||||
sed -i -e "s/localhost.localdomain/`hostname`/g" /etc/kvmd/meta.yaml
|
||||
|
||||
### restore htpasswd from previous install, if applies
|
||||
if [ -e /etc/kvmd/htpasswd.save ]; then cp /etc/kvmd/htpasswd.save /etc/kvmd/htpasswd; fi
|
||||
|
||||
### instead of showing # fps dynamic, show REDACTED fps dynamic instead; USELESS fps meter fix
|
||||
#sed -i -e 's|${__fps}|REDACTED|g' /usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js
|
||||
|
||||
### fix kvmd-webterm 0.49 change that changed ttyd to kvmd-ttyd which broke webterm
|
||||
sed -i -e 's/kvmd-ttyd/ttyd/g' /lib/systemd/system/kvmd-webterm.service
|
||||
|
||||
# get rid of this line, otherwise kvmd-nginx won't start properly since the nginx version is not 1.25 and higher
|
||||
if [ -e /etc/kvmd/nginx/nginx.conf.mako ]; then
|
||||
sed -i -e '/http2 on;/d' /etc/kvmd/nginx/nginx.conf.mako
|
||||
fi
|
||||
|
||||
systemctl restart kvmd-nginx kvmd-webterm kvmd
|
||||
|
||||
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
|
||||
LimitNOFILE=65536
|
||||
UMask=0117
|
||||
ExecStart=/usr/share/kvmd/display_when_ustream_exists.sh
|
||||
ExecStart=/usr/share/kvmd/display.sh
|
||||
TimeoutStopSec=10
|
||||
KillMode=mixed
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
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
|
||||
14
kvmd_h264_install.sh
Executable file → Normal file
14
kvmd_h264_install.sh
Executable file → Normal file
@ -49,15 +49,19 @@ EOF
|
||||
#修改原有kvmd代码和配置文件
|
||||
sed -i '17s/.*/ExecStart=\/usr\/bin\/janus --disable-colors --configs-folder=\/etc\/kvmd\/janus2/' /lib/systemd/system/kvmd-janus-static.service
|
||||
sed -i 's/janus.plugin.ustreamer/janus.plugin.streaming/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
|
||||
sed -i '293c \/\/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
|
||||
sed -i '324c \/\/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
|
||||
sed -i 's/request\": \"watch\", \"p/request\": \"watch\", \"id\" : 1, \"p/' /usr/share/kvmd/web/share/js/kvm/stream_janus.js
|
||||
#补全网页JS文件并添加相应脚本
|
||||
if [ ! -e /usr/share/janus/javascript/adapter.js ]; then
|
||||
mkdir /usr/share/janus/javascript/
|
||||
cp -f ./web/adapter.js /usr/share/janus/javascript/ && cp -f ./web/janus.js /usr/share/janus/javascript/
|
||||
cp -f ./patch/stream.sh /usr/share/kvmd/ && cp -f ./patch/stream_when_ustream_exists.sh /usr/share/kvmd/ && chmod +x /usr/share/kvmd/stream.sh /usr/share/kvmd/stream_when_ustream_exists.sh
|
||||
cp -f ./patches/adapter.js /usr/share/janus/javascript/ && cp -f ./patches/janus.js /usr/share/janus/javascript/
|
||||
fi
|
||||
cp -f ./patches/stream.sh /usr/share/kvmd/ && cp -f ./patches/stream_when_ustream_exists.sh /usr/share/kvmd/ && chmod +x /usr/share/kvmd/stream.sh /usr/share/kvmd/stream_when_ustream_exists.sh
|
||||
#启动服务
|
||||
systemctl enable kvmd-ffmpeg && systemctl enable kvmd-janus-static
|
||||
systemctl start kvmd-ffmpeg && systemctl start kvmd-janus-static
|
||||
systemctl daemon-reload
|
||||
! $NOTCHROOT || systemctl enable kvmd-ffmpeg && systemctl enable kvmd-janus-static
|
||||
! $NOTCHROOT || systemctl start kvmd-ffmpeg && systemctl start kvmd-janus-static
|
||||
echo "配置完成"
|
||||
}
|
||||
|
||||
kvmd_ffmpeg_h-264
|
||||
49
opikvm-logo.svg
Normal file
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
|
||||
--- kvmd/aiohelpers.py 2023-01-30 03:25:23.556804000 +0700
|
||||
+++ kvmd/aiohelpers.py 2023-01-30 08:12:21.773899000 +0700
|
||||
@@ -38,11 +38,26 @@
|
||||
From 3d137882ac38ac046b7d09cada1883b304b04319 Mon Sep 17 00:00:00 2001
|
||||
From: xe5700 <9338143+xe5700@users.noreply.github.com>
|
||||
Date: Fri, 20 May 2022 18:34:21 +0800
|
||||
Subject: [PATCH] Revert force eject feature to unlock helper
|
||||
|
||||
---
|
||||
kvmd/aiohelpers.py | 31 ++++++++++++-----
|
||||
kvmd/apps/otg/__init__.py | 3 +-
|
||||
kvmd/apps/otgmsd/__init__.py | 25 +++++++++++++-
|
||||
kvmd/helpers/unlock/__init__.py | 58 ++++++++++++++++++++++++++++++++
|
||||
kvmd/helpers/unlock/__main__.py | 24 +++++++++++++
|
||||
kvmd/plugins/msd/otg/__init__.py | 19 ++++++++---
|
||||
kvmd/plugins/msd/otg/drive.py | 5 +--
|
||||
7 files changed, 145 insertions(+), 20 deletions(-)
|
||||
create mode 100644 kvmd/helpers/unlock/__init__.py
|
||||
create mode 100644 kvmd/helpers/unlock/__main__.py
|
||||
|
||||
diff --git a/kvmd/aiohelpers.py b/kvmd/aiohelpers.py
|
||||
index 6357764c..37a5d4b9 100644
|
||||
--- a/kvmd/aiohelpers.py
|
||||
+++ b/kvmd/aiohelpers.py
|
||||
@@ -40,11 +40,26 @@ async def remount(name: str, base_cmd: List[str], rw: bool) -> bool:
|
||||
]
|
||||
logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), tools.cmdfmt(cmd))
|
||||
logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), cmd)
|
||||
try:
|
||||
- proc = await aioproc.log_process(cmd, logger)
|
||||
- if proc.returncode != 0:
|
||||
@ -19,7 +37,7 @@ diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
|
||||
+ raise
|
||||
+
|
||||
+
|
||||
+async def unlock_drive(base_cmd: list[str]) -> None:
|
||||
+async def unlock_drive(base_cmd: List[str]) -> None:
|
||||
+ logger = get_logger(0)
|
||||
+ logger.info("Unlocking the drive ...")
|
||||
+ try:
|
||||
@ -30,16 +48,17 @@ diff -ruN kvmd/aiohelpers.py kvmd/aiohelpers.py
|
||||
+
|
||||
+
|
||||
+# =====
|
||||
+async def _run_helper(cmd: list[str]) -> None:
|
||||
+async def _run_helper(cmd: List[str]) -> None:
|
||||
+ logger = get_logger(0)
|
||||
+ logger.info("Executing helper %s ...", cmd)
|
||||
+ proc = await aioproc.log_process(cmd, logger)
|
||||
+ if proc.returncode != 0:
|
||||
+ logger.error(f"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}")
|
||||
diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
|
||||
--- kvmd/apps/otg/__init__.py 2022-12-22 10:01:48.000000000 +0700
|
||||
+++ kvmd/apps/otg/__init__.py 2023-01-30 03:51:51.331539000 +0700
|
||||
@@ -182,7 +182,6 @@
|
||||
+ raise MsdError(f"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}")
|
||||
diff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py
|
||||
index cbf7a197..d0ed0554 100644
|
||||
--- a/kvmd/apps/otg/__init__.py
|
||||
+++ b/kvmd/apps/otg/__init__.py
|
||||
@@ -182,7 +182,6 @@ class _GadgetConfig:
|
||||
_chown(join(func_path, "lun.0/cdrom"), user)
|
||||
_chown(join(func_path, "lun.0/ro"), user)
|
||||
_chown(join(func_path, "lun.0/file"), user)
|
||||
@ -47,7 +66,7 @@ diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
|
||||
_symlink(func_path, join(self.__profile_path, func))
|
||||
name = ("Mass Storage Drive" if self.__msd_instance == 0 else f"Extra Drive #{self.__msd_instance}")
|
||||
self.__create_meta(func, name)
|
||||
@@ -291,7 +290,7 @@
|
||||
@@ -291,7 +290,7 @@ def _cmd_stop(config: Section) -> None:
|
||||
logger.info("Disabling gadget %r ...", config.otg.gadget)
|
||||
_write(join(gadget_path, "UDC"), "\n")
|
||||
|
||||
@ -56,21 +75,27 @@ diff -ruN kvmd/apps/otg/__init__.py kvmd/apps/otg/__init__.py
|
||||
|
||||
profile_path = join(gadget_path, usb.G_PROFILE)
|
||||
for func in os.listdir(profile_path):
|
||||
diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py
|
||||
--- kvmd/apps/otgmsd/__init__.py 2022-12-22 10:01:48.000000000 +0700
|
||||
+++ kvmd/apps/otgmsd/__init__.py 2023-01-30 04:35:09.702576000 +0700
|
||||
@@ -21,8 +21,10 @@
|
||||
diff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py
|
||||
index f57b3107..78f8e3c7 100644
|
||||
--- a/kvmd/apps/otgmsd/__init__.py
|
||||
+++ b/kvmd/apps/otgmsd/__init__.py
|
||||
@@ -21,12 +21,15 @@
|
||||
|
||||
|
||||
import os
|
||||
+import signal
|
||||
import errno
|
||||
import argparse
|
||||
+import psutil
|
||||
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
+import psutil
|
||||
+
|
||||
from ...validators.basic import valid_bool
|
||||
from ...validators.basic import valid_int_f0
|
||||
@@ -53,6 +55,21 @@
|
||||
from ...validators.os import valid_abs_file
|
||||
@@ -56,6 +59,21 @@ def _set_param(gadget: str, instance: int, param: str, value: str) -> None:
|
||||
raise
|
||||
|
||||
|
||||
@ -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(
|
||||
@@ -77,7 +94,7 @@
|
||||
parser.add_argument("--eject", action="store_true",
|
||||
help="Eject the image")
|
||||
parser.add_argument("--unlock", action="store_true",
|
||||
- help="Does nothing, just for backward compatibility")
|
||||
@@ -70,6 +88,8 @@ def main(argv: Optional[List[str]]=None) -> None:
|
||||
)
|
||||
parser.add_argument("-i", "--instance", default=0, type=valid_int_f0,
|
||||
metavar="<N>", help="Drive instance (0 for KVMD drive)")
|
||||
+ parser.add_argument("--unlock", action="store_true",
|
||||
+ help="Send SIGUSR1 to MSD kernel thread")
|
||||
options = parser.parse_args(argv[1:])
|
||||
|
||||
if config.kvmd.msd.type != "otg":
|
||||
@@ -87,8 +104,11 @@
|
||||
parser.add_argument("--set-cdrom", default=None, type=valid_bool,
|
||||
metavar="<1|0|yes|no>", help="Set CD-ROM flag")
|
||||
parser.add_argument("--set-rw", default=None, type=valid_bool,
|
||||
@@ -89,8 +109,11 @@ def main(argv: Optional[List[str]]=None) -> None:
|
||||
set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value))
|
||||
get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))
|
||||
|
||||
@ -114,9 +139,11 @@ diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py
|
||||
|
||||
if options.set_cdrom is not None:
|
||||
set_param("cdrom", str(int(options.set_cdrom)))
|
||||
diff -ruN kvmd/helpers/unlock/__init__.py kvmd/helpers/unlock/__init__.py
|
||||
--- kvmd/helpers/unlock/__init__.py 1970-01-01 07:00:00.000000000 +0700
|
||||
+++ kvmd/helpers/unlock/__init__.py 2023-01-30 04:04:07.000000000 +0700
|
||||
diff --git a/kvmd/helpers/unlock/__init__.py b/kvmd/helpers/unlock/__init__.py
|
||||
new file mode 100644
|
||||
index 00000000..140e0e7c
|
||||
--- /dev/null
|
||||
+++ b/kvmd/helpers/unlock/__init__.py
|
||||
@@ -0,0 +1,58 @@
|
||||
+# ========================================================================== #
|
||||
+# #
|
||||
@ -176,9 +203,11 @@ diff -ruN kvmd/helpers/unlock/__init__.py kvmd/helpers/unlock/__init__.py
|
||||
+ if len(sys.argv) != 2 or sys.argv[1] != "unlock":
|
||||
+ raise SystemExit(f"Usage: {sys.argv[0]} [unlock]")
|
||||
+ _unlock()
|
||||
diff -ruN kvmd/helpers/unlock/__main__.py kvmd/helpers/unlock/__main__.py
|
||||
--- kvmd/helpers/unlock/__main__.py 1970-01-01 07:00:00.000000000 +0700
|
||||
+++ kvmd/helpers/unlock/__main__.py 2023-01-30 04:04:07.000000000 +0700
|
||||
diff --git a/kvmd/helpers/unlock/__main__.py b/kvmd/helpers/unlock/__main__.py
|
||||
new file mode 100644
|
||||
index 00000000..3849d1b9
|
||||
--- /dev/null
|
||||
+++ b/kvmd/helpers/unlock/__main__.py
|
||||
@@ -0,0 +1,24 @@
|
||||
+# ========================================================================== #
|
||||
+# #
|
||||
@ -204,10 +233,87 @@ diff -ruN kvmd/helpers/unlock/__main__.py kvmd/helpers/unlock/__main__.py
|
||||
+
|
||||
+from . import main
|
||||
+main()
|
||||
diff -ruN kvmd/plugins/msd/otg/drive.py kvmd/plugins/msd/otg/drive.py
|
||||
--- kvmd/plugins/msd/otg/drive.py 2022-12-22 10:01:48.000000000 +0700
|
||||
+++ kvmd/plugins/msd/otg/drive.py 2023-01-30 06:31:13.923959000 +0700
|
||||
@@ -51,10 +51,7 @@
|
||||
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
|
||||
index 409b899a..1342c6b4 100644
|
||||
--- a/kvmd/plugins/msd/otg/__init__.py
|
||||
+++ b/kvmd/plugins/msd/otg/__init__.py
|
||||
@@ -140,6 +140,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
storage_path: str,
|
||||
|
||||
remount_cmd: List[str],
|
||||
+ unlock_cmd: List[str],
|
||||
|
||||
initial: Dict,
|
||||
|
||||
@@ -154,6 +155,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
self.__meta_path = os.path.join(self.__storage_path, "meta")
|
||||
|
||||
self.__remount_cmd = remount_cmd
|
||||
+ self.__unlock_cmd = unlock_cmd
|
||||
|
||||
self.__initial_image: str = initial["image"]
|
||||
self.__initial_cdrom: bool = initial["cdrom"]
|
||||
@@ -178,10 +180,8 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
"storage": Option("/var/lib/kvmd/msd", type=valid_abs_dir, unpack_as="storage_path"),
|
||||
|
||||
- "remount_cmd": Option([
|
||||
- "/usr/bin/sudo", "--non-interactive",
|
||||
- "/usr/bin/kvmd-helper-otgmsd-remount", "{mode}",
|
||||
- ], type=valid_command),
|
||||
+ "remount_cmd": Option([*sudo, "/usr/bin/kvmd-helper-otgmsd-remount", "{mode}"], type=valid_command),
|
||||
+ "unlock_cmd": Option([*sudo, "/usr/bin/kvmd-helper-otgmsd-unlock", "unlock"], type=valid_command),
|
||||
|
||||
"initial": {
|
||||
"image": Option("", type=valid_printable_filename, if_empty=""),
|
||||
@@ -241,6 +241,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
async def reset(self) -> None:
|
||||
async with self.__state.busy(check_online=False):
|
||||
try:
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_image_path("")
|
||||
self.__drive.set_rw_flag(False)
|
||||
self.__drive.set_cdrom_flag(False)
|
||||
@@ -290,12 +291,15 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
if not os.path.exists(self.__state.vd.image.path):
|
||||
raise MsdUnknownImageError()
|
||||
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
|
||||
self.__drive.set_image_path(self.__state.vd.image.path)
|
||||
|
||||
else:
|
||||
if not (self.__state.vd.connected or self.__drive.get_image_path()):
|
||||
raise MsdDisconnectedError()
|
||||
+
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_image_path("")
|
||||
|
||||
self.__state.vd.connected = connected
|
||||
@@ -474,6 +478,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
if os.path.exists(path):
|
||||
logger.info("Setting up initial image %r ...", self.__initial_image)
|
||||
try:
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_cdrom_flag(self.__initial_cdrom)
|
||||
self.__drive.set_image_path(path)
|
||||
except Exception:
|
||||
@@ -541,4 +546,8 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
async def __remount_storage(self, rw: bool) -> None:
|
||||
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
|
||||
- raise MsdError("Can't execute remount helper")
|
||||
+ pass
|
||||
+ #raise MsdError("Can't execute remount helper")
|
||||
+
|
||||
+ async def __unlock_drive(self) -> None:
|
||||
+ await helpers.unlock_drive(self.__unlock_cmd)
|
||||
\ No newline at end of file
|
||||
diff --git a/kvmd/plugins/msd/otg/drive.py b/kvmd/plugins/msd/otg/drive.py
|
||||
index 11af7f81..ee54e5e9 100644
|
||||
--- a/kvmd/plugins/msd/otg/drive.py
|
||||
+++ b/kvmd/plugins/msd/otg/drive.py
|
||||
@@ -53,10 +53,7 @@ class Drive:
|
||||
# =====
|
||||
|
||||
def set_image_path(self, path: str) -> None:
|
||||
@ -219,78 +325,6 @@ diff -ruN kvmd/plugins/msd/otg/drive.py kvmd/plugins/msd/otg/drive.py
|
||||
|
||||
def get_image_path(self) -> str:
|
||||
return self.__get_param("file")
|
||||
diff -ruN kvmd/plugins/msd/otg/__init__.py kvmd/plugins/msd/otg/__init__.py
|
||||
--- kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:42:28.021418683 +0700
|
||||
+++ kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:50:38.774955045 +0700
|
||||
@@ -129,6 +129,7 @@
|
||||
sync_chunk_size: int,
|
||||
--
|
||||
2.34.1.windows.1
|
||||
|
||||
remount_cmd: list[str],
|
||||
+ unlock_cmd: list[str],
|
||||
|
||||
initial: dict,
|
||||
|
||||
@@ -140,6 +141,7 @@
|
||||
self.__sync_chunk_size = sync_chunk_size
|
||||
|
||||
self.__remount_cmd = remount_cmd
|
||||
+ self.__unlock_cmd = unlock_cmd
|
||||
|
||||
self.__initial_image: str = initial["image"]
|
||||
self.__initial_cdrom: bool = initial["cdrom"]
|
||||
@@ -169,6 +171,11 @@
|
||||
"/usr/bin/kvmd-helper-otgmsd-remount", "{mode}",
|
||||
], type=valid_command),
|
||||
|
||||
+ "unlock_cmd": Option([
|
||||
+ "/usr/bin/sudo", "--non-interactive",
|
||||
+ "/usr/bin/kvmd-helper-otgmsd-unlock", "unlock",
|
||||
+ ], type=valid_command),
|
||||
+
|
||||
"initial": {
|
||||
"image": Option("", type=valid_printable_filename, if_empty=""),
|
||||
"cdrom": Option(False, type=valid_bool),
|
||||
@@ -230,6 +237,7 @@
|
||||
async def reset(self) -> None:
|
||||
async with self.__state.busy(check_online=False):
|
||||
try:
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_image_path("")
|
||||
self.__drive.set_cdrom_flag(False)
|
||||
self.__drive.set_rw_flag(False)
|
||||
@@ -286,7 +294,7 @@
|
||||
raise MsdUnknownImageError()
|
||||
|
||||
assert self.__state.vd.image.in_storage
|
||||
-
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_rw_flag(self.__state.vd.rw)
|
||||
self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
|
||||
if self.__state.vd.rw:
|
||||
@@ -294,6 +302,7 @@
|
||||
self.__drive.set_image_path(self.__state.vd.image.path)
|
||||
|
||||
else:
|
||||
+ await self.__unlock_drive()
|
||||
self.__state_check_connected()
|
||||
self.__drive.set_image_path("")
|
||||
await self.__remount_rw(False, fatal=False)
|
||||
@@ -499,6 +508,7 @@
|
||||
if image.exists():
|
||||
logger.info("Setting up initial image %r ...", self.__initial_image)
|
||||
try:
|
||||
+ await self.__unlock_drive()
|
||||
self.__drive.set_rw_flag(False)
|
||||
self.__drive.set_cdrom_flag(self.__initial_cdrom)
|
||||
self.__drive.set_image_path(image.path)
|
||||
@@ -531,5 +541,8 @@
|
||||
|
||||
async def __remount_rw(self, rw: bool, fatal: bool=True) -> None:
|
||||
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
|
||||
- if fatal:
|
||||
- raise MsdError("Can't execute remount helper")
|
||||
+ pass
|
||||
+ #raise MsdError("Can't execute remount helper")
|
||||
+
|
||||
+ async def __unlock_drive(self) -> None:
|
||||
+ await aiohelpers.unlock_drive(self.__unlock_cmd)
|
||||
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. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
@ -31,7 +31,7 @@ from ....logging import get_logger
|
||||
|
||||
from .... import env
|
||||
from .... import tools
|
||||
from .... import aiofs
|
||||
from .... import aiotools
|
||||
from .... import aioproc
|
||||
|
||||
from .base import BaseInfoSubmanager
|
||||
@ -46,33 +46,36 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
||||
def __init__(
|
||||
self,
|
||||
vcgencmd_cmd: list[str],
|
||||
ignore_past: bool,
|
||||
state_poll: float,
|
||||
) -> None:
|
||||
|
||||
self.__vcgencmd_cmd = vcgencmd_cmd
|
||||
self.__ignore_past = ignore_past
|
||||
self.__state_poll = state_poll
|
||||
|
||||
self.__dt_cache: dict[str, str] = {}
|
||||
|
||||
async def get_state(self) -> dict:
|
||||
(model, cpu_temp, throttling) = await asyncio.gather(
|
||||
self.__read_dt_file("model"),
|
||||
self.__get_cpu_temp(),
|
||||
self.__get_throttling(),
|
||||
)
|
||||
return {
|
||||
"platform": {
|
||||
"type": "rpi",
|
||||
"base": model,
|
||||
"serial": "0000000000000000",
|
||||
},
|
||||
"health": {
|
||||
"temp": {
|
||||
"cpu": cpu_temp,
|
||||
},
|
||||
"throttling": throttling,
|
||||
},
|
||||
}
|
||||
#async def get_state(self) -> dict:
|
||||
# (model, serial, cpu_temp, throttling) = await asyncio.gather(
|
||||
# self.__read_dt_file("model"),
|
||||
# self.__read_dt_file("serial-number"),
|
||||
# self.__get_cpu_temp(),
|
||||
# self.__get_throttling(),
|
||||
# )
|
||||
# return {
|
||||
# "platform": {
|
||||
# "type": "rpi",
|
||||
# "base": model,
|
||||
# "serial": serial,
|
||||
# },
|
||||
# "health": {
|
||||
# "temp": {
|
||||
# "cpu": cpu_temp,
|
||||
# },
|
||||
# "throttling": throttling,
|
||||
# },
|
||||
# }
|
||||
|
||||
async def poll_state(self) -> AsyncGenerator[dict, None]:
|
||||
prev_state: dict = {}
|
||||
@ -89,7 +92,7 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
||||
if name not in self.__dt_cache:
|
||||
path = os.path.join(f"{env.PROCFS_PREFIX}/proc/device-tree", name)
|
||||
try:
|
||||
self.__dt_cache[name] = (await aiofs.read(path)).strip(" \t\r\n\0")
|
||||
self.__dt_cache[name] = (await aiotools.read_file(path)).strip(" \t\r\n\0")
|
||||
except Exception as err:
|
||||
get_logger(0).error("Can't read DT %s from %s: %s", name, path, err)
|
||||
return None
|
||||
@ -98,8 +101,9 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
||||
async def __get_cpu_temp(self) -> (float | None):
|
||||
temp_path = f"{env.SYSFS_PREFIX}/sys/class/thermal/thermal_zone0/temp"
|
||||
try:
|
||||
return int((await aiofs.read(temp_path)).strip()) / 1000
|
||||
return int((await aiotools.read_file(temp_path)).strip()) / 1000
|
||||
except Exception as err:
|
||||
get_logger(0).error("Can't read CPU temp from %s: %s", temp_path, err)
|
||||
return None
|
||||
|
||||
async def __get_throttling(self) -> (dict | None):
|
||||
@ -125,6 +129,7 @@ class HwInfoSubmanager(BaseInfoSubmanager):
|
||||
"past": bool(flags & (1 << 18)),
|
||||
},
|
||||
},
|
||||
"ignore_past": self.__ignore_past,
|
||||
}
|
||||
return None
|
||||
|
||||
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