Full Code of xe5700/kvmd-armbian for AI

master ca73920f6cc7 cached
25 files
93.5 KB
28.9k tokens
1 requests
Download .txt
Repository: xe5700/kvmd-armbian
Branch: master
Commit: ca73920f6cc7
Files: 25
Total size: 93.5 KB

Directory structure:
gitextract_k6lnbzxp/

├── .gitignore
├── LICENSE
├── README-zh-CN.MD
├── README.MD
├── amglogic-dtb-mod.py
├── armbian/
│   ├── armbian-motd
│   ├── opt/
│   │   ├── armbian-sysinfo
│   │   └── vcgencmd
│   └── udev/
│       └── rules.d/
│           └── 99-kvmd.rules
├── bin/
│   └── kvmd-helper-otgmsd-unlock
├── config.sh
├── dtb/
│   └── 4.4/
│       └── rk322x-box.dtb
├── install-mirror.sh
├── install.sh
├── libs/
│   ├── checksum.sh
│   ├── download_aria2.sh
│   └── download_wget.sh
└── patches/
    ├── custom/
    │   └── old-kernel-msd/
    │       ├── apply.sh
    │       ├── v3.124-v3.142/
    │       │   └── 0001-Apply-old-kernel-msd-patch-for-v3.134.patch
    │       └── v3.84-v3.92/
    │           ├── 0001-Revert-force-eject-feature-to-unlock-helper.patch
    │           └── 0003-Allow-skip-some-features-unsupports-on-old-linux-ker.patch
    ├── disable_gpio/
    │   ├── v3.47-v3.81/
    │   │   └── 0001-Disable-GPIO-For-TV-Box.patch
    │   ├── v3.82-v3.83/
    │   │   └── 0001-Disable-GPIO-For-TV-Box.patch
    │   └── v3.84-v3.134/
    │       └── 0001-Disable-GPIO-For-TV-Box.patch
    └── genernal/
        └── v3.84-v3.92/
            └── 0001-Allow-skip-some-features-unsupports-on-old-linux-ker.patch

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
workspace.code-workspace
tmp
.vscode

================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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 2 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, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

================================================
FILE: README-zh-CN.MD
================================================
# 感谢由toss-a编写的中文教程
该教程未验证,可能需要使用 https://github.com/toss-a/pikvm-armbian 该分叉才能运行。

你可以尝试其他的项目基于pikvm
分支版本 kvmd-armbian https://github.com/srepac/kvmd-armbian
onekvm 项目 https://github.com/mofeng-git/One-KVM

# 硬件准备

## 开发版选择 

建议选择能刷比较新的armbian的开发版作为PI-KVM

##### 1.选择原生带有OTG的开发版 例如:

- Orange Pi Prime(貌似已停产)

- Orange Pi Zero(需要制作一分二数据线 性能比较差 适合低成本1080P 30FPS方案)

- Orange Pi Zero LTS(和Orange Pi Zero一样)
  - 等其他 未列举完

##### 2.选择非原生支持修改dtb来实现OTG功能的开发版 例如:

- King 3399 (1个USB3.0 和一个Type-c 3.0做为OTG 能够满足1080P 60FPS方案)

- phicomm n1 (理论可以 把刷机那USB作为OTG 未做测试 不建议购买 性价比并不高)
  - 等其他 未列举完 

## 视频采集设备选择 HDMI转USB

- 如果你的开发版只有USB 2.0 建议选择ms2109 而且价格便宜 tb 30 RMB左右的即可

- 如果你的开发版有USB 3.0 建议选择ms2130 淘宝偏贵点 可输出1080P 60FPS
  - 以上设备我都已测试可以用 但是 部分主板可能使用这两款视频采集设备都会导致进BIOS颜色输出有问题(又不是不能用)

- 其他的视频采集设备也可以 你可以试试???

## OTG数据线准备

- 开发版OTG口为单独的Type-C口或Micro USB 那么 你只需要准备一根数据线即可
- 开发版OTG口为开发版供电 你需要手动制作一分二数据线 如下图

