diff --git a/config/main.yaml b/config/main.yaml new file mode 100644 index 00000000..4ff1798d --- /dev/null +++ b/config/main.yaml @@ -0,0 +1,62 @@ +# 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 + cmd: + - "/usr/bin/ustreamer" + - "--device=/dev/kvmd-video" + - "--persistent" + - "--format=mjpeg" + - "--encoder=cpu" + - "--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" diff --git a/config/override.yaml b/config/override.yaml new file mode 100644 index 00000000..daba01f3 --- /dev/null +++ b/config/override.yaml @@ -0,0 +1,69 @@ +#################################################################### +# # +# Override Pi-KVM system settings. This file uses the YAML syntax. # +# # +# https://github.com/pikvm/pikvm/blob/master/pages/config.md # +# # +# All overridden parameters will be applied AFTER other configs # +# and "!include" directives and BEFORE validation. # +# Not: Sections should be combined under shared keys. # +# # +#################################################################### +# +######### +# Wrong # +######### +#kvmd: +# gpio: +# drivers: ... +#kvmd: +# gpio: +# scheme: ... +# +########### +# Correct # +########### +#kvmd: +# gpio: +# drivers: ... +# scheme: ... +# +########### +# Example # +########### +vnc: + # See https://github.com/pikvm/pikvm/blob/master/pages/vnc.md + keymap: /usr/share/kvmd/keymaps/ru # Set russian keymap + auth: + vncauth: + enabled: true # Enable auth via /etc/kvmd/vncpasswd +kvmd: + msd: + type: disabled + gpio: + drivers: + short_press: + type: cmd + cmd: [/usr/bin/sudo, short_press_gpio420] + long_press: + type: cmd + cmd: [/usr/bin/sudo, long_press_gpio420] + scheme: + short_button: + driver: short_press + pin: 0 + mode: output + switch: false + long_button: + driver: long_press + pin: 0 + mode: output + switch: false + view: + header: + title: ATX + table: + - ["#电源管理"] + - [] + - ["#短按(开/关机):", short_button|按下] + - ["#长按(强制关机):", long_button|按下] diff --git a/fruity-pikvm_0.2_armhf.deb b/fruity-pikvm_0.2_armhf.deb new file mode 100644 index 00000000..bfebf0d0 Binary files /dev/null and b/fruity-pikvm_0.2_armhf.deb differ diff --git a/install.sh b/install.sh new file mode 100644 index 00000000..4aee3e2f --- /dev/null +++ b/install.sh @@ -0,0 +1,28 @@ +PYVER=$(python3 -V) +ARCH=$(uname -m) +CURRENTWD=$PWD +echo $PYVER +echo $ARCH + +if [[ "$PYVER" != *"3.10"* && $(which python3.10) != *"python"* ]]; then + echo "你似乎没有安装 Python 3.10!" +fi + +mv ./patch/meson8b-onecloud.dtb /boot/dtb/meson8b-onecloud.dtb && echo "设备树文件覆盖成功" +gzip -dc ./patch/Boot_SkipUSBBurning.gz | dd of=/dev/mmcblk1 && echo "覆盖引导成功" + +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 "换源成功!" +apt install -y nginx tesseract-ocr tesseract-ocr-eng janus libevent-dev libgpiod-dev tesseract-ocr-chi-sim +echo "正在安装PiKVM......" +dgkg -i ./fruity-pikvm_0.2_armhf.deb && echo "PiKVM安装成功!" && systemctl enable kvmd-vnc + +mv ./patch/chinese.patch /usr/share/kvmd/web/ && cd /usr/share/kvmd/web/ && patch -p0 < chinese.patch +mv ./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 "补丁应用成功!" + +cd $CURRENTWD && mv ./patch/long_press_gpio420 /usr/bin && mv ./patch/short_press_gpio420 /usr/bin && echo "GPIO-420脚本移动成功!" +mv ./config/main.yaml /etc/kvmd/ && mv ./config/override.yaml /etc/kvmd/ && echo "配置文件修改成功!" + +kvmd -m && echo "请给玩客云重新上电,然后就可以开始使用One-KVM了!" + + diff --git a/patch/3.198msd.patch b/patch/3.198msd.patch new file mode 100644 index 00000000..1257ceef --- /dev/null +++ b/patch/3.198msd.patch @@ -0,0 +1,296 @@ +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 @@ + ] + logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), tools.cmdfmt(cmd)) + try: +- proc = await aioproc.log_process(cmd, logger) +- if proc.returncode != 0: +- assert proc.returncode is not None +- raise subprocess.CalledProcessError(proc.returncode, cmd) +- except Exception as err: +- logger.error("Can't remount %s storage: %s", name, tools.efmt(err)) +- return False +- return True ++ await _run_helper(cmd) ++ except Exception: ++ logger.error("Can't remount internal storage") ++ raise ++ ++ ++async def unlock_drive(base_cmd: list[str]) -> None: ++ logger = get_logger(0) ++ logger.info("Unlocking the drive ...") ++ try: ++ await _run_helper(base_cmd) ++ except Exception: ++ logger.error("Can't unlock the drive") ++ raise ++ ++ ++# ===== ++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 @@ + _chown(join(func_path, "lun.0/cdrom"), user) + _chown(join(func_path, "lun.0/ro"), user) + _chown(join(func_path, "lun.0/file"), user) +- _chown(join(func_path, "lun.0/forced_eject"), user) + _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 @@ + logger.info("Disabling gadget %r ...", config.otg.gadget) + _write(join(gadget_path, "UDC"), "\n") + +- _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True) ++ _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), True) + + profile_path = join(gadget_path, usb.G_PROFILE) + for func in os.listdir(profile_path): +diff -ruN kvmd/apps/otgmsd/__init__.py kvmd/apps/otgmsd/__init__.py +--- kvmd/apps/otgmsd/__init__.py 2022-12-22 10:01:48.000000000 +0700 ++++ kvmd/apps/otgmsd/__init__.py 2023-01-30 04:35:09.702576000 +0700 +@@ -21,8 +21,10 @@ + + + import os ++import signal + import errno + import argparse ++import psutil + + from ...validators.basic import valid_bool + from ...validators.basic import valid_int_f0 +@@ -53,6 +55,21 @@ + raise + + ++def _unlock() -> None: ++ # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924 ++ found = False ++ for proc in psutil.process_iter(): ++ attrs = proc.as_dict(attrs=["name", "exe", "pid"]) ++ if attrs.get("name") == "file-storage" and not attrs.get("exe"): ++ try: ++ proc.send_signal(signal.SIGUSR1) ++ found = True ++ except Exception as err: ++ raise SystemExit(f"Can't send SIGUSR1 to MSD kernel thread with pid={attrs['pid']}: {err}") ++ if not found: ++ raise SystemExit("Can't find MSD kernel thread") ++ ++ + # ===== + def main(argv: (list[str] | None)=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") ++ help="Send SIGUSR1 to MSD kernel thread") + options = parser.parse_args(argv[1:]) + + if config.kvmd.msd.type != "otg": +@@ -87,8 +104,11 @@ + set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value)) + get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param)) + ++ if options.unlock: ++ _unlock() ++ + if options.eject: +- set_param("forced_eject", "") ++ set_param("file", "") + + if options.set_cdrom is not None: + set_param("cdrom", str(int(options.set_cdrom))) +diff -ruN kvmd/helpers/unlock/__init__.py kvmd/helpers/unlock/__init__.py +--- kvmd/helpers/unlock/__init__.py 1970-01-01 07:00:00.000000000 +0700 ++++ kvmd/helpers/unlock/__init__.py 2023-01-30 04:04:07.000000000 +0700 +@@ -0,0 +1,58 @@ ++# ========================================================================== # ++# # ++# KVMD - The main PiKVM daemon. # ++# # ++# Copyright (C) 2018-2022 Maxim Devaev # ++# # ++# 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 . # ++# # ++# ========================================================================== # ++ ++ ++import sys ++import signal ++ ++import psutil ++ ++ ++# ===== ++_PROCESS_NAME = "file-storage" ++ ++ ++# ===== ++def _log(msg: str) -> None: ++ print(msg, file=sys.stderr) ++ ++ ++def _unlock() -> None: ++ # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924 ++ found = False ++ for proc in psutil.process_iter(): ++ attrs = proc.as_dict(attrs=["name", "exe", "pid"]) ++ if attrs.get("name") == _PROCESS_NAME and not attrs.get("exe"): ++ _log(f"Sending SIGUSR1 to MSD {_PROCESS_NAME!r} kernel thread with pid={attrs['pid']} ...") ++ try: ++ proc.send_signal(signal.SIGUSR1) ++ found = True ++ except Exception as err: ++ raise SystemExit(f"Can't send SIGUSR1 to MSD kernel thread with pid={attrs['pid']}: {err}") ++ if not found: ++ raise SystemExit(f"Can't find MSD kernel thread {_PROCESS_NAME!r}") ++ ++ ++# ===== ++def main() -> None: ++ 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 +@@ -0,0 +1,24 @@ ++# ========================================================================== # ++# # ++# KVMD - The main PiKVM daemon. # ++# # ++# Copyright (C) 2018-2022 Maxim Devaev # ++# # ++# 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 . # ++# # ++# ========================================================================== # ++ ++ ++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 @@ + # ===== + + def set_image_path(self, path: str) -> None: +- if path: +- self.__set_param("file", path) +- else: +- self.__set_param("forced_eject", "") ++ self.__set_param("file", path) + + def get_image_path(self) -> str: + return self.__get_param("file") +diff -ruN kvmd/plugins/msd/otg/__init__.py kvmd/plugins/msd/otg/__init__.py +--- kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:42:28.021418683 +0700 ++++ kvmd/plugins/msd/otg/__init__.py 2023-02-02 09:50:38.774955045 +0700 +@@ -129,6 +129,7 @@ + sync_chunk_size: int, + + 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) diff --git a/Boot_SkipUSBBurning.gz b/patch/Boot_SkipUSBBurning.gz similarity index 100% rename from Boot_SkipUSBBurning.gz rename to patch/Boot_SkipUSBBurning.gz diff --git a/chinese.patch b/patch/chinese.patch similarity index 100% rename from chinese.patch rename to patch/chinese.patch diff --git a/patch/long_press_gpio420 b/patch/long_press_gpio420 new file mode 100644 index 00000000..5639da55 --- /dev/null +++ b/patch/long_press_gpio420 @@ -0,0 +1,7 @@ +#!/bin/bash +echo 420 > /sys/class/gpio/export +echo out > /sys/class/gpio/gpio420/direction +echo 0 > /sys/class/gpio/gpio420/value +sleep 5 +echo 1 > /sys/class/gpio/gpio420/value +echo 420 > /sys/class/gpio/unexport \ No newline at end of file diff --git a/meson8b-onecloud.dtb b/patch/meson8b-onecloud.dtb similarity index 100% rename from meson8b-onecloud.dtb rename to patch/meson8b-onecloud.dtb diff --git a/patch/short_press_gpio420 b/patch/short_press_gpio420 new file mode 100644 index 00000000..f2dfbc24 --- /dev/null +++ b/patch/short_press_gpio420 @@ -0,0 +1,7 @@ +#!/bin/bash +echo 420 > /sys/class/gpio/export +echo out > /sys/class/gpio/gpio420/direction +echo 0 > /sys/class/gpio/gpio420/value +sleep 0.5 +echo 1 > /sys/class/gpio/gpio420/value +echo 420 > /sys/class/gpio/unexport \ No newline at end of file