![1to2](https://raw.githubusercontent.com/toss-a/pikvm-armbian/master/1to2.png)

- 开发版OTG口为USB 修改dtb后实现的  你需要准备USB-A 转 USB-A 线缆

  建议切断 USB 线的电源线,它可能会导致 OTG 断开连接。

#### 第一步

- 刷入 armbian 用于您的开发版或电视盒(如果内核不支持 otg,您应该构建一个启用 otg 功能的内核)
- 如果你的开发版或者电视机或者支持从SD卡启动或者U盘启动 那么你需要把armbian写入到可移动存储介质上,写入完成后将一部分空闲分区划分为10G左右的空间格式化为EXT4 作为PI KVM 镜像储存分区(可选 非必须)

#### 第二步

- 修改您的 dtb 文件以启用 otg 功能。对于 otg USB 端口,将 dr_mode 从host更改为peripheral。
- 修改方法(Linux 推荐 Ubuntu)
  - sudo apt install device-tree-compiler
  - dtc 你开发版dtb -I dtb -O dts -o 输出的名称.dts
  - 修改 将dr_mode = "host" 修改为dr_mode = "peripheral"
  - dtc 刚刚修改的dts.dts -I dts -O dtb -o 你开发版.dtb
  - 替换,然后重启你的开发版

#### 第三步

- ```
  git clone https://github.com/toss-a/pikvm-armbian.git
  cd pikvm-armbian
  ./install.sh
  ```

- 我们内核比较新 不需要 所以按n
- 重启

#### 第四步

- 重启后再次运行刚刚运行的./install.sh
- 安装完成!!!

#### 启用大容量存储设备

需要格式化为EXT4

- 1.开发版或电视盒从U盘或者SD卡启动 使用内置emmc做为PI KVM 镜像存储
- 2.在armbian启动之前划分了PI KVM 镜像存储分区
- 3.再插入一个SD卡或U盘格式化为EXT4做为PI KVM 镜像存储



- 修改挂载点

  - ```
    vim /etc/fstab
    ```

  - 添加(/dev/sda1需要修改为你的存储介质dev路径)

  - ```
    /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
    ```

  - ```
    vim /etc/kvmd/override.yaml
    ```

  - 删除以下两行

  - ```
    msd:
    type:disable
    ```

  - 重启

#### 修复重启后视频采集设备无法采集到视频

- 1.以root用户登陆后克隆

- ```
  git clone https://github.com/jkulesza/usbreset
  ```

- 2.打开克隆的目录

- ```
  cd usbreset
  ```

- 3.将源码编译成可执行文件(如果报错请安装gcc)

- ```
  cc usbreset.c -o usbreset
  ```

- 4.赋予可执行权限

- ```
  chmod +x usbreset
  ```

- 4.获取需要重置的视频采集设备的总线和设备 ID

- ```
  lsusb
  ```

  - 我的输出内容为输出:

  - ```
    root@king3399:~# lsusb
    Bus 002 Device 002: ID 345f:2130 UltraSemi USB3 Video
    Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
    Bus 003 Device 003: ID 0c45:768a Microdia USB DEVICE
    Bus 003 Device 002: ID 05e3:0608 Genesys Logic, Inc. Hub
    Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
    Bus 006 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    root@king3399:~#
    ```
  
- 5.尝试重启你的视频采集设备(我的设备为UltraSemi USB3 Video 对应为002/002)

- ```
  ./usbreset /dev/bus/usb/002/002
  ```

- 6.成功重启USB设备后添加到开机自启动

- ```
  vim /etc/rc.local
  ```

  - 添加内容

  - ```
    sleep 5
    /root/usbreset /dev/bus/usb/002/002
    ```
  
- 7.重启开发版再次查看

#### 更改PI KVM 登陆密码

- ```
  kvmd-htpasswd set admin
  ```

- 输入你需要的密码即可

#### 感谢,如果没有他们我不可能做到

- [kvmd-armbian](https://github.com/xe5700/kvmd-armbian)

- [peacokswiss](https://github.com/xe5700/kvmd-armbian/issues/12)

- [armkvm](https://github.com/wxjiyc/amlogic-s9xxx-armbian/blob/main/rebuild#L629)
  
- [usbreset](https://github.com/jkulesza/usbreset)

================================================
FILE: README.MD
================================================
# KVMD-ARMBIAN
This project support non Raspberry Pi device to running pikvm on armbian
You can try other project based on pikvm 
fork version of kvmd-armbian https://github.com/srepac/kvmd-armbian
onekvm project https://github.com/mofeng-git/One-KVM

# Chinese installation steps
Chinese installation steps by toss-a [https://github.com/xe5700/pikvm-armbian/blob/master/README-zh-CN.MD]

# Install
KVMD Install for armbian
It support Allwinner, Amlogic and Rockchip based tv box, tested on phicomm n1, mxq pro 4k, tqc a01. 
Chipset needs support USB OTG feature, lots of old amglogic chipset not support otg feature, such as s805 and s905.
You should install armbian with debian buster or bullseye.
Then running this script to install pikvm.
Install scripts is fork from @srepac rasbian pikvm install script.

Original Script [https://kvmnerds.com/RPiKVM/install-pikvm-raspbian.sh]

# Hardware for kvmd-armbian project
* A tv box/arm board supports otg feature:
    - Tests on phicomm n1(Amlogic s905d), mxq pro 4k (rk322x), tqc a01(Allwinner H6). 
    - If you are use 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.)
      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:
    - Recommended cut off usb cable's power line, it might causes otg disconnect.

## Step 1
- Flash armbian debian [Recommended bullseye] for your tv box (If kernel not support otg you should build a kernel enable otg features)
## Step 2
- 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 
## Step 3

```
git clone https://github.com/xe5700/kvmd-armbian.git
cd kvmd-armbian
./install.sh
```
(If very slow, you can use install-mirror.sh to boost install speed.)

## Step 4
- running install.sh or install-mirror.sh after reboot os then running again.
- Enjoy

# 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)


# Update log
## Version 1.0
    
## Version 2.0
    Now support download hook, config file, diffrent version of kvmd, and fix lots of bug. 
## Version 2.1
    Fix #8 #7 #15, allow custom apt manager tools.


================================================
FILE: amglogic-dtb-mod.py
================================================


================================================
FILE: armbian/armbian-motd
================================================
#!/bin/sh
/etc/update-motd.d/10-armbian-header
/etc/update-motd.d/30-armbian-sysinfo
/etc/update-motd.d/41-armbian-config



================================================
FILE: armbian/opt/armbian-sysinfo
================================================
#!/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



================================================
FILE: armbian/opt/vcgencmd
================================================
#!/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)
                                KB=$( grep 'Memory:' /var/log/dmesg* | awk '{print $5}' | cut -d'/' -f2 | sed 's/K//g' | head -1 )
                                MB=$( echo $KB / 1024 | bc )
                                echo "total_mem=$MB";;
                        *)
                                echo "invalid option";;
                esac
        ;;
esac



================================================
FILE: armbian/udev/rules.d/99-kvmd.rules
================================================
# 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"


================================================
FILE: bin/kvmd-helper-otgmsd-unlock
================================================
#!/usr/sbin/python
# KVMD-ARMBIAN

from kvmd.helpers.unlock import main

if __name__ == "__main__":
    main()


================================================
FILE: config.sh
================================================
export APT_EXE="apt-get" #If you installed apt-fast can change it to apt-fast to boost install speed.
export GIT_EXE="git"
export MIRROR_GITHUB="https://github.com" # Use a github mirror to boost download speed in some place has no github cdn
export MIRROR_GITHUB_API="https://api.github.com"

export PIKVMREPO="https://files.pikvm.org/repos/arch/rpi4"
export PIKVMREPO_PKG="/"
#export PIKVMREPO=""
#export KVMD_VERSION="3.47" # LEGECY KVMD VERSION SUPPORTS MSD AND RUNNING ON DEBIAN BULLSEYE OR BUSTER WITHOUT PATCH

# export KVMD_VERSION=""
export KVMD_COMMON_PKG_URL="$MIRROR_GITHUB/xe5700/kvmd-armbian-repo/raw/master/kvmd-common.tar.xz"
export CUSTOM_KVMD_VERSION=1 # If you want install lastest version of kvmd set to 0
export KVMD_VERSION="3.142" # LAST KVMD VERSION SUPPORTS PYTHON3.9 
export PIKVM_KEY="912C773ABBD1B584"
export USE_GPIO=0
export DEBIAN_PYTHON=1
export KVMDCACHE="/var/cache/kvmd"
export PKGINFO="${KVMDCACHE}/packages.txt"
export DOWNLOAD_FUNC="./libs/download_wget.sh" # can change to ./lib/download_aria2.sh to boost download speed.
export GIT_CLONE_WITH_DEPTH="--depth=1"
export USE_JANUS=0
export USE_CSI=0
#export HID_MODE="" # Allow otg, ch9329, arduino, bluetooth mode
export USE_MSD=0
export USE_UDEV=0
#export PLATFORM_PATCH # Apply patch for board platform


================================================
FILE: install-mirror.sh
================================================
# Github 增强加速脚本
# 加速地址参考github 增强加速下载脚本
# Created by xe5700
# @namespace    https://greasyfork.org/scripts/412245
# @supportURL   https://github.com/XIU2/UserScript
# @homepageURL  https://github.com/XIU2/UserScript
url_clone="";

url_raw="";
url_raw2="";

clone_mirror(){
	printf "Choose github clone mirror\n
	0. github.com [Orginal]
	1. hub.fastgit.org [China Hong Kong] \n
	2. gitclone.com [China Zhe Jiang] \n
	3. github.com.cnpmjs.org [Singapore]\n
	4. kgithub.com \n
	5. hub.njuu.cf \n
	6. hub.yzuu.cf \n
	"
	tryagain=1
	while [ $tryagain -eq 1 ]; do
		read -p "Please type [0-3]: " capture
		case $capture in 
		0) url_clone="https:\\/\\/github.com\\/"; tryagain=0;;
		1) url_clone="https:\\/\\/hub.fastgit.org\\/"; tryagain=0;;
		2) url_clone="https:\\/\\/gitclone.com\\/github.com\\/"; tryagain=0;;
		3) url_clone="https:\\/\\/github.com.cnpmjs.org\\/"; tryagain=0;;
		4) url_clone="https:\\/\\/kgithub.com\\/"; tryagin=0;;
		5) url_clone="https:\\/\\/hub.njuu.cf\\/"; tryagin=0;;
		6) url_clone="https:\\/\\/hub.yzuu.cf\\/"; tryagin=0;;
		*) printf "\nTry again.\n"; tryagain=1;;
		esac
		echo
		echo "Github clone URL -> $url_clone"
		echo
	done
}

raw_mirror(){
	printf "Choose github raw mirror\n
	0. https://raw.githubusercontent.com [Orginal]
	1. https://raw.fastgit.org [China Hong Kong]
	2. https://cdn.staticaly.com [Global]
	3. https://ghproxy.com [South Korea]
	"

	#1. https://cdn.jsdelivr.net [Global]
	tryagain=1
	while [ $tryagain -eq 1 ]; do
		read -p "Please type [0-3]: " capture
		case $capture in 
		0) url_raw="https:\\/\\/raw.githubusercontent.com\\/";url_raw2="https://raw.githubusercontent.com/"; tryagain=0;;
		1) url_raw="https:\\/\\/raw.fastgit.org\\/";url_raw2="https://raw.fastgit.org/"; tryagain=0;;
		2) url_raw="https:\\/\\/cdn.staticaly.com\\/gh\\/";url_raw2="https://cdn.staticaly.com/gh/"; tryagain=0;;
		3) url_raw="https:\\/\\/ghproxy.com\\/https:\\/\\/raw.githubusercontent.com\\/";url_raw2="https://ghproxy.com/https://raw.githubusercontent.com/"; tryagain=0;;
		*) printf "\nTry again.\n"; tryagain=1;;
		esac
		echo
		echo "Github raw URL -> $url_raw"
		echo
	done
}

clone_mirror
raw_mirror

appPath=$(dirname $0)

cd $appPath

cat install.sh | sed "s/https:\\/\\/raw.githubusercontent.com\\//$url_raw/" | sed "s/https:\\/\\/github.com\\//$url_clone/" | tee .tmp.kvmd-install.sh > /dev/null
chmod +x .tmp.kvmd-install.sh
./.tmp.kvmd-install.sh
rm -f .tmp.kvmd-install.sh

================================================
FILE: install.sh
================================================
#!/bin/bash
# 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 Raspbian (32-bit) meant for RPi4
#
# *** 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 20210818 1830 PDT
# VER=1.0
source config.sh
source $DOWNLOAD_FUNC
set +x
APP_PATH=$(readlink -f $(dirname $0))
export KVMD_BV=`echo $KVMD_VERSION | awk '{print substr($1,1,1)}'`
export KVMD_SV=`echo $KVMD_VERSION | awk '{print substr($1,3)}'`

if [[ "$1" == "-h" || "$1" == "--help" ]]; then
  echo "usage:  $0 [-f]   where -f will force re-install new pikvm platform"
  exit 1
fi

WHOAMI=$( whoami ) 
if [ "$WHOAMI" != "root" ]; then
  echo "$WHOAMI, please run script as root."
  exit 1
fi

press-enter() {
  echo 
  read -p "Press ENTER to continue or CTRL+C to break out of script."
} # end press-enter

gen-ssl-certs() {
  cd /etc/kvmd/nginx/ssl
  openssl ecparam -out server.key -name prime256v1 -genkey
  openssl req -new -x509 -sha256 -nodes -key server.key -out server.crt -days 3650 \
        -subj "/C=US/ST=Denial/L=Denial/O=Pi-KVM/OU=Pi-KVM/CN=$(hostname)"
  cp server* /etc/kvmd/vnc/ssl/
  cd ${APP_PATH}
} # end gen-ssl-certs

create-override() {
  if [ $( grep ^kvmd: /etc/kvmd/override.yaml | wc -l ) -eq 0 ]; then

    if [[ $( echo $platform | grep usb | wc -l ) -eq 1 ]]; then
      cat <<USBOVERRIDE >> /etc/kvmd/override.yaml
kvmd:
    hid:
        mouse_alt:
            device: /dev/kvmd-hid-mouse-alt  # allow absolute/relative mouse mode
    msd:
        type: disabled
    atx:
        type: disabled
    streamer:
        forever: true
        cmd_append:
            - "--slowdown"      # for usb dongle (so target doesn't have to reboot)
        resolution:
            default: 1280x720
USBOVERRIDE

    else

      cat <<CSIOVERRIDE >> /etc/kvmd/override.yaml
kvmd:
    hid:
        mouse_alt:
            device: /dev/kvmd-hid-mouse-alt
    msd:
        type: disabled
    streamer:
        forever: true
CSIOVERRIDE

    fi

  fi
} # end create-override

install-python-packages() {
pkgs=""
  for i in $( echo "aiofiles appdirs asn1crypto async-timeout bottle cffi chardet click 
colorama cryptography dateutil dbus dev hidapi idna libgpiod marshmallow more-itertools multidict netifaces 
packaging passlib pillow ply psutil pycparser pyelftools pyghmi pygments pyparsing requests semantic-version 
setproctitle setuptools six spidev systemd tabulate urllib3 wrapt xlib yaml yarl" )
  do
    pkgs="$pkgs python3-$i"
  done
  echo "-> Install python packages"
  $APT_EXE install $pkgs -y > /dev/null
  # U
  pip3 install dbus_next==0.2.3 zstandard==0.18.0 pyserial==3.5 aiohttp==3.8.3
} # 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

install-tc358743() {
  ### CSI Support for Raspbian ###
  wget -O- -q https://www.linux-projects.org/listing/uv4l_repo/lpkey.asc | apt-key add -
  echo "deb https://www.linux-projects.org/listing/uv4l_repo/raspbian/stretch stretch main" | tee /etc/apt/sources.list.d/uv4l.list

  apt-get update > /dev/null
  echo "$APT_EXE install uv4l-tc358743-extras -y" 
  $APT_EXE install uv4l-tc358743-extras -y > /dev/null
} # install package for tc358743

boot-files() { 
  if [[ $( grep srepac /boot/config.txt | wc -l ) -eq 0 ]]; then

    if [[ $( echo $platform | grep usb | wc -l ) -eq 1 ]]; then

      # Armbian does not support config.txt, remove it.

      # amlogic does not support CSI, skip the following
      # add the tc358743 module to be loaded at boot for CSI
      # if [[ $( grep -w tc358743 /etc/modules | wc -l ) -eq 0 ]]; then
      #   echo "tc358743" >> /etc/modules
      # fi

      # install-tc358743 
      :
    fi 
  fi  # end of check if entries are already in /boot/config.txt

  # 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/boot/config.txt\n\n"
#  cat /boot/config.txt
  printf "\n/etc/modules\n\n"
  cat /etc/modules
} # end of necessary boot files
get-packages() { 
  printf "\n\n-> Getting Pi-KVM packages from ${PIKVMREPO}\n\n"
  mkdir -p "${KVMDCACHE}"
  #echo "wget ${PIKVMREPO} -O ${PKGINFO}"
  rm -f "${PKGINFO}"
  download "${PIKVMREPO}${PIKVMREPO_PKG}" "${PKGINFO}"
  echo "import Pi-Kvm Repo Key"
  gpg --keyserver keyserver.ubuntu.com --recv-keys $PIKVM_KEY
  gpg -a --export $PIKVM_KEY | apt-key add -
  # Download each of the pertinent packages for Rpi4, webterm, and the main service
  PIKVM_PKGS_CMD="egrep 'janus|kvmd' \"${PKGINFO}\" | grep -v sig | cut -d'>' -f1 | cut -d'\"' -f2 | egrep -v 'fan|oled' | egrep 'janus|pi4|webterm|kvmd-[0-9]'"
  if [ $CUSTOM_KVMD_VERSION -eq 1 ]; then
    PIKVM_PKGS_CMD="$PIKVM_PKGS_CMD | egrep -v 'kvmd-[0-9]'"
  fi
  PIKVM_PKGS=`$PIKVM_PKGS_CMD`
  for pkg in $PIKVM_PKGS
  do
    rm -f "${KVMDCACHE}/$pkg.sig"
    download "${PIKVMREPO}/$pkg.sig" "${KVMDCACHE}/$pkg.sig"
    download "${PIKVMREPO}/$pkg ${KVMDCACHE}/$pkg gpg ${KVMDCACHE}/$pkg.sig"
  done

  echo
  echo "ls -l ${KVMDCACHE}"
  ls -l "${KVMDCACHE}"
  echo
} # end get-packages function

get-platform() {
  # tryagain=1
  # while [ $tryagain -eq 1 ]; do
  #   # amglogic tv box only has usb port, use usb dongle.
	# # printf "Choose which capture device you will use:\n\n  1 - USB dongle\n  2 - v2 CSI\n  3 - V3 HAT\n" 
  #   # read -p "Please type [1-3]: " capture
	# capture=1;

  # done
    case $USE_CSI in 
      0) platform="kvmd-platform-v2-hdmiusb-rpi4"; tryagain=0;;
      # 2) platform="kvmd-platform-v2-hdmi-rpi4"; tryagain=0;;
      1) platform="kvmd-platform-v3-hdmi-rpi4"; tryagain=0;;
      *) printf "\nTry again.\n"; tryagain=1;;
    esac
    echo
    echo "Platform selected -> $platform"
    echo
} # end get-platform


install-kvmd-pkgs() {
  cd /

  INSTLOG="${KVMDCACHE}/installed_ver.txt"; rm -f "$INSTLOG"
  date > $INSTLOG 

# # uncompress platform package first
#   for i in $( ls "${KVMDCACHE}/${platform}-*.tar.xz" )
#   do
#     echo "-> Extracting package $i into /" >> "$INSTLOG" 
#     tar -vxf "$i"
#   done

# then uncompress, kvmd-{version}, kvmd-webterm, and janus packages 

  for i in $PIKVM_PKGS
  do
    echo "-> Extracting package $i into /" >> "$INSTLOG"
    tar -vxf $i
  done
  if [ $CUSTOM_KVMD_VERSION -eq 1 ]; then
  # Use custom kvmd version replace kvmd offical package
    download "${KVMD_COMMON_PKG_URL}" "${KVMDCACHE}/kvmd-common.tar.gz"
    echo "-> Extracting common kvmd package into /" >> "$INSTLOG"
    tar -vxf "${KVMDCACHE}/kvmd-common.tar.gz"
    echo "-> Install custom version kvmd" >> "$INSTLOG"
    $APT_EXE install python3-setuptools -y
    download "${MIRROR_GITHUB}/pikvm/kvmd/archive/refs/tags/v$KVMD_VERSION.tar.gz" "${KVMDCACHE}/kvmd.tar.gz"
    mkdir -p "${KVMDCACHE}/kvmd-tmp"
    tar axf "${KVMDCACHE}/kvmd.tar.gz" -C "${KVMDCACHE}/kvmd-tmp"
    cd "${KVMDCACHE}/kvmd-tmp/kvmd-$KVMD_VERSION/"
    ./setup.py install
    cd "$APP_PATH"
    rm -rf "${KVMDCACHE}/kvmd-tmp"
  fi
  cd "${APP_PATH}"
  cp bin/* /usr/bin/
} # 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
  echo
  cat /etc/udev/rules.d/99-kvmd.rules
} # end fix-udevrules

enable-kvmd-svcs() { 
  # enable KVMD services but don't start them
  echo "-> Enabling kvmd-nginx kvmd-webterm kvmd-otg and kvmd services, but do not start them."
  systemctl enable kvmd-nginx kvmd-webterm kvmd-otg kvmd 

  # in case going from CSI to USB, then disable kvmd-tc358743 service (in case it's enabled)
  if [[ $USE_CSI -eq 0 ]]; then
    systemctl disable --now kvmd-tc358743 
  else
    systemctl enable kvmd-tc358743 
  fi
} # end enable-kvmd-svcs 

build-ustreamer() {
  printf "\n\n-> Building ustreamer\n\n"
  # Install packages needed for building ustreamer source
  echo "$APT_EXE install -y libevent-dev libjpeg-dev libbsd-dev libgpiod-dev libsystemd-dev janus-dev janus"
  $APT_EXE install -y libevent-dev libjpeg-dev libbsd-dev libsystemd-dev
  if [[ $USE_GPIO -eq 1 ]]; then
    $APT_EXE install -y libgpiod-dev
  fi
  if [[ $USE_JANUS -eq 1 ]]; then
    $APT_EXE install -y janus-dev janus
  fi
  # Download ustreamer source and build it
  cd /tmp
  $GIT_EXE clone $GIT_CLONE_WITH_DEPTH "$MIRROR_GITHUB/pikvm/ustreamer"
  cd ustreamer/
  # if [[ $( uname -m ) == "aarch64" ]]; then
  #   make WITH_OMX=0 WITH_GPIO=1 WITH_SETPROCTITLE=1	# ustreamer doesn't support 64-bit hardware OMX 
  # else
  #   make WITH_OMX=1 WITH_GPIO=1 WITH_SETPROCTITLE=1	# hardware OMX support with 32-bit ONLY
  # fi
  make WITH_GPIO=$USE_GPIO WITH_SYSTEMD=1 WITH_JANUS=$USE_JANUS -j
  make install
  # kvmd service is looking for /usr/bin/ustreamer   
  ln -s /usr/local/bin/ustreamer /usr/bin/
} # end build-ustreamer 

install-dependencies() {
  echo
  echo "-> Installing dependencies for pikvm"

  apt-get update > /dev/null
  # for i in $( echo "" )
  # do
  #   echo "$APT_EXE install -y $i"
  #   $APT_EXE install -y $i > /dev/null
  # done
  echo "-> Install basic packages"
  $APT_EXE install -y nginx python3 bc expect v4l-utils gpiod dialog git python3-pip tesseract-ocr tesseract-ocr-chi-sim jq
  install-python-packages

  echo "-> Make tesseract data link"
  ln -s /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata

  echo "-> Install TTYD"
  $APT_EXE install -y ttyd
  if [ ! -e /usr/bin/ttyd ]; then
    echo "-> Download from apt failed, try download offical lastest version binary file."
    # Build and install ttyd
    # cd /tmp
    # $APT_EXE install -y build-essential cmake git libjson-c-dev libwebsockets-dev
    # git clone --depth=1 https://github.com/tsl0922/ttyd.git
    # cd ttyd && mkdir build && cd build
    # cmake ..
    # make -j && make install
    # Install binary from GitHub
    arch=$(dpkg --print-architecture)
    latest=$(wget -q -O- $MIRROR_GITHUB_API/repos/tsl0922/ttyd/releases/latest | jq -r ".tag_name")
    if [ $arch = arm64 ]; then
      arch='aarch64'
    fi
    if [ $arch = amd64 ]; then
      arch='x86_64'
    fi
    wget "$MIRROR_GITHUB/tsl0922/ttyd/releases/download/$latest/ttyd.$arch" -O /usr/bin/ttyd
    chmod +x /usr/bin/ttyd
  fi

  echo "-> Install ustreamer"
  if [ ! -e /usr/bin/ustreamer ]; then
    # apt install ustreamer
    cd /tmp/
	  $APT_EXE install -y libevent-2.1-7 libevent-core-2.1-7 libevent-pthreads-2.1-7 build-essential
    # ### required dependent packages for ustreamer ###
    build-ustreamer
    cd ${APP_PATH}
  fi
} # 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

  export PYTHONDIR_SYS=$( /tmp/syspath.py | grep packages | sed -e 's/, /\n/g' -e 's/\[//g' -e 's/\]//g' -e "s+'++g" | tail -1 )
  export PYTHONDIR_PIP=$( python3 -c "import site; print(site.getsitepackages()[0])" )
} # 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" 
  systemctl disable --now nginx

  # setup symlinks
  echo
  echo "-> Creating symlinks for use with kvmd python scripts"
  if [ ! -e /usr/bin/nginx ]; then ln -s /usr/sbin/nginx /usr/bin/; fi
  if [ ! -e /usr/sbin/python ]; then ln -s /usr/bin/python3 /usr/sbin/python; fi
  if [ ! -e /usr/bin/iptables ]; then ln -s /usr/sbin/iptables /usr/bin/iptables; fi
  # if [ ! -e /opt/vc/bin/vcgencmd ]; then mkdir -p /opt/vc/bin/; ln -s /usr/bin/vcgencmd /opt/vc/bin/vcgencmd; fi

  python-pkg-dir

  if [ ! -e $PYTHONDIR_PIP/kvmd ]; then
    # Debian python版本比 pikvm官方的低一些
    ln -s /usr/lib/python3.10/site-packages/kvmd* ${PYTHONDIR_PIP}
  fi
} # end fix-nginx-symlinks

fix-python-symlinks(){
    python-pkg-dir

  if [ ! -e $PYTHONDIR_PIP/kvmd ]; then
    # Debian python版本比 pikvm官方的低一些
    ln -s /usr/lib/python3.10/site-packages/kvmd* ${PYTHONDIR_PIP}
  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 skiped this patch.'
      ;;
    y|Y|Yes|yes)
      ./patches/custom/old-kernel-msd/apply.sh
      ;;
    *)
      echo "Try again.";;
  esac
}

fix-kvmd-for-tvbox-armbian(){
  # 打补丁来移除一些对armbian和电视盒子不太支持的特性
  python-pkg-dir
  if [[ "$CUSTOM_KVMD_VERSION" -eq 1 ]]; then
    cd "$PYTHONDIR_PIP/kvmd-$KVMD_VERSION-py${PYTHON_VERSION}.egg"
  else
    cd $PYTHONDIR_PIP
  fi

  # if [[ "$DEBIAN_PYTHON" -eq 1 ]]; then
    # if [ `$KVMD_VERSION < 3.134` -eq ]; then
    #   PATCH_VER="v3.90"
    # fi
    # if [ `$KVMD_VERSION \>= 3.134` -eq 1 ]; then
    #   PATCH_VER="v3.134"
    # fi
    # if [ ! -z "$PATCH_VER" ]; then
    #   $GIT_EXE apply ${APP_PATH}/patches/debian_python/$PATCH_VER/*.patch
    # fi
  # fi
  if [[ "$USE_GPIO" -eq 0 ]] && [[ "$KVMD_BV" -eq "3" ]] ; then
    PATCH_VER=""
    if [ `expr $KVMD_SV \<= 81` -eq 1 ]; then
      PATCH_VER="v3.47-v3.81"
    fi
    if [ `expr $KVMD_SV \>= 82` -eq 1 ] && [ `expr $KVMD_SV \<= 83` -eq 1 ]; then
      PATCH_VER="v3.82-v3.83"
    fi
    if [ `expr $KVMD_SV \>= 84` -eq 1 ] && [ `expr $KVMD_SV \<= 134` -eq 1 ]; then
      PATCH_VER="v3.84-v3.134"
    fi
    if [ ! -z "$PATCH_VER" ]; then
      sh -c "$GIT_EXE apply '${APP_PATH}/patches/disable_gpio/$PATCH_VER/*.patch'"
    fi
  fi
  if [ `expr $KVMD_SV \>= 84` -eq 1 ] && [ `expr $KVMD_SV \<= 92` -eq 1 ]; then
      PATCH_VER="v3.84-v3.92"
      sh -c "$GIT_EXE apply '${APP_PATH}/patches/genernal/$PATCH_VER/*.patch'"
  fi
  cd ${APP_PATH}
  read -p "Do you want to apply custom patches?  [y/n] " answer
  case $answer in
    n|N|no|No)
     return;
     ;;
    y|Y|Yes|yes)
     apply-custom-patch;
     return;
     ;;
    *)
     echo "Try again.";;
  esac
}

fix-webterm() {
  echo
  echo "-> Creating kvmd-webterm homedir"
  mkdir -p /home/kvmd-webterm
  chown kvmd-webterm /home/kvmd-webterm
  ls -ld /home/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*   ### this is required in case gpio (wiringpi) is installed
ls -l /dev/gpio*

ls -l /dev/kvmd-video
rm /dev/kvmd-video
# Need to use video0 for orange pi (if you don't, the video capture won't work)
ln -s video1 /dev/kvmd-video
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
} # end set-ownership

check-kvmd-works() {
  # check to make sure kvmd -m works before continuing
  invalid=1
  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 restart kvmd-nginx kvmd-otg kvmd-webterm kvmd 
  # systemctl status kvmd-nginx kvmd-otg kvmd-webterm kvmd 
} # end start-kvmd-svcs

fix-motd() { 
  rm /etc/motd
  cp armbian/armbian-motd /usr/bin/
  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
  # Install vcgencmd for armbian platform
  cp -rf armbian/opt/* /opt/vc/bin
  #cp -rf armbian/udev /etc/

  cd ${APP_PATH}
  # 
}	#end armbian-packages
$APT_EXE update
$APT_EXE -y install python3 xz-utils tar wget aria2 curl
### 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
# added option to re-install by adding -f parameter (for use as platform switcher)
export 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 for Raspbian by @srepac\n"
  get-packages
  get-platform
  boot-files
  install-kvmd-pkgs
  create-override
  gen-ssl-certs
  fix-udevrules
  install-dependencies
  otg-devices
  armbian-packages
  systemctl disable --now janus
  fix-kvmd-for-tvbox-armbian
  
  # 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' $PYTHONDIR/kvmd/keyboard/printer.py
  fi

  sync
  echo "-> Synced data, you can reboot system safety."
  printf "\n\nReboot is required to create kvmd users and groups.\nPlease re-run this script after reboot to complete the install.\n"
  # Ask user to press CTRL+C before reboot or ENTER to proceed with reboot
  press-enter
  reboot
else
  printf "\nRunning part 2 of PiKVM installer script for Raspbian by @srepac\n"
  fix-nginx-symlinks
  fix-python-symlinks
  fix-webterm
  fix-motd
  set-ownership 
  create-kvmdfix
  check-kvmd-works
  enable-kvmd-svcs
  start-kvmd-svcs

  sync
  printf "\nCheck kvmd devices\n\n" 
  ls -l /dev/kvmd*
  printf "\nYou should see devices for keyboard, mouse, and video.\n"

  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"
fi


================================================
FILE: libs/checksum.sh
================================================
checksum(){
    export sumRet=0
    case $1 in
        gpg) checksum_gpg $2 $3;;
    esac
}
checksum_gpg(){
    gpg --verify $2 $1 2> /dev/null
    case $? in
        1) export sumRet=0;echo bad signature, skip checksum.;;
        *) export sumRet=$?;;
    esac
    return;
}

================================================
FILE: libs/download_aria2.sh
================================================
#!/bin/bash
source libs/checksum.sh
download(){
    tryCount=0
    echo Downloading $1 To $2
    download2 $1 $2 $3 $4
    unset tryCount
}
download2() {
    filename=$(readlink -f $2)
    echo "aria2c -x 16 -s 16 --file-allocation=falloc --min-split-size 2M $1 -o $filename -d /"
    if [ ! -f "$2" ]; then
        aria2c -x 16 -s 16 --file-allocation=falloc --min-split-size 2M $1 -o $filename -d /
    else
        if [ ! -n "$3" ]; then
            echo "File $2 is exists, skip download."
            return
        fi
    fi
    echo "Checksum for $2"
    checksum $3 $2 $4
    if [ "$sumRet" != 0 ]; then
        echo "File checksum failed, try redownload file. Result is $sumRet"
        if [[ "$tryCount" -lt 3 ]]; then
            tryCount=`expr $tryCount + 1`
            rm $2
            download2 $1 $2 $3 $4
        else
            echo "Try $tryCount times, download failed."
        fi
    else
        echo "File checksum successful."
    fi
    unset sumRet
}

================================================
FILE: libs/download_wget.sh
================================================
#!/bin/bash
source libs/checksum.sh
download(){
    tryCount=0
    echo Downloading $1 To $2
    download2 $1 $2 $3 $4
    unset tryCount
}
download2() {
    if [ ! -f "$2" ]; then
        echo "wget $1 -O $2"
        wget $1 -O $2
    else
        if [ ! -n "$3" ]; then
            echo "File $2 is exists, skip download."
            return
        fi
    fi
    echo "Checksum for $2"
    checksum $3 $2 $4
    if [ "$sumRet" != 0 ]; then
        echo "File checksum failed, try redownload file. Result is $sumRet"
        if [[ "$tryCount" -lt 3 ]]; then
            tryCount=`expr $tryCount + 1`
            rm $2
            download2 $1 $2 $3 $4
        else
            echo "Try $tryCount times, download failed."
        fi
    else
        echo "File checksum successful."
    fi
    unset sumRet
}

================================================
FILE: patches/custom/old-kernel-msd/apply.sh
================================================
#!/bin/bash
# PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )
APP_PATH=$(readlink -f $(dirname $0))
echo "-> Apply patches"
if [[ "$CUSTOM_KVMD_VERSION" -eq 1 ]]; then
    cd $PYTHONDIR_PIP/kvmd-$KVMD_VERSION-py${PYTHON_VERSION}.egg/
else
    cd $PYTHONDIR_PIP
fi
PATCH_VER=""
if [ `expr $KVMD_SV \>= 84` -eq 1 ] && [ `expr $KVMD_SV \<= 92` -eq 1 ]; then
      PATCH_VER="v3.84-v3.134"
fi
if [ `expr $KVMD_SV \>= 124` -eq 1 ] && [ `expr $KVMD_SV \<= 142` -eq 1 ]; then
      PATCH_VER="v3.124-v3.142"
fi
git apply ${APP_PATH}/${PATCH_VER}/*.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."

================================================
FILE: patches/custom/old-kernel-msd/v3.124-v3.142/0001-Apply-old-kernel-msd-patch-for-v3.134.patch
================================================
From 18723b4c8e13a5cd0049982cfbbf61b4409ba159 Mon Sep 17 00:00:00 2001
From: xe5700 <9338143+xe5700@users.noreply.github.com>
Date: Mon, 15 Aug 2022 19:31:45 +0800
Subject: [PATCH] Apply old kernel msd patch for v3.134

---
 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 | 20 +++++++----
 kvmd/plugins/msd/otg/drive.py    |  5 +--
 7 files changed, 145 insertions(+), 21 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 ae943d23..e0e27fd3 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))
     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 --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py
index 9b6f5e69..af6327b2 100644
--- a/kvmd/apps/otg/__init__.py
+++ b/kvmd/apps/otg/__init__.py
@@ -186,7 +186,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)
-            _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)
@@ -295,7 +294,7 @@ def _cmd_stop(config: Section) -> None:
     logger.info("Disabling gadget %r ...", config.otg.gadget)
     _write(join(gadget_path, "UDC"), "\n")
 
-    _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True)
+    _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), True)
 
     profile_path = join(gadget_path, usb.G_PROFILE)
     for func in os.listdir(profile_path):
diff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py
index 0d32331b..26db4c8e 100644
--- a/kvmd/apps/otgmsd/__init__.py
+++ b/kvmd/apps/otgmsd/__init__.py
@@ -21,12 +21,15 @@
 
 
 import os
+import signal
 import errno
 import argparse
 
 from typing import List
 from typing import Optional
 
+import psutil
+
 from ...validators.basic import valid_bool
 from ...validators.basic import valid_int_f0
 from ...validators.os import valid_abs_file
@@ -56,6 +59,21 @@ def _set_param(gadget: str, instance: int, param: str, value: str) -> None:
         raise
 
 
+def _unlock() -> None:
+    # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924
+    found = False
+    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: Optional[List[str]]=None) -> None:
     (parent_parser, argv, config) = init(
@@ -71,6 +89,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")
     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,
@@ -90,8 +110,11 @@ def main(argv: Optional[List[str]]=None) -> None:
     set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value))
     get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))
 
+    if options.unlock:
+        _unlock()
+
     if options.eject:
-        set_param("forced_eject", "")
+        set_param("file", "")
 
     if options.set_cdrom is not None:
         set_param("cdrom", str(int(options.set_cdrom)))
diff --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 @@
+# ========================================================================== #
+#                                                                            #
+#    KVMD - The main PiKVM daemon.                                           #
+#                                                                            #
+#    Copyright (C) 2018-2022  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 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 --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 @@
+# ========================================================================== #
+#                                                                            #
+#    KVMD - The main PiKVM daemon.                                           #
+#                                                                            #
+#    Copyright (C) 2018-2022  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/>.  #
+#                                                                            #
+# ========================================================================== #
+
+
+from . import main
+main()
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
index 5a8b86a6..d4de24b8 100644
--- a/kvmd/plugins/msd/otg/__init__.py
+++ b/kvmd/plugins/msd/otg/__init__.py
@@ -144,6 +144,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
         storage_path: str,
 
         remount_cmd: List[str],
+        unlock_cmd: List[str],
 
         initial: Dict,
 
@@ -159,6 +160,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"]
@@ -184,10 +186,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=""),
@@ -250,6 +250,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_cdrom_flag(False)
                 self.__drive.set_rw_flag(False)
@@ -314,6 +315,7 @@ 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_rw_flag(self.__state.vd.rw)
                 self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
                 if self.__state.vd.rw:
@@ -323,6 +325,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
             else:
                 if not (self.__state.vd.connected or self.__drive.get_image_path()):
                     raise MsdDisconnectedError()
+
+                await self.__unlock_drive()
                 self.__drive.set_image_path("")
                 await self.__remount_rw(False, fatal=False)
 
@@ -529,6 +533,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_rw_flag(False)
                     self.__drive.set_cdrom_flag(self.__initial_cdrom)
                     self.__drive.set_image_path(path)
@@ -597,5 +602,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
 
     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/kvmd/plugins/msd/otg/drive.py b/kvmd/plugins/msd/otg/drive.py
index 11af7f81..ee54e5e9 100644
--- a/kvmd/plugins/msd/otg/drive.py
+++ b/kvmd/plugins/msd/otg/drive.py
@@ -53,10 +53,7 @@ class Drive:
     # =====
 
     def set_image_path(self, path: str) -> None:
-        if path:
-            self.__set_param("file", path)
-        else:
-            self.__set_param("forced_eject", "")
+        self.__set_param("file", path)
 
     def get_image_path(self) -> str:
         return self.__get_param("file")
-- 
2.34.1.windows.1



================================================
FILE: patches/custom/old-kernel-msd/v3.84-v3.92/0001-Revert-force-eject-feature-to-unlock-helper.patch
================================================
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(), 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:
+        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)
-            _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 @@ def _cmd_stop(config: Section) -> None:
     logger.info("Disabling gadget %r ...", config.otg.gadget)
     _write(join(gadget_path, "UDC"), "\n")
 
-    _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True)
+    _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), True)
 
     profile_path = join(gadget_path, usb.G_PROFILE)
     for func in os.listdir(profile_path):
diff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py
index f57b3107..78f8e3c7 100644
--- a/kvmd/apps/otgmsd/__init__.py
+++ b/kvmd/apps/otgmsd/__init__.py
@@ -21,12 +21,15 @@
 
 
 import os
+import signal
 import errno
 import argparse
 
 from typing import List
 from typing import Optional
 
+import psutil
+
 from ...validators.basic import valid_bool
 from ...validators.basic import valid_int_f0
 from ...validators.os import valid_abs_file
@@ -56,6 +59,21 @@ def _set_param(gadget: str, instance: int, param: str, value: str) -> None:
         raise
 
 
+def _unlock() -> None:
+    # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924
+    found = False
+    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: Optional[List[str]]=None) -> None:
     (parent_parser, argv, config) = init(
@@ -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")
     parser.add_argument("--set-cdrom", default=None, type=valid_bool,
                         metavar="<1|0|yes|no>", help="Set CD-ROM flag")
     parser.add_argument("--set-rw", default=None, type=valid_bool,
@@ -89,8 +109,11 @@ def main(argv: Optional[List[str]]=None) -> None:
     set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value))
     get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))
 
+    if options.unlock:
+        _unlock()
+
     if options.eject:
-        set_param("forced_eject", "")
+        set_param("file", "")
 
     if options.set_cdrom is not None:
         set_param("cdrom", str(int(options.set_cdrom)))
diff --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 @@
+# ========================================================================== #
+#                                                                            #
+#    KVMD - The main PiKVM daemon.                                           #
+#                                                                            #
+#    Copyright (C) 2018-2022  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 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 --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 @@
+# ========================================================================== #
+#                                                                            #
+#    KVMD - The main PiKVM daemon.                                           #
+#                                                                            #
+#    Copyright (C) 2018-2022  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/>.  #
+#                                                                            #
+# ========================================================================== #
+
+
+from . import main
+main()
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
index 409b899a..1342c6b4 100644
--- a/kvmd/plugins/msd/otg/__init__.py
+++ b/kvmd/plugins/msd/otg/__init__.py
@@ -140,6 +140,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
         storage_path: str,
 
         remount_cmd: List[str],
+        unlock_cmd: List[str],
 
         initial: Dict,
 
@@ -154,6 +155,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
         self.__meta_path = os.path.join(self.__storage_path, "meta")
 
         self.__remount_cmd = remount_cmd
+        self.__unlock_cmd = unlock_cmd
 
         self.__initial_image: str = initial["image"]
         self.__initial_cdrom: bool = initial["cdrom"]
@@ -178,10 +180,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
 
             "storage": Option("/var/lib/kvmd/msd", type=valid_abs_dir, unpack_as="storage_path"),
 
-            "remount_cmd": Option([
-                "/usr/bin/sudo", "--non-interactive",
-                "/usr/bin/kvmd-helper-otgmsd-remount", "{mode}",
-            ], type=valid_command),
+            "remount_cmd": Option([*sudo, "/usr/bin/kvmd-helper-otgmsd-remount", "{mode}"], type=valid_command),
+            "unlock_cmd":  Option([*sudo, "/usr/bin/kvmd-helper-otgmsd-unlock", "unlock"],  type=valid_command),
 
             "initial": {
                 "image": Option("",    type=valid_printable_filename, if_empty=""),
@@ -241,6 +241,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
     async def reset(self) -> None:
         async with self.__state.busy(check_online=False):
             try:
+                await self.__unlock_drive()
                 self.__drive.set_image_path("")
                 self.__drive.set_rw_flag(False)
                 self.__drive.set_cdrom_flag(False)
@@ -290,12 +291,15 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
                 if not os.path.exists(self.__state.vd.image.path):
                     raise MsdUnknownImageError()
 
+                await self.__unlock_drive()
                 self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
                 self.__drive.set_image_path(self.__state.vd.image.path)
 
             else:
                 if not (self.__state.vd.connected or self.__drive.get_image_path()):
                     raise MsdDisconnectedError()
+
+                await self.__unlock_drive()
                 self.__drive.set_image_path("")
 
             self.__state.vd.connected = connected
@@ -474,6 +478,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
             if os.path.exists(path):
                 logger.info("Setting up initial image %r ...", self.__initial_image)
                 try:
+                    await self.__unlock_drive()
                     self.__drive.set_cdrom_flag(self.__initial_cdrom)
                     self.__drive.set_image_path(path)
                 except Exception:
@@ -541,4 +546,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes
 
     async def __remount_storage(self, rw: bool) -> None:
         if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
-            raise MsdError("Can't execute remount helper")
+            pass
+            #raise MsdError("Can't execute remount helper")
+
+    async def __unlock_drive(self) -> None:
+        await helpers.unlock_drive(self.__unlock_cmd)
\ No newline at end of file
diff --git a/kvmd/plugins/msd/otg/drive.py b/kvmd/plugins/msd/otg/drive.py
index 11af7f81..ee54e5e9 100644
--- a/kvmd/plugins/msd/otg/drive.py
+++ b/kvmd/plugins/msd/otg/drive.py
@@ -53,10 +53,7 @@ class Drive:
     # =====
 
     def set_image_path(self, path: str) -> None:
-        if path:
-            self.__set_param("file", path)
-        else:
-            self.__set_param("forced_eject", "")
+        self.__set_param("file", path)
 
     def get_image_path(self) -> str:
         return self.__get_param("file")
-- 
2.34.1.windows.1



================================================
FILE: patches/custom/old-kernel-msd/v3.84-v3.92/0003-Allow-skip-some-features-unsupports-on-old-linux-ker.patch
================================================
From 840b9af2bc6f65851bd45eeb0cb4d629aed3423a Mon Sep 17 00:00:00 2001
From: Frank Zhang <xe5700@outlook.com>
Date: Thu, 19 May 2022 22:48:49 +0800
Subject: [PATCH] Allow skip some features unsupports on old linux kernel

---
 kvmd/apps/otg/__init__.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py
index d0ed0554..4aaeb3f8 100644
--- a/kvmd/apps/otg/__init__.py
+++ b/kvmd/apps/otg/__init__.py
@@ -78,8 +78,11 @@ def _unlink(path: str, optional: bool=False) -> None:
     os.unlink(path)
 
 
-def _write(path: str, value: Union[str, int]) -> None:
+def _write(path: str, value: Union[str, int], optional: bool=False) -> None:
     get_logger().info("WRITE --- %s", path)
+    if optional and not os.access(path, os.F_OK):
+        get_logger().info("SKIP ---- %s", path)
+        return
     with open(path, "w") as param_file:
         param_file.write(str(value))
 
@@ -158,9 +161,9 @@ class _GadgetConfig:
         func = f"hid.usb{self.__hid_instance}"
         func_path = join(self.__gadget_path, "functions", func)
         _mkdir(func_path)
-        _write(join(func_path, "no_out_endpoint"), "1")
+        _write(join(func_path, "no_out_endpoint"), "1", optional=True)
         if remote_wakeup:
-            _write(join(func_path, "wakeup_on_write"), "1")
+            _write(join(func_path, "wakeup_on_write"), "1", optional=True)
         _write(join(func_path, "protocol"), hid.protocol)
         _write(join(func_path, "subclass"), hid.subclass)
         _write(join(func_path, "report_length"), hid.report_length)
-- 
2.34.1.windows.1



================================================
FILE: patches/disable_gpio/v3.47-v3.81/0001-Disable-GPIO-For-TV-Box.patch
================================================
From 368eaa19ef1cc187c8012c00b95ad97f260d61c3 Mon Sep 17 00:00:00 2001
From: xe5700 <9338143+xe5700@users.noreply.github.com>
Date: Sun, 23 Oct 2022 11:10:24 +0800
Subject: [PATCH] Disable-GPIO-For-TV-Box

---
 kvmd/apps/kvmd/ugpio.py | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py
index a8fc9224..8bf17fff 100644
--- a/kvmd/apps/kvmd/ugpio.py
+++ b/kvmd/apps/kvmd/ugpio.py
@@ -281,16 +281,10 @@ class UserGpio:
             await self.__notifier.wait()
 
     def sysprep(self) -> None:
-        get_logger().info("Preparing User-GPIO drivers ...")
-        for (_, driver) in tools.sorted_kvs(self.__drivers):
-            driver.prepare()
+        pass
 
     async def systask(self) -> None:
-        get_logger(0).info("Running User-GPIO drivers ...")
-        await asyncio.gather(*[
-            driver.run()
-            for (_, driver) in tools.sorted_kvs(self.__drivers)
-        ])
+        await asyncio.Event().wait()
 
     async def cleanup(self) -> None:
         for driver in self.__drivers.values():
-- 
2.34.1.windows.1



================================================
FILE: patches/disable_gpio/v3.82-v3.83/0001-Disable-GPIO-For-TV-Box.patch
================================================
From 960b30ec20b370269ee43282f1867861e714e33f Mon Sep 17 00:00:00 2001
From: xe5700 <9338143+xe5700@users.noreply.github.com>
Date: Thu, 19 May 2022 23:01:20 +0800
Subject: [PATCH] Disable-GPIO-For-TV-Box

---
 kvmd/apps/kvmd/ugpio.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py
index cea60bb9..9328496a 100644
--- a/kvmd/apps/kvmd/ugpio.py
+++ b/kvmd/apps/kvmd/ugpio.py
@@ -282,15 +282,15 @@ class UserGpio:
 
     def sysprep(self) -> None:
         get_logger(0).info("Preparing User-GPIO drivers ...")
-        for (_, driver) in tools.sorted_kvs(self.__drivers):
-            driver.prepare()
+#        for (_, driver) in tools.sorted_kvs(self.__drivers):
+#            driver.prepare()
 
     async def systask(self) -> None:
         get_logger(0).info("Running User-GPIO drivers ...")
-        await asyncio.gather(*[
-            driver.run()
-            for (_, driver) in tools.sorted_kvs(self.__drivers)
-        ])
+#        await asyncio.gather(*[
+#            driver.run()
+#            for (_, driver) in tools.sorted_kvs(self.__drivers)
+#        ])
 
     async def cleanup(self) -> None:
         for driver in self.__drivers.values():
-- 
2.34.1.windows.1



================================================
FILE: patches/disable_gpio/v3.84-v3.134/0001-Disable-GPIO-For-TV-Box.patch
================================================
From 960b30ec20b370269ee43282f1867861e714e33f Mon Sep 17 00:00:00 2001
From: xe5700 <9338143+xe5700@users.noreply.github.com>
Date: Thu, 19 May 2022 23:01:20 +0800
Subject: [PATCH] Disable-GPIO-For-TV-Box

---
 kvmd/apps/kvmd/ugpio.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py
index cea60bb9..9328496a 100644
--- a/kvmd/apps/kvmd/ugpio.py
+++ b/kvmd/apps/kvmd/ugpio.py
@@ -282,15 +282,16 @@ class UserGpio:
 
     def sysprep(self) -> None:
         get_logger(0).info("Preparing User-GPIO drivers ...")
-        for (_, driver) in tools.sorted_kvs(self.__drivers):
-            driver.prepare()
+#        for (_, driver) in tools.sorted_kvs(self.__drivers):
+#            driver.prepare()
 
     async def systask(self) -> None:
         get_logger(0).info("Running User-GPIO drivers ...")
-        await asyncio.gather(*[
-            driver.run()
-            for (_, driver) in tools.sorted_kvs(self.__drivers)
-        ])
+#        await asyncio.gather(*[
+#            driver.run()
+#            for (_, driver) in tools.sorted_kvs(self.__drivers)
+#        ])
+        await asyncio.Event().wait()
 
     async def cleanup(self) -> None:
         for driver in self.__drivers.values():
-- 
2.34.1.windows.1



================================================
FILE: patches/genernal/v3.84-v3.92/0001-Allow-skip-some-features-unsupports-on-old-linux-ker.patch
================================================
From 840b9af2bc6f65851bd45eeb0cb4d629aed3423a Mon Sep 17 00:00:00 2001
From: Frank Zhang <xe5700@outlook.com>
Date: Thu, 19 May 2022 22:48:49 +0800
Subject: [PATCH] Allow skip some features unsupports on old linux kernel

---
 kvmd/apps/otg/__init__.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py
index d0ed0554..4aaeb3f8 100644
--- a/kvmd/apps/otg/__init__.py
+++ b/kvmd/apps/otg/__init__.py
@@ -78,8 +78,11 @@ def _unlink(path: str, optional: bool=False) -> None:
     os.unlink(path)
 
 
-def _write(path: str, value: Union[str, int]) -> None:
+def _write(path: str, value: Union[str, int], optional: bool=False) -> None:
     get_logger().info("WRITE --- %s", path)
+    if optional and not os.access(path, os.F_OK):
+        get_logger().info("SKIP ---- %s", path)
+        return
     with open(path, "w") as param_file:
         param_file.write(str(value))
 
@@ -158,9 +161,9 @@ class _GadgetConfig:
         func = f"hid.usb{self.__hid_instance}"
         func_path = join(self.__gadget_path, "functions", func)
         _mkdir(func_path)
-        _write(join(func_path, "no_out_endpoint"), "1")
+        _write(join(func_path, "no_out_endpoint"), "1", optional=True)
         if remote_wakeup:
-            _write(join(func_path, "wakeup_on_write"), "1")
+            _write(join(func_path, "wakeup_on_write"), "1", optional=True)
         _write(join(func_path, "protocol"), hid.protocol)
         _write(join(func_path, "subclass"), hid.subclass)
         _write(join(func_path, "report_length"), hid.report_length)
-- 
2.34.1.windows.1

Download .txt
gitextract_k6lnbzxp/

├── .gitignore
├── LICENSE
├── README-zh-CN.MD
├── README.MD
├── amglogic-dtb-mod.py
├── armbian/
│   ├── armbian-motd
│   ├── opt/
│   │   ├── armbian-sysinfo
│   │   └── vcgencmd
│   └── udev/
│       └── rules.d/
│           └── 99-kvmd.rules
├── bin/
│   └── kvmd-helper-otgmsd-unlock
├── config.sh
├── dtb/
│   └── 4.4/
│       └── rk322x-box.dtb
├── install-mirror.sh
├── install.sh
├── libs/
│   ├── checksum.sh
│   ├── download_aria2.sh
│   └── download_wget.sh
└── patches/
    ├── custom/
    │   └── old-kernel-msd/
    │       ├── apply.sh
    │       ├── v3.124-v3.142/
    │       │   └── 0001-Apply-old-kernel-msd-patch-for-v3.134.patch
    │       └── v3.84-v3.92/
    │           ├── 0001-Revert-force-eject-feature-to-unlock-helper.patch
    │           └── 0003-Allow-skip-some-features-unsupports-on-old-linux-ker.patch
    ├── disable_gpio/
    │   ├── v3.47-v3.81/
    │   │   └── 0001-Disable-GPIO-For-TV-Box.patch
    │   ├── v3.82-v3.83/
    │   │   └── 0001-Disable-GPIO-For-TV-Box.patch
    │   └── v3.84-v3.134/
    │       └── 0001-Disable-GPIO-For-TV-Box.patch
    └── genernal/
        └── v3.84-v3.92/
            └── 0001-Allow-skip-some-features-unsupports-on-old-linux-ker.patch
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (104K chars).
[
  {
    "path": ".gitignore",
    "chars": 38,
    "preview": "workspace.code-workspace\r\ntmp\r\n.vscode"
  },
  {
    "path": "LICENSE",
    "chars": 18091,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "README-zh-CN.MD",
    "chars": 4027,
    "preview": "# 感谢由toss-a编写的中文教程\r\n该教程未验证,可能需要使用 https://github.com/toss-a/pikvm-armbian 该分叉才能运行。\r\n\r\n你可以尝试其他的项目基于pikvm\r\n分支版本 kvmd-armbi"
  },
  {
    "path": "README.MD",
    "chars": 2530,
    "preview": "# KVMD-ARMBIAN\nThis project support non Raspberry Pi device to running pikvm on armbian\nYou can try other project based "
  },
  {
    "path": "amglogic-dtb-mod.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "armbian/armbian-motd",
    "chars": 128,
    "preview": "#!/bin/sh\r\n/etc/update-motd.d/10-armbian-header\r\n/etc/update-motd.d/30-armbian-sysinfo\r\n/etc/update-motd.d/41-armbian-co"
  },
  {
    "path": "armbian/opt/armbian-sysinfo",
    "chars": 7450,
    "preview": "#!/bin/bash\r\n#\r\n# Copyright (c) Authors: http://www.armbian.com/authors\r\n#\r\n# This file is licensed under the terms of t"
  },
  {
    "path": "armbian/opt/vcgencmd",
    "chars": 628,
    "preview": "#!/bin/bash\r\ncd `dirname $0`\r\nsource armbian-sysinfo\r\n\r\ncase $1 in\r\n        get_throttled) echo \"throttled=0x0\";;\r\n     "
  },
  {
    "path": "armbian/udev/rules.d/99-kvmd.rules",
    "chars": 510,
    "preview": "# https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name\r\n# https://wiki.archlinux.or"
  },
  {
    "path": "bin/kvmd-helper-otgmsd-unlock",
    "chars": 111,
    "preview": "#!/usr/sbin/python\n# KVMD-ARMBIAN\n\nfrom kvmd.helpers.unlock import main\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "config.sh",
    "chars": 1293,
    "preview": "export APT_EXE=\"apt-get\" #If you installed apt-fast can change it to apt-fast to boost install speed.\nexport GIT_EXE=\"gi"
  },
  {
    "path": "install-mirror.sh",
    "chars": 2497,
    "preview": "# Github 增强加速脚本\r\n# 加速地址参考github 增强加速下载脚本\r\n# Created by xe5700\r\n# @namespace    https://greasyfork.org/scripts/412245\r\n# "
  },
  {
    "path": "install.sh",
    "chars": 19611,
    "preview": "#!/bin/bash\n# modified by xe5700 \t\t2021-11-04\txe5700@outlook.com\n# modified by NewbieOrange\t2021-11-04\n# created by @sre"
  },
  {
    "path": "libs/checksum.sh",
    "chars": 275,
    "preview": "checksum(){\n    export sumRet=0\n    case $1 in\n        gpg) checksum_gpg $2 $3;;\n    esac\n}\nchecksum_gpg(){\n    gpg --ve"
  },
  {
    "path": "libs/download_aria2.sh",
    "chars": 979,
    "preview": "#!/bin/bash\nsource libs/checksum.sh\ndownload(){\n    tryCount=0\n    echo Downloading $1 To $2\n    download2 $1 $2 $3 $4\n "
  },
  {
    "path": "libs/download_wget.sh",
    "chars": 810,
    "preview": "#!/bin/bash\nsource libs/checksum.sh\ndownload(){\n    tryCount=0\n    echo Downloading $1 To $2\n    download2 $1 $2 $3 $4\n "
  },
  {
    "path": "patches/custom/old-kernel-msd/apply.sh",
    "chars": 814,
    "preview": "#!/bin/bash\n# PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )\nAPP_PATH=$(readlink -f $(dirname $0))\n"
  },
  {
    "path": "patches/custom/old-kernel-msd/v3.124-v3.142/0001-Apply-old-kernel-msd-patch-for-v3.134.patch",
    "chars": 14579,
    "preview": "From 18723b4c8e13a5cd0049982cfbbf61b4409ba159 Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github"
  },
  {
    "path": "patches/custom/old-kernel-msd/v3.84-v3.92/0001-Revert-force-eject-feature-to-unlock-helper.patch",
    "chars": 14414,
    "preview": "From 3d137882ac38ac046b7d09cada1883b304b04319 Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github"
  },
  {
    "path": "patches/custom/old-kernel-msd/v3.84-v3.92/0003-Allow-skip-some-features-unsupports-on-old-linux-ker.patch",
    "chars": 1637,
    "preview": "From 840b9af2bc6f65851bd45eeb0cb4d629aed3423a Mon Sep 17 00:00:00 2001\nFrom: Frank Zhang <xe5700@outlook.com>\nDate: Thu,"
  },
  {
    "path": "patches/disable_gpio/v3.47-v3.81/0001-Disable-GPIO-For-TV-Box.patch",
    "chars": 1123,
    "preview": "From 368eaa19ef1cc187c8012c00b95ad97f260d61c3 Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github"
  },
  {
    "path": "patches/disable_gpio/v3.82-v3.83/0001-Disable-GPIO-For-TV-Box.patch",
    "chars": 1266,
    "preview": "From 960b30ec20b370269ee43282f1867861e714e33f Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github"
  },
  {
    "path": "patches/disable_gpio/v3.84-v3.134/0001-Disable-GPIO-For-TV-Box.patch",
    "chars": 1304,
    "preview": "From 960b30ec20b370269ee43282f1867861e714e33f Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github"
  },
  {
    "path": "patches/genernal/v3.84-v3.92/0001-Allow-skip-some-features-unsupports-on-old-linux-ker.patch",
    "chars": 1637,
    "preview": "From 840b9af2bc6f65851bd45eeb0cb4d629aed3423a Mon Sep 17 00:00:00 2001\nFrom: Frank Zhang <xe5700@outlook.com>\nDate: Thu,"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the xe5700/kvmd-armbian GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (93.5 KB), approximately 28.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!