[
  {
    "path": ".gitignore",
    "content": "workspace.code-workspace\r\ntmp\r\n.vscode"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License."
  },
  {
    "path": "README-zh-CN.MD",
    "content": "# 感谢由toss-a编写的中文教程\r\n该教程未验证，可能需要使用 https://github.com/toss-a/pikvm-armbian 该分叉才能运行。\r\n\r\n你可以尝试其他的项目基于pikvm\r\n分支版本 kvmd-armbian https://github.com/srepac/kvmd-armbian\r\nonekvm 项目 https://github.com/mofeng-git/One-KVM\r\n\r\n# 硬件准备\r\n\r\n## 开发版选择 \r\n\r\n建议选择能刷比较新的armbian的开发版作为PI-KVM\r\n\r\n##### 1.选择原生带有OTG的开发版 例如:\r\n\r\n- Orange Pi Prime(貌似已停产)\r\n\r\n- Orange Pi Zero(需要制作一分二数据线 性能比较差 适合低成本1080P 30FPS方案)\r\n\r\n- Orange Pi Zero LTS(和Orange Pi Zero一样)\r\n  - 等其他 未列举完\r\n\r\n##### 2.选择非原生支持修改dtb来实现OTG功能的开发版 例如:\r\n\r\n- King 3399 (1个USB3.0 和一个Type-c 3.0做为OTG 能够满足1080P 60FPS方案)\r\n\r\n- phicomm n1 (理论可以 把刷机那USB作为OTG 未做测试 不建议购买 性价比并不高)\r\n  - 等其他 未列举完 \r\n\r\n## 视频采集设备选择 HDMI转USB\r\n\r\n- 如果你的开发版只有USB 2.0 建议选择ms2109 而且价格便宜 tb 30 RMB左右的即可\r\n\r\n- 如果你的开发版有USB 3.0 建议选择ms2130 淘宝偏贵点 可输出1080P 60FPS\r\n  - 以上设备我都已测试可以用 但是 部分主板可能使用这两款视频采集设备都会导致进BIOS颜色输出有问题(又不是不能用)\r\n\r\n- 其他的视频采集设备也可以 你可以试试???\r\n\r\n## OTG数据线准备\r\n\r\n- 开发版OTG口为单独的Type-C口或Micro USB 那么 你只需要准备一根数据线即可\r\n- 开发版OTG口为开发版供电 你需要手动制作一分二数据线 如下图\r\n\r\n![1to2](https://raw.githubusercontent.com/toss-a/pikvm-armbian/master/1to2.png)\r\n\r\n- 开发版OTG口为USB 修改dtb后实现的  你需要准备USB-A 转 USB-A 线缆\r\n\r\n  建议切断 USB 线的电源线，它可能会导致 OTG 断开连接。\r\n\r\n#### 第一步\r\n\r\n- 刷入 armbian 用于您的开发版或电视盒（如果内核不支持 otg，您应该构建一个启用 otg 功能的内核）\r\n- 如果你的开发版或者电视机或者支持从SD卡启动或者U盘启动 那么你需要把armbian写入到可移动存储介质上,写入完成后将一部分空闲分区划分为10G左右的空间格式化为EXT4 作为PI KVM 镜像储存分区(可选 非必须)\r\n\r\n#### 第二步\r\n\r\n- 修改您的 dtb 文件以启用 otg 功能。对于 otg USB 端口，将 dr_mode 从host更改为peripheral。\r\n- 修改方法(Linux 推荐 Ubuntu)\r\n  - sudo apt install device-tree-compiler\r\n  - dtc 你开发版dtb -I dtb -O dts -o 输出的名称.dts\r\n  - 修改 将dr_mode = \"host\" 修改为dr_mode = \"peripheral\"\r\n  - dtc 刚刚修改的dts.dts -I dts -O dtb -o 你开发版.dtb\r\n  - 替换,然后重启你的开发版\r\n\r\n#### 第三步\r\n\r\n- ```\r\n  git clone https://github.com/toss-a/pikvm-armbian.git\r\n  cd pikvm-armbian\r\n  ./install.sh\r\n  ```\r\n\r\n- 我们内核比较新 不需要 所以按n\r\n- 重启\r\n\r\n#### 第四步\r\n\r\n- 重启后再次运行刚刚运行的./install.sh\r\n- 安装完成!!!\r\n\r\n#### 启用大容量存储设备\r\n\r\n需要格式化为EXT4\r\n\r\n- 1.开发版或电视盒从U盘或者SD卡启动 使用内置emmc做为PI KVM 镜像存储\r\n- 2.在armbian启动之前划分了PI KVM 镜像存储分区\r\n- 3.再插入一个SD卡或U盘格式化为EXT4做为PI KVM 镜像存储\r\n\r\n\r\n\r\n- 修改挂载点\r\n\r\n  - ```\r\n    vim /etc/fstab\r\n    ```\r\n\r\n  - 添加(/dev/sda1需要修改为你的存储介质dev路径)\r\n\r\n  - ```\r\n    /dev/sda1  /var/lib/kvmd/msd   ext4  nodev,nosuid,noexec,ro,errors=remount-ro,data=journal,X-kvmd.otgmsd-root=/var/lib/kvmd/msd,X-kvmd.otgmsd-user=kvmd  0  0\r\n    ```\r\n\r\n  - ```\r\n    vim /etc/kvmd/override.yaml\r\n    ```\r\n\r\n  - 删除以下两行\r\n\r\n  - ```\r\n    msd:\r\n    type:disable\r\n    ```\r\n\r\n  - 重启\r\n\r\n#### 修复重启后视频采集设备无法采集到视频\r\n\r\n- 1.以root用户登陆后克隆\r\n\r\n- ```\r\n  git clone https://github.com/jkulesza/usbreset\r\n  ```\r\n\r\n- 2.打开克隆的目录\r\n\r\n- ```\r\n  cd usbreset\r\n  ```\r\n\r\n- 3.将源码编译成可执行文件(如果报错请安装gcc)\r\n\r\n- ```\r\n  cc usbreset.c -o usbreset\r\n  ```\r\n\r\n- 4.赋予可执行权限\r\n\r\n- ```\r\n  chmod +x usbreset\r\n  ```\r\n\r\n- 4.获取需要重置的视频采集设备的总线和设备 ID\r\n\r\n- ```\r\n  lsusb\r\n  ```\r\n\r\n  - 我的输出内容为输出：\r\n\r\n  - ```\r\n    root@king3399:~# lsusb\r\n    Bus 002 Device 002: ID 345f:2130 UltraSemi USB3 Video\r\n    Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub\r\n    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub\r\n    Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub\r\n    Bus 003 Device 003: ID 0c45:768a Microdia USB DEVICE\r\n    Bus 003 Device 002: ID 05e3:0608 Genesys Logic, Inc. Hub\r\n    Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub\r\n    Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub\r\n    Bus 006 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub\r\n    root@king3399:~#\r\n    ```\r\n  \r\n- 5.尝试重启你的视频采集设备(我的设备为UltraSemi USB3 Video 对应为002/002)\r\n\r\n- ```\r\n  ./usbreset /dev/bus/usb/002/002\r\n  ```\r\n\r\n- 6.成功重启USB设备后添加到开机自启动\r\n\r\n- ```\r\n  vim /etc/rc.local\r\n  ```\r\n\r\n  - 添加内容\r\n\r\n  - ```\r\n    sleep 5\r\n    /root/usbreset /dev/bus/usb/002/002\r\n    ```\r\n  \r\n- 7.重启开发版再次查看\r\n\r\n#### 更改PI KVM 登陆密码\r\n\r\n- ```\r\n  kvmd-htpasswd set admin\r\n  ```\r\n\r\n- 输入你需要的密码即可\r\n\r\n#### 感谢,如果没有他们我不可能做到\r\n\r\n- [kvmd-armbian](https://github.com/xe5700/kvmd-armbian)\r\n\r\n- [peacokswiss](https://github.com/xe5700/kvmd-armbian/issues/12)\r\n\r\n- [armkvm](https://github.com/wxjiyc/amlogic-s9xxx-armbian/blob/main/rebuild#L629)\r\n  \r\n- [usbreset](https://github.com/jkulesza/usbreset)"
  },
  {
    "path": "README.MD",
    "content": "# KVMD-ARMBIAN\nThis project support non Raspberry Pi device to running pikvm on armbian\nYou can try other project based on pikvm \nfork version of kvmd-armbian https://github.com/srepac/kvmd-armbian\nonekvm project https://github.com/mofeng-git/One-KVM\n\n# Chinese installation steps\nChinese installation steps by toss-a [https://github.com/xe5700/pikvm-armbian/blob/master/README-zh-CN.MD]\n\n# Install\nKVMD Install for armbian\nIt support Allwinner, Amlogic and Rockchip based tv box, tested on phicomm n1, mxq pro 4k, tqc a01. \nChipset needs support USB OTG feature, lots of old amglogic chipset not support otg feature, such as s805 and s905.\nYou should install armbian with debian buster or bullseye.\nThen running this script to install pikvm.\nInstall scripts is fork from @srepac rasbian pikvm install script.\n\nOriginal Script [https://kvmnerds.com/RPiKVM/install-pikvm-raspbian.sh]\n\n# Hardware for kvmd-armbian project\n* A tv box/arm board supports otg feature:\n    - Tests on phicomm n1(Amlogic s905d), mxq pro 4k (rk322x), tqc a01(Allwinner H6). \n    - If you are use arm board you can remove gpio patch to enable gpio feature.\n* Video capture device:\n    - HDMI to USB dongle (30 RMB On taobao, 10$ on aliexpress.)\n      cheap hdmi to usb dongle all use physics USB2.0 port, but fake USB3.0(USB 5GBPS, USB3.2GEN1) version supports 720P 60FPS,\n      usb 2.0 version only supports 720P 30FPS.\n* USB-A to USB-A cable:\n    - Recommended cut off usb cable's power line, it might causes otg disconnect.\n\n## Step 1\n- Flash armbian debian [Recommended bullseye] for your tv box (If kernel not support otg you should build a kernel enable otg features)\n## Step 2\n- Modify your dtb file to enable otg feature. Change dr_mode from host to peripheral for otg usb port.\n- If you use rk322x (rk3228A rk3228B rk3229) series chipset, you can use dtb/4.4/rk332x-box.dtb \n## Step 3\n\n```\ngit clone https://github.com/xe5700/kvmd-armbian.git\ncd kvmd-armbian\n./install.sh\n```\n(If very slow, you can use install-mirror.sh to boost install speed.)\n\n## Step 4\n- running install.sh or install-mirror.sh after reboot os then running again.\n- Enjoy\n\n# Tested device\n - Phicomm N1\n - TQC A01 (Ethernet port not working, only support wireless.)\n - RK322x based tvbox (MXQ, V88)\n - S905L2 based tvbox\n - Orange pi zero (tested by @MrSuicideParrot)\n\n\n# Update log\n## Version 1.0\n    \n## Version 2.0\n    Now support download hook, config file, diffrent version of kvmd, and fix lots of bug. \n## Version 2.1\n    Fix #8 #7 #15, allow custom apt manager tools.\n"
  },
  {
    "path": "amglogic-dtb-mod.py",
    "content": ""
  },
  {
    "path": "armbian/armbian-motd",
    "content": "#!/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-config\r\n\r\n"
  },
  {
    "path": "armbian/opt/armbian-sysinfo",
    "content": "#!/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 the GNU General Public\r\n# License version 2. This program is licensed \"as is\" without any\r\n# warranty of any kind, whether express or implied.\r\n#\r\n\r\n# DO NOT EDIT THIS FILE but add config options to /etc/default/armbian-motd\r\n# generate system information\r\n\r\nexport PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\r\n\r\nTHIS_SCRIPT=\"sysinfo\"\r\nMOTD_DISABLE=\"\"\r\nSTORAGE=/dev/sda1\r\nSHOW_IP_PATTERN=\"^bond.*|^[ewr].*|^br.*|^lt.*|^umts.*|^lan.*\"\r\n\r\nCPU_TEMP_LIMIT=60\r\nHDD_TEMP_LIMIT=60\r\nAMB_TEMP_LIMIT=40\r\n\r\n[[ -f /etc/default/armbian-motd ]] && . /etc/default/armbian-motd\r\n\r\nfor f in $MOTD_DISABLE; do\r\n\t[[ $f == $THIS_SCRIPT ]] && exit 0\r\ndone\r\n\r\n# don't edit below here\r\n\r\nfunction display() {\r\n\t# $1=name $2=value $3=red_limit $4=minimal_show_limit $5=unit $6=after $7=acs/desc{\r\n\t# battery red color is opposite, lower number\r\n\tif [[ \"$1\" == \"Battery\" ]]; then local great=\"<\"; else local great=\">\"; fi\r\n\tif [[ -n \"$2\" && \"$2\" > \"0\" && (( \"${2%.*}\" -ge \"$4\" )) ]]; then\r\n\tprintf \"%-14s%s\" \"$1:\"\r\n\t\tif awk \"BEGIN{exit ! ($2 $great $3)}\"; then echo -ne \"\\e[0;91m $2\"; else echo -ne \"\\e[0;92m $2\"; fi\r\n\t\tprintf \"%-1s%s\\x1B[0m\" \"$5\"\r\n\t\tprintf \"%-11s%s\\t\" \"$6\"\r\n\t\treturn 1\r\n\tfi\r\n} # display\r\n\r\nfunction getboardtemp() {\r\n\tif [ -f /etc/armbianmonitor/datasources/soctemp ]; then\r\n\t\tread raw_temp </etc/armbianmonitor/datasources/soctemp 2>/dev/null\r\n\t\tif [ ! -z $(echo \"$raw_temp\" | grep -o \"^[1-9][0-9]*\\.\\?[0-9]*$\") ] && (( $(echo \"${raw_temp} < 200\" |bc -l) )); then\r\n\t\t\t# Allwinner legacy kernels output degree C\r\n\t\t\tboard_temp=${raw_temp}\r\n\t\telse\r\n\t\t\tboard_temp=$(awk '{printf(\"%d\",$1/1000)}' <<<${raw_temp})\r\n\t\tfi\r\n\telif [ -f /etc/armbianmonitor/datasources/pmictemp ]; then\r\n\t\t# fallback to PMIC temperature\r\n\t\tboard_temp=$(awk '{printf(\"%d\",$1/1000)}' </etc/armbianmonitor/datasources/pmictemp)\r\n\tfi\r\n} # getboardtemp\r\n\r\nfunction batteryinfo() {\r\n\t# Battery info for Allwinner\r\n\tmainline_dir=\"/sys/power/axp_pmu\"\r\n\tlegacy_dir=\"/sys/class/power_supply\"\r\n\tif [[ -e \"$mainline_dir\" ]]; then\r\n\t\tread status_battery_connected < $mainline_dir/battery/connected 2>/dev/null\r\n\t\tif [[ \"$status_battery_connected\" == \"1\" ]]; then\r\n\t\t\tread status_battery_charging < $mainline_dir/charger/charging\r\n\t\t\tread status_ac_connect < $mainline_dir/ac/connected\r\n\t\t\tread battery_percent< $mainline_dir/battery/capacity\r\n\t\t\t# dispay charging / percentage\r\n\t\t\tif [[ \"$status_ac_connect\" == \"1\" && \"$battery_percent\" -lt \"100\" ]]; then\r\n\t\t\t\tstatus_battery_text=\" charging\"\r\n\t\t\telif [[ \"$status_ac_connect\" == \"1\" && \"$battery_percent\" -eq \"100\" ]]; then\r\n\t\t\t\tstatus_battery_text=\" charged\"\r\n\t\t\telse\r\n\t\t\t\tstatus_battery_text=\" discharging\"\r\n\t\t\tfi\r\n\t\tfi\r\n\telif [[ -e \"$legacy_dir/axp813-ac\" ]]; then\r\n\t\tread status_battery_connected < $legacy_dir/axp20x-battery/present\r\n\t\tif [[ \"$status_battery_connected\" == \"1\" ]]; then\r\n\t\t\tstatus_battery_text=\" \"$(awk '{print tolower($0)}' < $legacy_dir/axp20x-battery/status)\r\n\t                read status_ac_connect < $legacy_dir/axp813-ac/present\r\n\t                read battery_percent< $legacy_dir/axp20x-battery/capacity\r\n\t\tfi\r\n\telif [[ -e \"$legacy_dir/battery\" ]]; then\r\n\t\tif [[ ((\"$(cat $legacy_dir/battery/voltage_now)\" -gt \"5\" )) ]]; then\r\n\t\t\tstatus_battery_text=\" \"$(awk '{print tolower($0)}' < $legacy_dir/battery/status)\r\n\t\t\tread battery_percent <$legacy_dir/battery/capacity\r\n\t\tfi\r\n\tfi\r\n} # batteryinfo\r\n\r\nfunction ambienttemp() {\r\n\t# define where w1 usually shows up\r\n        W1_DIR=\"/sys/devices/w1_bus_master1/\"\r\n\tif [ -f /etc/armbianmonitor/datasources/ambienttemp ]; then\r\n\t\tread raw_temp </etc/armbianmonitor/datasources/ambienttemp 2>/dev/null\r\n\t\tamb_temp=$(awk '{printf(\"%d\",$1/1000)}' <<<${raw_temp})\r\n\t\techo $amb_temp\r\n\telif [[ -d $W1_DIR && $ONE_WIRE == yes ]]; then\r\n\t\tdevice=$(ls -1 $W1_DIR | grep -Eo '^[0-9]{1,4}' | head -1)\r\n\t\tif [[ -n $device ]]; then\r\n\t\t\tif [[ -d ${W1_DIR}${device}/hwmon/hwmon0 ]]; then hwmon=0; else hwmon=1; fi\r\n\t\t\tread raw_temp < ${W1_DIR}${device}/hwmon/hwmon${hwmon}/temp1_input 2>/dev/null\r\n\t\t\tamb_temp=$(awk '{printf(\"%d\",$1/1000)}' <<<${raw_temp})\r\n\t\t\techo $amb_temp\r\n\t\tfi\r\n\telse\r\n\t\t# read ambient temperature from USB device if available\r\n\t\tif [[ ! -f /usr/bin/temper ]]; then\r\n\t\t\techo \"\"\r\n\t\t\treturn\r\n\t\tfi\r\n\t\tamb_temp=$(temper -c 2>/dev/null)\r\n\t\tcase ${amb_temp} in\r\n\t\t\t*\"find the USB device\"*)\r\n\t\t\t\techo \"\"\r\n\t\t\t\t;;\r\n\t\t\t*)\r\n\t\t\t\tamb_temp=$(awk '{print $NF}' <<<$amb_temp |  sed 's/C//g')\r\n\t\t\t\techo -n \"scale=1;${amb_temp}/1\" | grep -oE \"\\-?[[:digit:]]+\\.[[:digit:]]\"\r\n\t\tesac\r\n\tfi\r\n} # ambienttemp\r\n\r\nfunction get_ip_addresses() {\r\n\tlocal ips=()\r\n\tfor f in /sys/class/net/*; do\r\n\t\tlocal intf=$(basename $f)\r\n\t\t# match only interface names starting with e (Ethernet), br (bridge), w (wireless), r (some Ralink drivers use ra<number> format)\r\n\t\tif [[ $intf =~ $SHOW_IP_PATTERN ]]; then\r\n\t\t\tlocal tmp=$(ip -4 addr show dev $intf | awk '/inet/ {print $2}' | cut -d'/' -f1)\r\n\t\t\t# add both name and IP - can be informative but becomes ugly with long persistent/predictable device names\r\n\t\t\t#[[ -n $tmp ]] && ips+=(\"$intf: $tmp\")\r\n\t\t\t# add IP only\r\n\t\t\t[[ -n $tmp ]] && ips+=(\"$tmp\")\r\n\t\tfi\r\n\tdone\r\n\techo \"${ips[@]}\"\r\n} # get_ip_addresses\r\n\r\nfunction storage_info() {\r\n\t# storage info\r\n\tRootInfo=$(df -h /)\r\n\troot_usage=$(awk '/\\// {print $(NF-1)}' <<<${RootInfo} | sed 's/%//g')\r\n\troot_total=$(awk '/\\// {print $(NF-4)}' <<<${RootInfo})\r\n\tStorageInfo=$(df -h $STORAGE 2>/dev/null | grep $STORAGE)\r\n\tif [[ -n \"${StorageInfo}\" && ${RootInfo} != *$STORAGE* ]]; then\r\n\t\tstorage_usage=$(awk '/\\// {print $(NF-1)}' <<<${StorageInfo} | sed 's/%//g')\r\n\t\tstorage_total=$(awk '/\\// {print $(NF-4)}' <<<${StorageInfo})\r\n\t\tif [[ -n \"$(command -v smartctl)\" ]]; then\r\n\t\t\tDISK=\"${STORAGE::-1}\"\r\n\t\t\tstorage_temp+=$(sudo smartctl -A $DISK 2> /dev/null | grep -i temperature | awk '{print $(NF-2)}')\r\n\t\tfi\r\n\tfi\r\n} # storage_info\r\n\r\n\r\n\r\n# query various systems and send some stuff to the background for overall faster execution.\r\n# Works only with ambienttemp and batteryinfo since A20 is slow enough :)\r\namb_temp=$(ambienttemp &)\r\nip_address=$(get_ip_addresses &)\r\nbatteryinfo\r\nstorage_info\r\ngetboardtemp\r\ncritical_load=80\r\n\r\n# get uptime, logged in users and load in one take\r\nUPTIME=$(LC_ALL=C uptime)\r\nUPT1=${UPTIME#*'up '}\r\nUPT2=${UPT1%'user'*}\r\nusers=${UPT2//*','}\r\nusers=${users//' '}\r\ntime=${UPT2%','*}\r\ntime=${time//','}\r\ntime=$(echo $time | xargs)\r\nload=${UPTIME#*'load average: '}\r\nload=${load//','}\r\nload=$(echo $load | cut -d\" \" -f1)\r\n[[ $load == 0.0* ]] && load=0.10\r\ncpucount=$(grep -c processor /proc/cpuinfo)\r\n\r\nload=$(awk '{printf(\"%.0f\",($1/$2) * 100)}' <<< \"$load $cpucount\")\r\n\r\n# memory and swap\r\nmem_info=$(LC_ALL=C free -w 2>/dev/null | grep \"^Mem\" || LC_ALL=C free | grep \"^Mem\")\r\nmemory_usage=$(awk '{printf(\"%.0f\",(($2-($4+$6+$7))/$2) * 100)}' <<<${mem_info})\r\nmem_info=$(echo $mem_info | awk '{print $2}')\r\nmemory_total=$(( mem_info / 1024 ))\r\nswap_info=$(LC_ALL=C free -m | grep \"^Swap\")\r\nswap_usage=$( (awk '/Swap/ { printf(\"%3.0f\", $3/$2*100) }' <<<${swap_info} 2>/dev/null || echo 0) | tr -c -d '[:digit:]')\r\nswap_total=$(awk '{print $(2)}' <<<${swap_info})\r\nif [[ ${memory_total} -gt 1000 ]]; then\r\n\t memory_total=$(awk '{printf(\"%.2f\",$1/1024)}' <<<${memory_total})\"G\"\r\nelse\r\n\tmemory_total+=\"M\"\r\nfi\r\n\r\nif [[ ${swap_total} -gt 500 ]]; then\r\n\tswap_total=$(awk '{printf(\"%.2f\",$1/1024)}' <<<${swap_total})\"G\"\r\nelse\r\n\tswap_total+=\"M\"\r\nfi\r\n\r\n"
  },
  {
    "path": "armbian/opt/vcgencmd",
    "content": "#!/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        measure_temp) echo \"temp=$board_temp'C\";;\r\n        get_config)\r\n                case $2 in\r\n                        total_mem)\r\n                                KB=$( grep 'Memory:' /var/log/dmesg* | awk '{print $5}' | cut -d'/' -f2 | sed 's/K//g' | head -1 )\r\n                                MB=$( echo $KB / 1024 | bc )\r\n                                echo \"total_mem=$MB\";;\r\n                        *)\r\n                                echo \"invalid option\";;\r\n                esac\r\n        ;;\r\nesac\r\n\r\n"
  },
  {
    "path": "armbian/udev/rules.d/99-kvmd.rules",
    "content": "# https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name\r\n# https://wiki.archlinux.org/index.php/Udev#Setting_static_device_names\r\nKERNEL==\"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\"\r\nKERNEL==\"hidg0\", GROUP=\"kvmd\", SYMLINK+=\"kvmd-hid-keyboard\"\r\nKERNEL==\"hidg1\", GROUP=\"kvmd\", SYMLINK+=\"kvmd-hid-mouse\"\r\nKERNEL==\"hidg2\", GROUP=\"kvmd\", SYMLINK+=\"kvmd-hid-mouse-alt\"\r\n"
  },
  {
    "path": "bin/kvmd-helper-otgmsd-unlock",
    "content": "#!/usr/sbin/python\n# KVMD-ARMBIAN\n\nfrom kvmd.helpers.unlock import main\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "config.sh",
    "content": "export APT_EXE=\"apt-get\" #If you installed apt-fast can change it to apt-fast to boost install speed.\nexport GIT_EXE=\"git\"\nexport MIRROR_GITHUB=\"https://github.com\" # Use a github mirror to boost download speed in some place has no github cdn\nexport MIRROR_GITHUB_API=\"https://api.github.com\"\n\nexport PIKVMREPO=\"https://files.pikvm.org/repos/arch/rpi4\"\nexport PIKVMREPO_PKG=\"/\"\n#export PIKVMREPO=\"\"\n#export KVMD_VERSION=\"3.47\" # LEGECY KVMD VERSION SUPPORTS MSD AND RUNNING ON DEBIAN BULLSEYE OR BUSTER WITHOUT PATCH\n\n# export KVMD_VERSION=\"\"\nexport KVMD_COMMON_PKG_URL=\"$MIRROR_GITHUB/xe5700/kvmd-armbian-repo/raw/master/kvmd-common.tar.xz\"\nexport CUSTOM_KVMD_VERSION=1 # If you want install lastest version of kvmd set to 0\nexport KVMD_VERSION=\"3.142\" # LAST KVMD VERSION SUPPORTS PYTHON3.9 \nexport PIKVM_KEY=\"912C773ABBD1B584\"\nexport USE_GPIO=0\nexport DEBIAN_PYTHON=1\nexport KVMDCACHE=\"/var/cache/kvmd\"\nexport PKGINFO=\"${KVMDCACHE}/packages.txt\"\nexport DOWNLOAD_FUNC=\"./libs/download_wget.sh\" # can change to ./lib/download_aria2.sh to boost download speed.\nexport GIT_CLONE_WITH_DEPTH=\"--depth=1\"\nexport USE_JANUS=0\nexport USE_CSI=0\n#export HID_MODE=\"\" # Allow otg, ch9329, arduino, bluetooth mode\nexport USE_MSD=0\nexport USE_UDEV=0\n#export PLATFORM_PATCH # Apply patch for board platform\n"
  },
  {
    "path": "install-mirror.sh",
    "content": "# Github 增强加速脚本\r\n# 加速地址参考github 增强加速下载脚本\r\n# Created by xe5700\r\n# @namespace    https://greasyfork.org/scripts/412245\r\n# @supportURL   https://github.com/XIU2/UserScript\r\n# @homepageURL  https://github.com/XIU2/UserScript\r\nurl_clone=\"\";\r\n\r\nurl_raw=\"\";\r\nurl_raw2=\"\";\r\n\r\nclone_mirror(){\r\n\tprintf \"Choose github clone mirror\\n\r\n\t0. github.com [Orginal]\r\n\t1. hub.fastgit.org [China Hong Kong] \\n\r\n\t2. gitclone.com [China Zhe Jiang] \\n\r\n\t3. github.com.cnpmjs.org [Singapore]\\n\r\n\t4. kgithub.com \\n\r\n\t5. hub.njuu.cf \\n\r\n\t6. hub.yzuu.cf \\n\r\n\t\"\r\n\ttryagain=1\r\n\twhile [ $tryagain -eq 1 ]; do\r\n\t\tread -p \"Please type [0-3]: \" capture\r\n\t\tcase $capture in \r\n\t\t0) url_clone=\"https:\\\\/\\\\/github.com\\\\/\"; tryagain=0;;\r\n\t\t1) url_clone=\"https:\\\\/\\\\/hub.fastgit.org\\\\/\"; tryagain=0;;\r\n\t\t2) url_clone=\"https:\\\\/\\\\/gitclone.com\\\\/github.com\\\\/\"; tryagain=0;;\r\n\t\t3) url_clone=\"https:\\\\/\\\\/github.com.cnpmjs.org\\\\/\"; tryagain=0;;\r\n\t\t4) url_clone=\"https:\\\\/\\\\/kgithub.com\\\\/\"; tryagin=0;;\r\n\t\t5) url_clone=\"https:\\\\/\\\\/hub.njuu.cf\\\\/\"; tryagin=0;;\r\n\t\t6) url_clone=\"https:\\\\/\\\\/hub.yzuu.cf\\\\/\"; tryagin=0;;\r\n\t\t*) printf \"\\nTry again.\\n\"; tryagain=1;;\r\n\t\tesac\r\n\t\techo\r\n\t\techo \"Github clone URL -> $url_clone\"\r\n\t\techo\r\n\tdone\r\n}\r\n\r\nraw_mirror(){\r\n\tprintf \"Choose github raw mirror\\n\r\n\t0. https://raw.githubusercontent.com [Orginal]\r\n\t1. https://raw.fastgit.org [China Hong Kong]\r\n\t2. https://cdn.staticaly.com [Global]\r\n\t3. https://ghproxy.com [South Korea]\r\n\t\"\r\n\r\n\t#1. https://cdn.jsdelivr.net [Global]\r\n\ttryagain=1\r\n\twhile [ $tryagain -eq 1 ]; do\r\n\t\tread -p \"Please type [0-3]: \" capture\r\n\t\tcase $capture in \r\n\t\t0) url_raw=\"https:\\\\/\\\\/raw.githubusercontent.com\\\\/\";url_raw2=\"https://raw.githubusercontent.com/\"; tryagain=0;;\r\n\t\t1) url_raw=\"https:\\\\/\\\\/raw.fastgit.org\\\\/\";url_raw2=\"https://raw.fastgit.org/\"; tryagain=0;;\r\n\t\t2) url_raw=\"https:\\\\/\\\\/cdn.staticaly.com\\\\/gh\\\\/\";url_raw2=\"https://cdn.staticaly.com/gh/\"; tryagain=0;;\r\n\t\t3) url_raw=\"https:\\\\/\\\\/ghproxy.com\\\\/https:\\\\/\\\\/raw.githubusercontent.com\\\\/\";url_raw2=\"https://ghproxy.com/https://raw.githubusercontent.com/\"; tryagain=0;;\r\n\t\t*) printf \"\\nTry again.\\n\"; tryagain=1;;\r\n\t\tesac\r\n\t\techo\r\n\t\techo \"Github raw URL -> $url_raw\"\r\n\t\techo\r\n\tdone\r\n}\r\n\r\nclone_mirror\r\nraw_mirror\r\n\r\nappPath=$(dirname $0)\r\n\r\ncd $appPath\r\n\r\ncat install.sh | sed \"s/https:\\\\/\\\\/raw.githubusercontent.com\\\\//$url_raw/\" | sed \"s/https:\\\\/\\\\/github.com\\\\//$url_clone/\" | tee .tmp.kvmd-install.sh > /dev/null\r\nchmod +x .tmp.kvmd-install.sh\r\n./.tmp.kvmd-install.sh\r\nrm -f .tmp.kvmd-install.sh"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/bash\n# modified by xe5700 \t\t2021-11-04\txe5700@outlook.com\n# modified by NewbieOrange\t2021-11-04\n# created by @srepac   08/09/2021   srepac@kvmnerds.com\n# Scripted Installer of Pi-KVM on Raspbian (32-bit) meant for RPi4\n#\n# *** MSD is disabled by default ***\n#\n# Mass Storage Device requires the use of a USB thumbdrive or SSD and will need to be added in /etc/fstab\n: '\n# SAMPLE /etc/fstab entry for USB drive with only one partition formatted as ext4 for the entire drive:\n\n/dev/sda1  /var/lib/kvmd/msd   ext4  nodev,nosuid,noexec,ro,errors=remount-ro,data=journal,X-kvmd.otgmsd-root=/var/lib/kvmd/msd,X-kvmd.otgmsd-user=kvmd  0  0\n\n'\n# NOTE:  This was tested on a new install of raspbian desktop and lite versions, but should also work on an existing install.\n#\n# Last change 20210818 1830 PDT\n# VER=1.0\nsource config.sh\nsource $DOWNLOAD_FUNC\nset +x\nAPP_PATH=$(readlink -f $(dirname $0))\nexport KVMD_BV=`echo $KVMD_VERSION | awk '{print substr($1,1,1)}'`\nexport KVMD_SV=`echo $KVMD_VERSION | awk '{print substr($1,3)}'`\n\nif [[ \"$1\" == \"-h\" || \"$1\" == \"--help\" ]]; then\n  echo \"usage:  $0 [-f]   where -f will force re-install new pikvm platform\"\n  exit 1\nfi\n\nWHOAMI=$( whoami ) \nif [ \"$WHOAMI\" != \"root\" ]; then\n  echo \"$WHOAMI, please run script as root.\"\n  exit 1\nfi\n\npress-enter() {\n  echo \n  read -p \"Press ENTER to continue or CTRL+C to break out of script.\"\n} # end press-enter\n\ngen-ssl-certs() {\n  cd /etc/kvmd/nginx/ssl\n  openssl ecparam -out server.key -name prime256v1 -genkey\n  openssl req -new -x509 -sha256 -nodes -key server.key -out server.crt -days 3650 \\\n        -subj \"/C=US/ST=Denial/L=Denial/O=Pi-KVM/OU=Pi-KVM/CN=$(hostname)\"\n  cp server* /etc/kvmd/vnc/ssl/\n  cd ${APP_PATH}\n} # end gen-ssl-certs\n\ncreate-override() {\n  if [ $( grep ^kvmd: /etc/kvmd/override.yaml | wc -l ) -eq 0 ]; then\n\n    if [[ $( echo $platform | grep usb | wc -l ) -eq 1 ]]; then\n      cat <<USBOVERRIDE >> /etc/kvmd/override.yaml\nkvmd:\n    hid:\n        mouse_alt:\n            device: /dev/kvmd-hid-mouse-alt  # allow absolute/relative mouse mode\n    msd:\n        type: disabled\n    atx:\n        type: disabled\n    streamer:\n        forever: true\n        cmd_append:\n            - \"--slowdown\"      # for usb dongle (so target doesn't have to reboot)\n        resolution:\n            default: 1280x720\nUSBOVERRIDE\n\n    else\n\n      cat <<CSIOVERRIDE >> /etc/kvmd/override.yaml\nkvmd:\n    hid:\n        mouse_alt:\n            device: /dev/kvmd-hid-mouse-alt\n    msd:\n        type: disabled\n    streamer:\n        forever: true\nCSIOVERRIDE\n\n    fi\n\n  fi\n} # end create-override\n\ninstall-python-packages() {\npkgs=\"\"\n  for i in $( echo \"aiofiles appdirs asn1crypto async-timeout bottle cffi chardet click \ncolorama cryptography dateutil dbus dev hidapi idna libgpiod marshmallow more-itertools multidict netifaces \npackaging passlib pillow ply psutil pycparser pyelftools pyghmi pygments pyparsing requests semantic-version \nsetproctitle setuptools six spidev systemd tabulate urllib3 wrapt xlib yaml yarl\" )\n  do\n    pkgs=\"$pkgs python3-$i\"\n  done\n  echo \"-> Install python packages\"\n  $APT_EXE install $pkgs -y > /dev/null\n  # U\n  pip3 install dbus_next==0.2.3 zstandard==0.18.0 pyserial==3.5 aiohttp==3.8.3\n} # end install python-packages\n\notg-devices() {\n  modprobe libcomposite\n  if [ ! -e /sys/kernel/config/usb_gadget/kvmd ]; then\n    mkdir -p /sys/kernel/config/usb_gadget/kvmd/functions\n    cd /sys/kernel/config/usb_gadget/kvmd/functions\n    mkdir hid.usb0  hid.usb1  hid.usb2  mass_storage.usb0\n  fi\n  cd ${APP_PATH}\n} # end otg-device creation\n\ninstall-tc358743() {\n  ### CSI Support for Raspbian ###\n  wget -O- -q https://www.linux-projects.org/listing/uv4l_repo/lpkey.asc | apt-key add -\n  echo \"deb https://www.linux-projects.org/listing/uv4l_repo/raspbian/stretch stretch main\" | tee /etc/apt/sources.list.d/uv4l.list\n\n  apt-get update > /dev/null\n  echo \"$APT_EXE install uv4l-tc358743-extras -y\" \n  $APT_EXE install uv4l-tc358743-extras -y > /dev/null\n} # install package for tc358743\n\nboot-files() { \n  if [[ $( grep srepac /boot/config.txt | wc -l ) -eq 0 ]]; then\n\n    if [[ $( echo $platform | grep usb | wc -l ) -eq 1 ]]; then\n\n      # Armbian does not support config.txt, remove it.\n\n      # amlogic does not support CSI, skip the following\n      # add the tc358743 module to be loaded at boot for CSI\n      # if [[ $( grep -w tc358743 /etc/modules | wc -l ) -eq 0 ]]; then\n      #   echo \"tc358743\" >> /etc/modules\n      # fi\n\n      # install-tc358743 \n      :\n    fi \n  fi  # end of check if entries are already in /boot/config.txt\n\n  # Remove OTG serial (Orange pi zero's kernel not support it)\n  sed -i '/^g_serial/d' /etc/modules \n\n  # /etc/modules required entries for DWC2, HID and I2C\n  if [[ $( grep -w dwc2 /etc/modules | wc -l ) -eq 0 ]]; then\n    echo \"dwc2\" >> /etc/modules\n  fi\n  if [[ $( grep -w libcomposite /etc/modules | wc -l ) -eq 0 ]]; then\n    echo \"libcomposite\" >> /etc/modules\n  fi\n  if [[ $( grep -w i2c-dev /etc/modules | wc -l ) -eq 0 ]]; then\n    echo \"i2c-dev\" >> /etc/modules\n  fi\n\n#  printf \"\\n/boot/config.txt\\n\\n\"\n#  cat /boot/config.txt\n  printf \"\\n/etc/modules\\n\\n\"\n  cat /etc/modules\n} # end of necessary boot files\nget-packages() { \n  printf \"\\n\\n-> Getting Pi-KVM packages from ${PIKVMREPO}\\n\\n\"\n  mkdir -p \"${KVMDCACHE}\"\n  #echo \"wget ${PIKVMREPO} -O ${PKGINFO}\"\n  rm -f \"${PKGINFO}\"\n  download \"${PIKVMREPO}${PIKVMREPO_PKG}\" \"${PKGINFO}\"\n  echo \"import Pi-Kvm Repo Key\"\n  gpg --keyserver keyserver.ubuntu.com --recv-keys $PIKVM_KEY\n  gpg -a --export $PIKVM_KEY | apt-key add -\n  # Download each of the pertinent packages for Rpi4, webterm, and the main service\n  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]'\"\n  if [ $CUSTOM_KVMD_VERSION -eq 1 ]; then\n    PIKVM_PKGS_CMD=\"$PIKVM_PKGS_CMD | egrep -v 'kvmd-[0-9]'\"\n  fi\n  PIKVM_PKGS=`$PIKVM_PKGS_CMD`\n  for pkg in $PIKVM_PKGS\n  do\n    rm -f \"${KVMDCACHE}/$pkg.sig\"\n    download \"${PIKVMREPO}/$pkg.sig\" \"${KVMDCACHE}/$pkg.sig\"\n    download \"${PIKVMREPO}/$pkg ${KVMDCACHE}/$pkg gpg ${KVMDCACHE}/$pkg.sig\"\n  done\n\n  echo\n  echo \"ls -l ${KVMDCACHE}\"\n  ls -l \"${KVMDCACHE}\"\n  echo\n} # end get-packages function\n\nget-platform() {\n  # tryagain=1\n  # while [ $tryagain -eq 1 ]; do\n  #   # amglogic tv box only has usb port, use usb dongle.\n\t# # printf \"Choose which capture device you will use:\\n\\n  1 - USB dongle\\n  2 - v2 CSI\\n  3 - V3 HAT\\n\" \n  #   # read -p \"Please type [1-3]: \" capture\n\t# capture=1;\n\n  # done\n    case $USE_CSI in \n      0) platform=\"kvmd-platform-v2-hdmiusb-rpi4\"; tryagain=0;;\n      # 2) platform=\"kvmd-platform-v2-hdmi-rpi4\"; tryagain=0;;\n      1) platform=\"kvmd-platform-v3-hdmi-rpi4\"; tryagain=0;;\n      *) printf \"\\nTry again.\\n\"; tryagain=1;;\n    esac\n    echo\n    echo \"Platform selected -> $platform\"\n    echo\n} # end get-platform\n\n\ninstall-kvmd-pkgs() {\n  cd /\n\n  INSTLOG=\"${KVMDCACHE}/installed_ver.txt\"; rm -f \"$INSTLOG\"\n  date > $INSTLOG \n\n# # uncompress platform package first\n#   for i in $( ls \"${KVMDCACHE}/${platform}-*.tar.xz\" )\n#   do\n#     echo \"-> Extracting package $i into /\" >> \"$INSTLOG\" \n#     tar -vxf \"$i\"\n#   done\n\n# then uncompress, kvmd-{version}, kvmd-webterm, and janus packages \n\n  for i in $PIKVM_PKGS\n  do\n    echo \"-> Extracting package $i into /\" >> \"$INSTLOG\"\n    tar -vxf $i\n  done\n  if [ $CUSTOM_KVMD_VERSION -eq 1 ]; then\n  # Use custom kvmd version replace kvmd offical package\n    download \"${KVMD_COMMON_PKG_URL}\" \"${KVMDCACHE}/kvmd-common.tar.gz\"\n    echo \"-> Extracting common kvmd package into /\" >> \"$INSTLOG\"\n    tar -vxf \"${KVMDCACHE}/kvmd-common.tar.gz\"\n    echo \"-> Install custom version kvmd\" >> \"$INSTLOG\"\n    $APT_EXE install python3-setuptools -y\n    download \"${MIRROR_GITHUB}/pikvm/kvmd/archive/refs/tags/v$KVMD_VERSION.tar.gz\" \"${KVMDCACHE}/kvmd.tar.gz\"\n    mkdir -p \"${KVMDCACHE}/kvmd-tmp\"\n    tar axf \"${KVMDCACHE}/kvmd.tar.gz\" -C \"${KVMDCACHE}/kvmd-tmp\"\n    cd \"${KVMDCACHE}/kvmd-tmp/kvmd-$KVMD_VERSION/\"\n    ./setup.py install\n    cd \"$APP_PATH\"\n    rm -rf \"${KVMDCACHE}/kvmd-tmp\"\n  fi\n  cd \"${APP_PATH}\"\n  cp bin/* /usr/bin/\n} # end install-kvmd-pkgs\n\nfix-udevrules() { \n  # for hdmiusb, replace %b with 1-1.4:1.0 in /etc/udev/rules.d/99-kvmd.rules\n  sed -i -e 's+\\%b+1-1.4:1.0+g' /etc/udev/rules.d/99-kvmd.rules\n  echo\n  cat /etc/udev/rules.d/99-kvmd.rules\n} # end fix-udevrules\n\nenable-kvmd-svcs() { \n  # enable KVMD services but don't start them\n  echo \"-> Enabling kvmd-nginx kvmd-webterm kvmd-otg and kvmd services, but do not start them.\"\n  systemctl enable kvmd-nginx kvmd-webterm kvmd-otg kvmd \n\n  # in case going from CSI to USB, then disable kvmd-tc358743 service (in case it's enabled)\n  if [[ $USE_CSI -eq 0 ]]; then\n    systemctl disable --now kvmd-tc358743 \n  else\n    systemctl enable kvmd-tc358743 \n  fi\n} # end enable-kvmd-svcs \n\nbuild-ustreamer() {\n  printf \"\\n\\n-> Building ustreamer\\n\\n\"\n  # Install packages needed for building ustreamer source\n  echo \"$APT_EXE install -y libevent-dev libjpeg-dev libbsd-dev libgpiod-dev libsystemd-dev janus-dev janus\"\n  $APT_EXE install -y libevent-dev libjpeg-dev libbsd-dev libsystemd-dev\n  if [[ $USE_GPIO -eq 1 ]]; then\n    $APT_EXE install -y libgpiod-dev\n  fi\n  if [[ $USE_JANUS -eq 1 ]]; then\n    $APT_EXE install -y janus-dev janus\n  fi\n  # Download ustreamer source and build it\n  cd /tmp\n  $GIT_EXE clone $GIT_CLONE_WITH_DEPTH \"$MIRROR_GITHUB/pikvm/ustreamer\"\n  cd ustreamer/\n  # if [[ $( uname -m ) == \"aarch64\" ]]; then\n  #   make WITH_OMX=0 WITH_GPIO=1 WITH_SETPROCTITLE=1\t# ustreamer doesn't support 64-bit hardware OMX \n  # else\n  #   make WITH_OMX=1 WITH_GPIO=1 WITH_SETPROCTITLE=1\t# hardware OMX support with 32-bit ONLY\n  # fi\n  make WITH_GPIO=$USE_GPIO WITH_SYSTEMD=1 WITH_JANUS=$USE_JANUS -j\n  make install\n  # kvmd service is looking for /usr/bin/ustreamer   \n  ln -s /usr/local/bin/ustreamer /usr/bin/\n} # end build-ustreamer \n\ninstall-dependencies() {\n  echo\n  echo \"-> Installing dependencies for pikvm\"\n\n  apt-get update > /dev/null\n  # for i in $( echo \"\" )\n  # do\n  #   echo \"$APT_EXE install -y $i\"\n  #   $APT_EXE install -y $i > /dev/null\n  # done\n  echo \"-> Install basic packages\"\n  $APT_EXE install -y nginx python3 bc expect v4l-utils gpiod dialog git python3-pip tesseract-ocr tesseract-ocr-chi-sim jq\n  install-python-packages\n\n  echo \"-> Make tesseract data link\"\n  ln -s /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata\n\n  echo \"-> Install TTYD\"\n  $APT_EXE install -y ttyd\n  if [ ! -e /usr/bin/ttyd ]; then\n    echo \"-> Download from apt failed, try download offical lastest version binary file.\"\n    # Build and install ttyd\n    # cd /tmp\n    # $APT_EXE install -y build-essential cmake git libjson-c-dev libwebsockets-dev\n    # git clone --depth=1 https://github.com/tsl0922/ttyd.git\n    # cd ttyd && mkdir build && cd build\n    # cmake ..\n    # make -j && make install\n    # Install binary from GitHub\n    arch=$(dpkg --print-architecture)\n    latest=$(wget -q -O- $MIRROR_GITHUB_API/repos/tsl0922/ttyd/releases/latest | jq -r \".tag_name\")\n    if [ $arch = arm64 ]; then\n      arch='aarch64'\n    fi\n    if [ $arch = amd64 ]; then\n      arch='x86_64'\n    fi\n    wget \"$MIRROR_GITHUB/tsl0922/ttyd/releases/download/$latest/ttyd.$arch\" -O /usr/bin/ttyd\n    chmod +x /usr/bin/ttyd\n  fi\n\n  echo \"-> Install ustreamer\"\n  if [ ! -e /usr/bin/ustreamer ]; then\n    # apt install ustreamer\n    cd /tmp/\n\t  $APT_EXE install -y libevent-2.1-7 libevent-core-2.1-7 libevent-pthreads-2.1-7 build-essential\n    # ### required dependent packages for ustreamer ###\n    build-ustreamer\n    cd ${APP_PATH}\n  fi\n} # end install-dependencies\n\npython-pkg-dir() {\n  # debian system python3 no alias\n  # create quick python script to show where python packages need to go\n  cat << MYSCRIPT > /tmp/syspath.py\n#!$(which python3)\nimport sys\nprint (sys.path)\nMYSCRIPT\n\n  chmod +x /tmp/syspath.py\n\n  export PYTHONDIR_SYS=$( /tmp/syspath.py | grep packages | sed -e 's/, /\\n/g' -e 's/\\[//g' -e 's/\\]//g' -e \"s+'++g\" | tail -1 )\n  export PYTHONDIR_PIP=$( python3 -c \"import site; print(site.getsitepackages()[0])\" )\n} # end python-pkg-dir\n\nfix-nginx-symlinks() {\n  # disable default nginx service since we will use kvmd-nginx instead \n  echo\n  echo \"-> Disabling nginx service, so that we can use kvmd-nginx instead\" \n  systemctl disable --now nginx\n\n  # setup symlinks\n  echo\n  echo \"-> Creating symlinks for use with kvmd python scripts\"\n  if [ ! -e /usr/bin/nginx ]; then ln -s /usr/sbin/nginx /usr/bin/; fi\n  if [ ! -e /usr/sbin/python ]; then ln -s /usr/bin/python3 /usr/sbin/python; fi\n  if [ ! -e /usr/bin/iptables ]; then ln -s /usr/sbin/iptables /usr/bin/iptables; fi\n  # if [ ! -e /opt/vc/bin/vcgencmd ]; then mkdir -p /opt/vc/bin/; ln -s /usr/bin/vcgencmd /opt/vc/bin/vcgencmd; fi\n\n  python-pkg-dir\n\n  if [ ! -e $PYTHONDIR_PIP/kvmd ]; then\n    # Debian python版本比 pikvm官方的低一些\n    ln -s /usr/lib/python3.10/site-packages/kvmd* ${PYTHONDIR_PIP}\n  fi\n} # end fix-nginx-symlinks\n\nfix-python-symlinks(){\n    python-pkg-dir\n\n  if [ ! -e $PYTHONDIR_PIP/kvmd ]; then\n    # Debian python版本比 pikvm官方的低一些\n    ln -s /usr/lib/python3.10/site-packages/kvmd* ${PYTHONDIR_PIP}\n  fi\n}\n\napply-custom-patch(){\n  read -p \"Do you want apply old kernel msd patch? [y/n]\" answer\n  case $answer in\n    n|N|no|No)\n      echo 'You skiped this patch.'\n      ;;\n    y|Y|Yes|yes)\n      ./patches/custom/old-kernel-msd/apply.sh\n      ;;\n    *)\n      echo \"Try again.\";;\n  esac\n}\n\nfix-kvmd-for-tvbox-armbian(){\n  # 打补丁来移除一些对armbian和电视盒子不太支持的特性\n  python-pkg-dir\n  if [[ \"$CUSTOM_KVMD_VERSION\" -eq 1 ]]; then\n    cd \"$PYTHONDIR_PIP/kvmd-$KVMD_VERSION-py${PYTHON_VERSION}.egg\"\n  else\n    cd $PYTHONDIR_PIP\n  fi\n\n  # if [[ \"$DEBIAN_PYTHON\" -eq 1 ]]; then\n    # if [ `$KVMD_VERSION < 3.134` -eq ]; then\n    #   PATCH_VER=\"v3.90\"\n    # fi\n    # if [ `$KVMD_VERSION \\>= 3.134` -eq 1 ]; then\n    #   PATCH_VER=\"v3.134\"\n    # fi\n    # if [ ! -z \"$PATCH_VER\" ]; then\n    #   $GIT_EXE apply ${APP_PATH}/patches/debian_python/$PATCH_VER/*.patch\n    # fi\n  # fi\n  if [[ \"$USE_GPIO\" -eq 0 ]] && [[ \"$KVMD_BV\" -eq \"3\" ]] ; then\n    PATCH_VER=\"\"\n    if [ `expr $KVMD_SV \\<= 81` -eq 1 ]; then\n      PATCH_VER=\"v3.47-v3.81\"\n    fi\n    if [ `expr $KVMD_SV \\>= 82` -eq 1 ] && [ `expr $KVMD_SV \\<= 83` -eq 1 ]; then\n      PATCH_VER=\"v3.82-v3.83\"\n    fi\n    if [ `expr $KVMD_SV \\>= 84` -eq 1 ] && [ `expr $KVMD_SV \\<= 134` -eq 1 ]; then\n      PATCH_VER=\"v3.84-v3.134\"\n    fi\n    if [ ! -z \"$PATCH_VER\" ]; then\n      sh -c \"$GIT_EXE apply '${APP_PATH}/patches/disable_gpio/$PATCH_VER/*.patch'\"\n    fi\n  fi\n  if [ `expr $KVMD_SV \\>= 84` -eq 1 ] && [ `expr $KVMD_SV \\<= 92` -eq 1 ]; then\n      PATCH_VER=\"v3.84-v3.92\"\n      sh -c \"$GIT_EXE apply '${APP_PATH}/patches/genernal/$PATCH_VER/*.patch'\"\n  fi\n  cd ${APP_PATH}\n  read -p \"Do you want to apply custom patches?  [y/n] \" answer\n  case $answer in\n    n|N|no|No)\n     return;\n     ;;\n    y|Y|Yes|yes)\n     apply-custom-patch;\n     return;\n     ;;\n    *)\n     echo \"Try again.\";;\n  esac\n}\n\nfix-webterm() {\n  echo\n  echo \"-> Creating kvmd-webterm homedir\"\n  mkdir -p /home/kvmd-webterm\n  chown kvmd-webterm /home/kvmd-webterm\n  ls -ld /home/kvmd-webterm\n} # end fix-webterm\n\ncreate-kvmdfix() { \n  # Create kvmd-fix service and script\n  cat <<ENDSERVICE > /lib/systemd/system/kvmd-fix.service\n[Unit]\nDescription=KVMD Fixes\nAfter=network.target network-online.target nss-lookup.target\nBefore=kvmd.service\n\n[Service]\nUser=root\nType=simple\nExecStart=/usr/bin/kvmd-fix\n\n[Install]\nWantedBy=multi-user.target\nENDSERVICE\n\n  cat <<SCRIPTEND > /usr/bin/kvmd-fix\n#!/bin/bash\n# Written by @srepac\n# 1.  Properly set group ownership of /dev/gpio*\n# 2.  fix /dev/kvmd-video symlink to point to /dev/video1 (Amglogic Device video0 is not usb device)\n#\n### These fixes are required in order for kvmd service to start properly\n#\nset -x\nchgrp gpio /dev/gpio*\nchmod 660 /dev/gpio*   ### this is required in case gpio (wiringpi) is installed\nls -l /dev/gpio*\n\nls -l /dev/kvmd-video\nrm /dev/kvmd-video\n# Need to use video0 for orange pi (if you don't, the video capture won't work)\nln -s video1 /dev/kvmd-video\nSCRIPTEND\n\n  chmod +x /usr/bin/kvmd-fix\n} # end create-kvmdfix\n\nset-ownership() {\n  # set proper ownership of password files and kvmd-webterm homedir\n  cd /etc/kvmd\n  chown kvmd:kvmd htpasswd\n  chown kvmd-ipmi:kvmd-ipmi ipmipasswd\n  chown kvmd-vnc:kvmd-vnc vncpasswd\n  chown kvmd-webterm /home/kvmd-webterm\n\n  # add kvmd user to video group (this is required in order to use CSI bridge with OMX and h264 support)\n  usermod -a -G video kvmd\n} # end set-ownership\n\ncheck-kvmd-works() {\n  # check to make sure kvmd -m works before continuing\n  invalid=1\n  while [ $invalid -eq 1 ]; do\n    kvmd -m\n    read -p \"Did kvmd -m run properly?  [y/n] \" answer\n    case $answer in\n      n|N|no|No)\n        echo \"Please install missing packages as per the kvmd -m output in another ssh/terminal.\"\n        ;;\n      y|Y|Yes|yes)\n        invalid=0\t\n        ;;\n      *)\n        echo \"Try again.\";;\n    esac\n  done\n} # end check-kvmd-works\n\nstart-kvmd-svcs() {\n  #### start the main KVM services in order ####\n  # 1. nginx is the webserver\n  # 2. kvmd-otg is for OTG devices (keyboard/mouse, etc..)\n  # 3. kvmd is the main daemon\n  systemctl restart kvmd-nginx kvmd-otg kvmd-webterm kvmd \n  # systemctl status kvmd-nginx kvmd-otg kvmd-webterm kvmd \n} # end start-kvmd-svcs\n\nfix-motd() { \n  rm /etc/motd\n  cp armbian/armbian-motd /usr/bin/\n  sed -i 's/cat \\/etc\\/motd/armbian-motd/g' /lib/systemd/system/kvmd-webterm.service\n  systemctl daemon-reload\n  # systemctl restart kvmd-webterm\n} # end fix-motd\n\n# 安装armbian的包\narmbian-packages() {\n  mkdir -p /opt/vc/bin/\n  #cd /opt/vc/bin\n  # Install vcgencmd for armbian platform\n  cp -rf armbian/opt/* /opt/vc/bin\n  #cp -rf armbian/udev /etc/\n\n  cd ${APP_PATH}\n  # \n}\t#end armbian-packages\n$APT_EXE update\n$APT_EXE -y install python3 xz-utils tar wget aria2 curl\n### MAIN STARTS HERE ###\n# Install is done in two parts\n# First part requires a reboot in order to create kvmd users and groups\n# Second part will start the necessary kvmd services\n# added option to re-install by adding -f parameter (for use as platform switcher)\nexport PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )\nif [[ $( grep kvmd /etc/passwd | wc -l ) -eq 0 || \"$1\" == \"-f\" ]]; then\n  printf \"\\nRunning part 1 of PiKVM installer script for Raspbian by @srepac\\n\"\n  get-packages\n  get-platform\n  boot-files\n  install-kvmd-pkgs\n  create-override\n  gen-ssl-certs\n  fix-udevrules\n  install-dependencies\n  otg-devices\n  armbian-packages\n  systemctl disable --now janus\n  fix-kvmd-for-tvbox-armbian\n  \n  # Fix paste-as-keys if running python 3.7\n  if [[ $( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 ) == \"3.7\" ]]; then\n    sed -i -e 's/reversed//g' $PYTHONDIR/kvmd/keyboard/printer.py\n  fi\n\n  sync\n  echo \"-> Synced data, you can reboot system safety.\"\n  printf \"\\n\\nReboot is required to create kvmd users and groups.\\nPlease re-run this script after reboot to complete the install.\\n\"\n  # Ask user to press CTRL+C before reboot or ENTER to proceed with reboot\n  press-enter\n  reboot\nelse\n  printf \"\\nRunning part 2 of PiKVM installer script for Raspbian by @srepac\\n\"\n  fix-nginx-symlinks\n  fix-python-symlinks\n  fix-webterm\n  fix-motd\n  set-ownership \n  create-kvmdfix\n  check-kvmd-works\n  enable-kvmd-svcs\n  start-kvmd-svcs\n\n  sync\n  printf \"\\nCheck kvmd devices\\n\\n\" \n  ls -l /dev/kvmd*\n  printf \"\\nYou should see devices for keyboard, mouse, and video.\\n\"\n\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\"\nfi\n"
  },
  {
    "path": "libs/checksum.sh",
    "content": "checksum(){\n    export sumRet=0\n    case $1 in\n        gpg) checksum_gpg $2 $3;;\n    esac\n}\nchecksum_gpg(){\n    gpg --verify $2 $1 2> /dev/null\n    case $? in\n        1) export sumRet=0;echo bad signature, skip checksum.;;\n        *) export sumRet=$?;;\n    esac\n    return;\n}"
  },
  {
    "path": "libs/download_aria2.sh",
    "content": "#!/bin/bash\nsource libs/checksum.sh\ndownload(){\n    tryCount=0\n    echo Downloading $1 To $2\n    download2 $1 $2 $3 $4\n    unset tryCount\n}\ndownload2() {\n    filename=$(readlink -f $2)\n    echo \"aria2c -x 16 -s 16 --file-allocation=falloc --min-split-size 2M $1 -o $filename -d /\"\n    if [ ! -f \"$2\" ]; then\n        aria2c -x 16 -s 16 --file-allocation=falloc --min-split-size 2M $1 -o $filename -d /\n    else\n        if [ ! -n \"$3\" ]; then\n            echo \"File $2 is exists, skip download.\"\n            return\n        fi\n    fi\n    echo \"Checksum for $2\"\n    checksum $3 $2 $4\n    if [ \"$sumRet\" != 0 ]; then\n        echo \"File checksum failed, try redownload file. Result is $sumRet\"\n        if [[ \"$tryCount\" -lt 3 ]]; then\n            tryCount=`expr $tryCount + 1`\n            rm $2\n            download2 $1 $2 $3 $4\n        else\n            echo \"Try $tryCount times, download failed.\"\n        fi\n    else\n        echo \"File checksum successful.\"\n    fi\n    unset sumRet\n}"
  },
  {
    "path": "libs/download_wget.sh",
    "content": "#!/bin/bash\nsource libs/checksum.sh\ndownload(){\n    tryCount=0\n    echo Downloading $1 To $2\n    download2 $1 $2 $3 $4\n    unset tryCount\n}\ndownload2() {\n    if [ ! -f \"$2\" ]; then\n        echo \"wget $1 -O $2\"\n        wget $1 -O $2\n    else\n        if [ ! -n \"$3\" ]; then\n            echo \"File $2 is exists, skip download.\"\n            return\n        fi\n    fi\n    echo \"Checksum for $2\"\n    checksum $3 $2 $4\n    if [ \"$sumRet\" != 0 ]; then\n        echo \"File checksum failed, try redownload file. Result is $sumRet\"\n        if [[ \"$tryCount\" -lt 3 ]]; then\n            tryCount=`expr $tryCount + 1`\n            rm $2\n            download2 $1 $2 $3 $4\n        else\n            echo \"Try $tryCount times, download failed.\"\n        fi\n    else\n        echo \"File checksum successful.\"\n    fi\n    unset sumRet\n}"
  },
  {
    "path": "patches/custom/old-kernel-msd/apply.sh",
    "content": "#!/bin/bash\n# PYTHON_VERSION=$( python3 -V | awk '{print $2}' | cut -d'.' -f1,2 )\nAPP_PATH=$(readlink -f $(dirname $0))\necho \"-> Apply patches\"\nif [[ \"$CUSTOM_KVMD_VERSION\" -eq 1 ]]; then\n    cd $PYTHONDIR_PIP/kvmd-$KVMD_VERSION-py${PYTHON_VERSION}.egg/\nelse\n    cd $PYTHONDIR_PIP\nfi\nPATCH_VER=\"\"\nif [ `expr $KVMD_SV \\>= 84` -eq 1 ] && [ `expr $KVMD_SV \\<= 92` -eq 1 ]; then\n      PATCH_VER=\"v3.84-v3.134\"\nfi\nif [ `expr $KVMD_SV \\>= 124` -eq 1 ] && [ `expr $KVMD_SV \\<= 142` -eq 1 ]; then\n      PATCH_VER=\"v3.124-v3.142\"\nfi\ngit apply ${APP_PATH}/${PATCH_VER}/*.patch\ncd ${APP_PATH}\n# echo \"-> Add otgmsd unlock link\"\n# cp kvmd-helper-otgmsd-unlock /usr/bin/\necho \"-> Add sudoer\"\necho \"kvmd ALL=(ALL) NOPASSWD: /usr/bin/kvmd-helper-otgmsd-unlock\" >> /etc/sudoers.d/99_kvmd\necho \"-> Apply old kernel msd patch done.\""
  },
  {
    "path": "patches/custom/old-kernel-msd/v3.124-v3.142/0001-Apply-old-kernel-msd-patch-for-v3.134.patch",
    "content": "From 18723b4c8e13a5cd0049982cfbbf61b4409ba159 Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github.com>\nDate: Mon, 15 Aug 2022 19:31:45 +0800\nSubject: [PATCH] Apply old kernel msd patch for v3.134\n\n---\n kvmd/aiohelpers.py               | 31 ++++++++++++-----\n kvmd/apps/otg/__init__.py        |  3 +-\n kvmd/apps/otgmsd/__init__.py     | 25 +++++++++++++-\n kvmd/helpers/unlock/__init__.py  | 58 ++++++++++++++++++++++++++++++++\n kvmd/helpers/unlock/__main__.py  | 24 +++++++++++++\n kvmd/plugins/msd/otg/__init__.py | 20 +++++++----\n kvmd/plugins/msd/otg/drive.py    |  5 +--\n 7 files changed, 145 insertions(+), 21 deletions(-)\n create mode 100644 kvmd/helpers/unlock/__init__.py\n create mode 100644 kvmd/helpers/unlock/__main__.py\n\ndiff --git a/kvmd/aiohelpers.py b/kvmd/aiohelpers.py\nindex ae943d23..e0e27fd3 100644\n--- a/kvmd/aiohelpers.py\n+++ b/kvmd/aiohelpers.py\n@@ -40,11 +40,26 @@ async def remount(name: str, base_cmd: List[str], rw: bool) -> bool:\n     ]\n     logger.info(\"Remounting %s storage to %s: %s ...\", name, mode.upper(), tools.cmdfmt(cmd))\n     try:\n-        proc = await aioproc.log_process(cmd, logger)\n-        if proc.returncode != 0:\n-            assert proc.returncode is not None\n-            raise subprocess.CalledProcessError(proc.returncode, cmd)\n-    except Exception as err:\n-        logger.error(\"Can't remount %s storage: %s\", name, tools.efmt(err))\n-        return False\n-    return True\n+        await _run_helper(cmd)\n+    except Exception:\n+        logger.error(\"Can't remount internal storage\")\n+        raise\n+\n+\n+async def unlock_drive(base_cmd: List[str]) -> None:\n+    logger = get_logger(0)\n+    logger.info(\"Unlocking the drive ...\")\n+    try:\n+        await _run_helper(base_cmd)\n+    except Exception:\n+        logger.error(\"Can't unlock the drive\")\n+        raise\n+\n+\n+# =====\n+async def _run_helper(cmd: List[str]) -> None:\n+    logger = get_logger(0)\n+    logger.info(\"Executing helper %s ...\", cmd)\n+    proc = await aioproc.log_process(cmd, logger)\n+    if proc.returncode != 0:\n+        logger.error(f\"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}\")\ndiff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py\nindex 9b6f5e69..af6327b2 100644\n--- a/kvmd/apps/otg/__init__.py\n+++ b/kvmd/apps/otg/__init__.py\n@@ -186,7 +186,6 @@ class _GadgetConfig:\n             _chown(join(func_path, \"lun.0/cdrom\"), user)\n             _chown(join(func_path, \"lun.0/ro\"), user)\n             _chown(join(func_path, \"lun.0/file\"), user)\n-            _chown(join(func_path, \"lun.0/forced_eject\"), user)\n         _symlink(func_path, join(self.__profile_path, func))\n         name = (\"Mass Storage Drive\" if self.__msd_instance == 0 else f\"Extra Drive #{self.__msd_instance}\")\n         self.__create_meta(func, name)\n@@ -295,7 +294,7 @@ def _cmd_stop(config: Section) -> None:\n     logger.info(\"Disabling gadget %r ...\", config.otg.gadget)\n     _write(join(gadget_path, \"UDC\"), \"\\n\")\n \n-    _unlink(join(gadget_path, \"os_desc\", usb.G_PROFILE_NAME), optional=True)\n+    _unlink(join(gadget_path, \"os_desc\", usb.G_PROFILE_NAME), True)\n \n     profile_path = join(gadget_path, usb.G_PROFILE)\n     for func in os.listdir(profile_path):\ndiff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py\nindex 0d32331b..26db4c8e 100644\n--- a/kvmd/apps/otgmsd/__init__.py\n+++ b/kvmd/apps/otgmsd/__init__.py\n@@ -21,12 +21,15 @@\n \n \n import os\n+import signal\n import errno\n import argparse\n \n from typing import List\n from typing import Optional\n \n+import psutil\n+\n from ...validators.basic import valid_bool\n from ...validators.basic import valid_int_f0\n from ...validators.os import valid_abs_file\n@@ -56,6 +59,21 @@ def _set_param(gadget: str, instance: int, param: str, value: str) -> None:\n         raise\n \n \n+def _unlock() -> None:\n+    # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924\n+    found = False\n+    for proc in psutil.process_iter():\n+        attrs = proc.as_dict(attrs=[\"name\", \"exe\", \"pid\"])\n+        if attrs.get(\"name\") == \"file-storage\" and not attrs.get(\"exe\"):\n+            try:\n+                proc.send_signal(signal.SIGUSR1)\n+                found = True\n+            except Exception as err:\n+                raise SystemExit(f\"Can't send SIGUSR1 to MSD kernel thread with pid={attrs['pid']}: {err}\")\n+    if not found:\n+        raise SystemExit(\"Can't find MSD kernel thread\")\n+\n+\n # =====\n def main(argv: Optional[List[str]]=None) -> None:\n     (parent_parser, argv, config) = init(\n@@ -71,6 +89,8 @@ def main(argv: Optional[List[str]]=None) -> None:\n     )\n     parser.add_argument(\"-i\", \"--instance\", default=0, type=valid_int_f0,\n                         metavar=\"<N>\", help=\"Drive instance (0 for KVMD drive)\")\n+    parser.add_argument(\"--unlock\", action=\"store_true\",\n+                        help=\"Send SIGUSR1 to MSD kernel thread\")\n     parser.add_argument(\"--set-cdrom\", default=None, type=valid_bool,\n                         metavar=\"<1|0|yes|no>\", help=\"Set CD-ROM flag\")\n     parser.add_argument(\"--set-rw\", default=None, type=valid_bool,\n@@ -90,8 +110,11 @@ def main(argv: Optional[List[str]]=None) -> None:\n     set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value))\n     get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))\n \n+    if options.unlock:\n+        _unlock()\n+\n     if options.eject:\n-        set_param(\"forced_eject\", \"\")\n+        set_param(\"file\", \"\")\n \n     if options.set_cdrom is not None:\n         set_param(\"cdrom\", str(int(options.set_cdrom)))\ndiff --git a/kvmd/helpers/unlock/__init__.py b/kvmd/helpers/unlock/__init__.py\nnew file mode 100644\nindex 00000000..140e0e7c\n--- /dev/null\n+++ b/kvmd/helpers/unlock/__init__.py\n@@ -0,0 +1,58 @@\n+# ========================================================================== #\n+#                                                                            #\n+#    KVMD - The main PiKVM daemon.                                           #\n+#                                                                            #\n+#    Copyright (C) 2018-2022  Maxim Devaev <mdevaev@gmail.com>               #\n+#                                                                            #\n+#    This program is free software: you can redistribute it and/or modify    #\n+#    it under the terms of the GNU General Public License as published by    #\n+#    the Free Software Foundation, either version 3 of the License, or       #\n+#    (at your option) any later version.                                     #\n+#                                                                            #\n+#    This program is distributed in the hope that it will be useful,         #\n+#    but WITHOUT ANY WARRANTY; without even the implied warranty of          #\n+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #\n+#    GNU General Public License for more details.                            #\n+#                                                                            #\n+#    You should have received a copy of the GNU General Public License       #\n+#    along with this program.  If not, see <https://www.gnu.org/licenses/>.  #\n+#                                                                            #\n+# ========================================================================== #\n+\n+\n+import sys\n+import signal\n+\n+import psutil\n+\n+\n+# =====\n+_PROCESS_NAME = \"file-storage\"\n+\n+\n+# =====\n+def _log(msg: str) -> None:\n+    print(msg, file=sys.stderr)\n+\n+\n+def _unlock() -> None:\n+    # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924\n+    found = False\n+    for proc in psutil.process_iter():\n+        attrs = proc.as_dict(attrs=[\"name\", \"exe\", \"pid\"])\n+        if attrs.get(\"name\") == _PROCESS_NAME and not attrs.get(\"exe\"):\n+            _log(f\"Sending SIGUSR1 to MSD {_PROCESS_NAME!r} kernel thread with pid={attrs['pid']} ...\")\n+            try:\n+                proc.send_signal(signal.SIGUSR1)\n+                found = True\n+            except Exception as err:\n+                raise SystemExit(f\"Can't send SIGUSR1 to MSD kernel thread with pid={attrs['pid']}: {err}\")\n+    if not found:\n+        raise SystemExit(f\"Can't find MSD kernel thread {_PROCESS_NAME!r}\")\n+\n+\n+# =====\n+def main() -> None:\n+    if len(sys.argv) != 2 or sys.argv[1] != \"unlock\":\n+        raise SystemExit(f\"Usage: {sys.argv[0]} [unlock]\")\n+    _unlock()\ndiff --git a/kvmd/helpers/unlock/__main__.py b/kvmd/helpers/unlock/__main__.py\nnew file mode 100644\nindex 00000000..3849d1b9\n--- /dev/null\n+++ b/kvmd/helpers/unlock/__main__.py\n@@ -0,0 +1,24 @@\n+# ========================================================================== #\n+#                                                                            #\n+#    KVMD - The main PiKVM daemon.                                           #\n+#                                                                            #\n+#    Copyright (C) 2018-2022  Maxim Devaev <mdevaev@gmail.com>               #\n+#                                                                            #\n+#    This program is free software: you can redistribute it and/or modify    #\n+#    it under the terms of the GNU General Public License as published by    #\n+#    the Free Software Foundation, either version 3 of the License, or       #\n+#    (at your option) any later version.                                     #\n+#                                                                            #\n+#    This program is distributed in the hope that it will be useful,         #\n+#    but WITHOUT ANY WARRANTY; without even the implied warranty of          #\n+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #\n+#    GNU General Public License for more details.                            #\n+#                                                                            #\n+#    You should have received a copy of the GNU General Public License       #\n+#    along with this program.  If not, see <https://www.gnu.org/licenses/>.  #\n+#                                                                            #\n+# ========================================================================== #\n+\n+\n+from . import main\n+main()\ndiff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py\nindex 5a8b86a6..d4de24b8 100644\n--- a/kvmd/plugins/msd/otg/__init__.py\n+++ b/kvmd/plugins/msd/otg/__init__.py\n@@ -144,6 +144,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n         storage_path: str,\n \n         remount_cmd: List[str],\n+        unlock_cmd: List[str],\n \n         initial: Dict,\n \n@@ -159,6 +160,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n         self.__meta_path = os.path.join(self.__storage_path, \"meta\")\n \n         self.__remount_cmd = remount_cmd\n+        self.__unlock_cmd = unlock_cmd\n \n         self.__initial_image: str = initial[\"image\"]\n         self.__initial_cdrom: bool = initial[\"cdrom\"]\n@@ -184,10 +186,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n \n             \"storage\": Option(\"/var/lib/kvmd/msd\", type=valid_abs_dir, unpack_as=\"storage_path\"),\n \n-            \"remount_cmd\": Option([\n-                \"/usr/bin/sudo\", \"--non-interactive\",\n-                \"/usr/bin/kvmd-helper-otgmsd-remount\", \"{mode}\",\n-            ], type=valid_command),\n+            \"remount_cmd\": Option([*sudo, \"/usr/bin/kvmd-helper-otgmsd-remount\", \"{mode}\"], type=valid_command),\n+            \"unlock_cmd\":  Option([*sudo, \"/usr/bin/kvmd-helper-otgmsd-unlock\", \"unlock\"],  type=valid_command),\n \n             \"initial\": {\n                 \"image\": Option(\"\",    type=valid_printable_filename, if_empty=\"\"),\n@@ -250,6 +250,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n     async def reset(self) -> None:\n         async with self.__state.busy(check_online=False):\n             try:\n+                await self.__unlock_drive()\n                 self.__drive.set_image_path(\"\")\n                 self.__drive.set_cdrom_flag(False)\n                 self.__drive.set_rw_flag(False)\n@@ -314,6 +315,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n                 if not os.path.exists(self.__state.vd.image.path):\n                     raise MsdUnknownImageError()\n \n+                await self.__unlock_drive()\n                 self.__drive.set_rw_flag(self.__state.vd.rw)\n                 self.__drive.set_cdrom_flag(self.__state.vd.cdrom)\n                 if self.__state.vd.rw:\n@@ -323,6 +325,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n             else:\n                 if not (self.__state.vd.connected or self.__drive.get_image_path()):\n                     raise MsdDisconnectedError()\n+\n+                await self.__unlock_drive()\n                 self.__drive.set_image_path(\"\")\n                 await self.__remount_rw(False, fatal=False)\n \n@@ -529,6 +533,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n             if os.path.exists(path):\n                 logger.info(\"Setting up initial image %r ...\", self.__initial_image)\n                 try:\n+                    await self.__unlock_drive()\n                     self.__drive.set_rw_flag(False)\n                     self.__drive.set_cdrom_flag(self.__initial_cdrom)\n                     self.__drive.set_image_path(path)\n@@ -597,5 +602,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n \n     async def __remount_rw(self, rw: bool, fatal: bool=True) -> None:\n         if not (await aiohelpers.remount(\"MSD\", self.__remount_cmd, rw)):\n-            if fatal:\n-                raise MsdError(\"Can't execute remount helper\")\n+            pass\n+            #raise MsdError(\"Can't execute remount helper\")\n+\n+    async def __unlock_drive(self) -> None:\n+        await aiohelpers.unlock_drive(self.__unlock_cmd)\ndiff --git a/kvmd/plugins/msd/otg/drive.py b/kvmd/plugins/msd/otg/drive.py\nindex 11af7f81..ee54e5e9 100644\n--- a/kvmd/plugins/msd/otg/drive.py\n+++ b/kvmd/plugins/msd/otg/drive.py\n@@ -53,10 +53,7 @@ class Drive:\n     # =====\n \n     def set_image_path(self, path: str) -> None:\n-        if path:\n-            self.__set_param(\"file\", path)\n-        else:\n-            self.__set_param(\"forced_eject\", \"\")\n+        self.__set_param(\"file\", path)\n \n     def get_image_path(self) -> str:\n         return self.__get_param(\"file\")\n-- \n2.34.1.windows.1\n\n"
  },
  {
    "path": "patches/custom/old-kernel-msd/v3.84-v3.92/0001-Revert-force-eject-feature-to-unlock-helper.patch",
    "content": "From 3d137882ac38ac046b7d09cada1883b304b04319 Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github.com>\nDate: Fri, 20 May 2022 18:34:21 +0800\nSubject: [PATCH] Revert force eject feature to unlock helper\n\n---\n kvmd/aiohelpers.py               | 31 ++++++++++++-----\n kvmd/apps/otg/__init__.py        |  3 +-\n kvmd/apps/otgmsd/__init__.py     | 25 +++++++++++++-\n kvmd/helpers/unlock/__init__.py  | 58 ++++++++++++++++++++++++++++++++\n kvmd/helpers/unlock/__main__.py  | 24 +++++++++++++\n kvmd/plugins/msd/otg/__init__.py | 19 ++++++++---\n kvmd/plugins/msd/otg/drive.py    |  5 +--\n 7 files changed, 145 insertions(+), 20 deletions(-)\n create mode 100644 kvmd/helpers/unlock/__init__.py\n create mode 100644 kvmd/helpers/unlock/__main__.py\n\ndiff --git a/kvmd/aiohelpers.py b/kvmd/aiohelpers.py\nindex 6357764c..37a5d4b9 100644\n--- a/kvmd/aiohelpers.py\n+++ b/kvmd/aiohelpers.py\n@@ -40,11 +40,26 @@ async def remount(name: str, base_cmd: List[str], rw: bool) -> bool:\n     ]\n     logger.info(\"Remounting %s storage to %s: %s ...\", name, mode.upper(), cmd)\n     try:\n-        proc = await aioproc.log_process(cmd, logger)\n-        if proc.returncode != 0:\n-            assert proc.returncode is not None\n-            raise subprocess.CalledProcessError(proc.returncode, cmd)\n-    except Exception as err:\n-        logger.error(\"Can't remount %s storage: %s\", name, tools.efmt(err))\n-        return False\n-    return True\n+        await _run_helper(cmd)\n+    except Exception:\n+        logger.error(\"Can't remount internal storage\")\n+        raise\n+\n+\n+async def unlock_drive(base_cmd: List[str]) -> None:\n+    logger = get_logger(0)\n+    logger.info(\"Unlocking the drive ...\")\n+    try:\n+        await _run_helper(base_cmd)\n+    except Exception:\n+        logger.error(\"Can't unlock the drive\")\n+        raise\n+\n+\n+# =====\n+async def _run_helper(cmd: List[str]) -> None:\n+    logger = get_logger(0)\n+    logger.info(\"Executing helper %s ...\", cmd)\n+    proc = await aioproc.log_process(cmd, logger)\n+    if proc.returncode != 0:\n+        raise MsdError(f\"Error while helper execution: pid={proc.pid}; retcode={proc.returncode}\")\ndiff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py\nindex cbf7a197..d0ed0554 100644\n--- a/kvmd/apps/otg/__init__.py\n+++ b/kvmd/apps/otg/__init__.py\n@@ -182,7 +182,6 @@ class _GadgetConfig:\n             _chown(join(func_path, \"lun.0/cdrom\"), user)\n             _chown(join(func_path, \"lun.0/ro\"), user)\n             _chown(join(func_path, \"lun.0/file\"), user)\n-            _chown(join(func_path, \"lun.0/forced_eject\"), user)\n         _symlink(func_path, join(self.__profile_path, func))\n         name = (\"Mass Storage Drive\" if self.__msd_instance == 0 else f\"Extra Drive #{self.__msd_instance}\")\n         self.__create_meta(func, name)\n@@ -291,7 +290,7 @@ def _cmd_stop(config: Section) -> None:\n     logger.info(\"Disabling gadget %r ...\", config.otg.gadget)\n     _write(join(gadget_path, \"UDC\"), \"\\n\")\n \n-    _unlink(join(gadget_path, \"os_desc\", usb.G_PROFILE_NAME), optional=True)\n+    _unlink(join(gadget_path, \"os_desc\", usb.G_PROFILE_NAME), True)\n \n     profile_path = join(gadget_path, usb.G_PROFILE)\n     for func in os.listdir(profile_path):\ndiff --git a/kvmd/apps/otgmsd/__init__.py b/kvmd/apps/otgmsd/__init__.py\nindex f57b3107..78f8e3c7 100644\n--- a/kvmd/apps/otgmsd/__init__.py\n+++ b/kvmd/apps/otgmsd/__init__.py\n@@ -21,12 +21,15 @@\n \n \n import os\n+import signal\n import errno\n import argparse\n \n from typing import List\n from typing import Optional\n \n+import psutil\n+\n from ...validators.basic import valid_bool\n from ...validators.basic import valid_int_f0\n from ...validators.os import valid_abs_file\n@@ -56,6 +59,21 @@ def _set_param(gadget: str, instance: int, param: str, value: str) -> None:\n         raise\n \n \n+def _unlock() -> None:\n+    # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924\n+    found = False\n+    for proc in psutil.process_iter():\n+        attrs = proc.as_dict(attrs=[\"name\", \"exe\", \"pid\"])\n+        if attrs.get(\"name\") == \"file-storage\" and not attrs.get(\"exe\"):\n+            try:\n+                proc.send_signal(signal.SIGUSR1)\n+                found = True\n+            except Exception as err:\n+                raise SystemExit(f\"Can't send SIGUSR1 to MSD kernel thread with pid={attrs['pid']}: {err}\")\n+    if not found:\n+        raise SystemExit(\"Can't find MSD kernel thread\")\n+\n+\n # =====\n def main(argv: Optional[List[str]]=None) -> None:\n     (parent_parser, argv, config) = init(\n@@ -70,6 +88,8 @@ def main(argv: Optional[List[str]]=None) -> None:\n     )\n     parser.add_argument(\"-i\", \"--instance\", default=0, type=valid_int_f0,\n                         metavar=\"<N>\", help=\"Drive instance (0 for KVMD drive)\")\n+    parser.add_argument(\"--unlock\", action=\"store_true\",\n+                        help=\"Send SIGUSR1 to MSD kernel thread\")\n     parser.add_argument(\"--set-cdrom\", default=None, type=valid_bool,\n                         metavar=\"<1|0|yes|no>\", help=\"Set CD-ROM flag\")\n     parser.add_argument(\"--set-rw\", default=None, type=valid_bool,\n@@ -89,8 +109,11 @@ def main(argv: Optional[List[str]]=None) -> None:\n     set_param = (lambda param, value: _set_param(config.otg.gadget, options.instance, param, value))\n     get_param = (lambda param: _get_param(config.otg.gadget, options.instance, param))\n \n+    if options.unlock:\n+        _unlock()\n+\n     if options.eject:\n-        set_param(\"forced_eject\", \"\")\n+        set_param(\"file\", \"\")\n \n     if options.set_cdrom is not None:\n         set_param(\"cdrom\", str(int(options.set_cdrom)))\ndiff --git a/kvmd/helpers/unlock/__init__.py b/kvmd/helpers/unlock/__init__.py\nnew file mode 100644\nindex 00000000..140e0e7c\n--- /dev/null\n+++ b/kvmd/helpers/unlock/__init__.py\n@@ -0,0 +1,58 @@\n+# ========================================================================== #\n+#                                                                            #\n+#    KVMD - The main PiKVM daemon.                                           #\n+#                                                                            #\n+#    Copyright (C) 2018-2022  Maxim Devaev <mdevaev@gmail.com>               #\n+#                                                                            #\n+#    This program is free software: you can redistribute it and/or modify    #\n+#    it under the terms of the GNU General Public License as published by    #\n+#    the Free Software Foundation, either version 3 of the License, or       #\n+#    (at your option) any later version.                                     #\n+#                                                                            #\n+#    This program is distributed in the hope that it will be useful,         #\n+#    but WITHOUT ANY WARRANTY; without even the implied warranty of          #\n+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #\n+#    GNU General Public License for more details.                            #\n+#                                                                            #\n+#    You should have received a copy of the GNU General Public License       #\n+#    along with this program.  If not, see <https://www.gnu.org/licenses/>.  #\n+#                                                                            #\n+# ========================================================================== #\n+\n+\n+import sys\n+import signal\n+\n+import psutil\n+\n+\n+# =====\n+_PROCESS_NAME = \"file-storage\"\n+\n+\n+# =====\n+def _log(msg: str) -> None:\n+    print(msg, file=sys.stderr)\n+\n+\n+def _unlock() -> None:\n+    # https://github.com/torvalds/linux/blob/3039fad/drivers/usb/gadget/function/f_mass_storage.c#L2924\n+    found = False\n+    for proc in psutil.process_iter():\n+        attrs = proc.as_dict(attrs=[\"name\", \"exe\", \"pid\"])\n+        if attrs.get(\"name\") == _PROCESS_NAME and not attrs.get(\"exe\"):\n+            _log(f\"Sending SIGUSR1 to MSD {_PROCESS_NAME!r} kernel thread with pid={attrs['pid']} ...\")\n+            try:\n+                proc.send_signal(signal.SIGUSR1)\n+                found = True\n+            except Exception as err:\n+                raise SystemExit(f\"Can't send SIGUSR1 to MSD kernel thread with pid={attrs['pid']}: {err}\")\n+    if not found:\n+        raise SystemExit(f\"Can't find MSD kernel thread {_PROCESS_NAME!r}\")\n+\n+\n+# =====\n+def main() -> None:\n+    if len(sys.argv) != 2 or sys.argv[1] != \"unlock\":\n+        raise SystemExit(f\"Usage: {sys.argv[0]} [unlock]\")\n+    _unlock()\ndiff --git a/kvmd/helpers/unlock/__main__.py b/kvmd/helpers/unlock/__main__.py\nnew file mode 100644\nindex 00000000..3849d1b9\n--- /dev/null\n+++ b/kvmd/helpers/unlock/__main__.py\n@@ -0,0 +1,24 @@\n+# ========================================================================== #\n+#                                                                            #\n+#    KVMD - The main PiKVM daemon.                                           #\n+#                                                                            #\n+#    Copyright (C) 2018-2022  Maxim Devaev <mdevaev@gmail.com>               #\n+#                                                                            #\n+#    This program is free software: you can redistribute it and/or modify    #\n+#    it under the terms of the GNU General Public License as published by    #\n+#    the Free Software Foundation, either version 3 of the License, or       #\n+#    (at your option) any later version.                                     #\n+#                                                                            #\n+#    This program is distributed in the hope that it will be useful,         #\n+#    but WITHOUT ANY WARRANTY; without even the implied warranty of          #\n+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #\n+#    GNU General Public License for more details.                            #\n+#                                                                            #\n+#    You should have received a copy of the GNU General Public License       #\n+#    along with this program.  If not, see <https://www.gnu.org/licenses/>.  #\n+#                                                                            #\n+# ========================================================================== #\n+\n+\n+from . import main\n+main()\ndiff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py\nindex 409b899a..1342c6b4 100644\n--- a/kvmd/plugins/msd/otg/__init__.py\n+++ b/kvmd/plugins/msd/otg/__init__.py\n@@ -140,6 +140,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n         storage_path: str,\n \n         remount_cmd: List[str],\n+        unlock_cmd: List[str],\n \n         initial: Dict,\n \n@@ -154,6 +155,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n         self.__meta_path = os.path.join(self.__storage_path, \"meta\")\n \n         self.__remount_cmd = remount_cmd\n+        self.__unlock_cmd = unlock_cmd\n \n         self.__initial_image: str = initial[\"image\"]\n         self.__initial_cdrom: bool = initial[\"cdrom\"]\n@@ -178,10 +180,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n \n             \"storage\": Option(\"/var/lib/kvmd/msd\", type=valid_abs_dir, unpack_as=\"storage_path\"),\n \n-            \"remount_cmd\": Option([\n-                \"/usr/bin/sudo\", \"--non-interactive\",\n-                \"/usr/bin/kvmd-helper-otgmsd-remount\", \"{mode}\",\n-            ], type=valid_command),\n+            \"remount_cmd\": Option([*sudo, \"/usr/bin/kvmd-helper-otgmsd-remount\", \"{mode}\"], type=valid_command),\n+            \"unlock_cmd\":  Option([*sudo, \"/usr/bin/kvmd-helper-otgmsd-unlock\", \"unlock\"],  type=valid_command),\n \n             \"initial\": {\n                 \"image\": Option(\"\",    type=valid_printable_filename, if_empty=\"\"),\n@@ -241,6 +241,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n     async def reset(self) -> None:\n         async with self.__state.busy(check_online=False):\n             try:\n+                await self.__unlock_drive()\n                 self.__drive.set_image_path(\"\")\n                 self.__drive.set_rw_flag(False)\n                 self.__drive.set_cdrom_flag(False)\n@@ -290,12 +291,15 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n                 if not os.path.exists(self.__state.vd.image.path):\n                     raise MsdUnknownImageError()\n \n+                await self.__unlock_drive()\n                 self.__drive.set_cdrom_flag(self.__state.vd.cdrom)\n                 self.__drive.set_image_path(self.__state.vd.image.path)\n \n             else:\n                 if not (self.__state.vd.connected or self.__drive.get_image_path()):\n                     raise MsdDisconnectedError()\n+\n+                await self.__unlock_drive()\n                 self.__drive.set_image_path(\"\")\n \n             self.__state.vd.connected = connected\n@@ -474,6 +478,7 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n             if os.path.exists(path):\n                 logger.info(\"Setting up initial image %r ...\", self.__initial_image)\n                 try:\n+                    await self.__unlock_drive()\n                     self.__drive.set_cdrom_flag(self.__initial_cdrom)\n                     self.__drive.set_image_path(path)\n                 except Exception:\n@@ -541,4 +546,8 @@ class Plugin(BaseMsd):  # pylint: disable=too-many-instance-attributes\n \n     async def __remount_storage(self, rw: bool) -> None:\n         if not (await aiohelpers.remount(\"MSD\", self.__remount_cmd, rw)):\n-            raise MsdError(\"Can't execute remount helper\")\n+            pass\n+            #raise MsdError(\"Can't execute remount helper\")\n+\n+    async def __unlock_drive(self) -> None:\n+        await helpers.unlock_drive(self.__unlock_cmd)\n\\ No newline at end of file\ndiff --git a/kvmd/plugins/msd/otg/drive.py b/kvmd/plugins/msd/otg/drive.py\nindex 11af7f81..ee54e5e9 100644\n--- a/kvmd/plugins/msd/otg/drive.py\n+++ b/kvmd/plugins/msd/otg/drive.py\n@@ -53,10 +53,7 @@ class Drive:\n     # =====\n \n     def set_image_path(self, path: str) -> None:\n-        if path:\n-            self.__set_param(\"file\", path)\n-        else:\n-            self.__set_param(\"forced_eject\", \"\")\n+        self.__set_param(\"file\", path)\n \n     def get_image_path(self) -> str:\n         return self.__get_param(\"file\")\n-- \n2.34.1.windows.1\n\n"
  },
  {
    "path": "patches/custom/old-kernel-msd/v3.84-v3.92/0003-Allow-skip-some-features-unsupports-on-old-linux-ker.patch",
    "content": "From 840b9af2bc6f65851bd45eeb0cb4d629aed3423a Mon Sep 17 00:00:00 2001\nFrom: Frank Zhang <xe5700@outlook.com>\nDate: Thu, 19 May 2022 22:48:49 +0800\nSubject: [PATCH] Allow skip some features unsupports on old linux kernel\n\n---\n kvmd/apps/otg/__init__.py | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py\nindex d0ed0554..4aaeb3f8 100644\n--- a/kvmd/apps/otg/__init__.py\n+++ b/kvmd/apps/otg/__init__.py\n@@ -78,8 +78,11 @@ def _unlink(path: str, optional: bool=False) -> None:\n     os.unlink(path)\n \n \n-def _write(path: str, value: Union[str, int]) -> None:\n+def _write(path: str, value: Union[str, int], optional: bool=False) -> None:\n     get_logger().info(\"WRITE --- %s\", path)\n+    if optional and not os.access(path, os.F_OK):\n+        get_logger().info(\"SKIP ---- %s\", path)\n+        return\n     with open(path, \"w\") as param_file:\n         param_file.write(str(value))\n \n@@ -158,9 +161,9 @@ class _GadgetConfig:\n         func = f\"hid.usb{self.__hid_instance}\"\n         func_path = join(self.__gadget_path, \"functions\", func)\n         _mkdir(func_path)\n-        _write(join(func_path, \"no_out_endpoint\"), \"1\")\n+        _write(join(func_path, \"no_out_endpoint\"), \"1\", optional=True)\n         if remote_wakeup:\n-            _write(join(func_path, \"wakeup_on_write\"), \"1\")\n+            _write(join(func_path, \"wakeup_on_write\"), \"1\", optional=True)\n         _write(join(func_path, \"protocol\"), hid.protocol)\n         _write(join(func_path, \"subclass\"), hid.subclass)\n         _write(join(func_path, \"report_length\"), hid.report_length)\n-- \n2.34.1.windows.1\n\n"
  },
  {
    "path": "patches/disable_gpio/v3.47-v3.81/0001-Disable-GPIO-For-TV-Box.patch",
    "content": "From 368eaa19ef1cc187c8012c00b95ad97f260d61c3 Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github.com>\nDate: Sun, 23 Oct 2022 11:10:24 +0800\nSubject: [PATCH] Disable-GPIO-For-TV-Box\n\n---\n kvmd/apps/kvmd/ugpio.py | 10 ++--------\n 1 file changed, 2 insertions(+), 8 deletions(-)\n\ndiff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py\nindex a8fc9224..8bf17fff 100644\n--- a/kvmd/apps/kvmd/ugpio.py\n+++ b/kvmd/apps/kvmd/ugpio.py\n@@ -281,16 +281,10 @@ class UserGpio:\n             await self.__notifier.wait()\n \n     def sysprep(self) -> None:\n-        get_logger().info(\"Preparing User-GPIO drivers ...\")\n-        for (_, driver) in tools.sorted_kvs(self.__drivers):\n-            driver.prepare()\n+        pass\n \n     async def systask(self) -> None:\n-        get_logger(0).info(\"Running User-GPIO drivers ...\")\n-        await asyncio.gather(*[\n-            driver.run()\n-            for (_, driver) in tools.sorted_kvs(self.__drivers)\n-        ])\n+        await asyncio.Event().wait()\n \n     async def cleanup(self) -> None:\n         for driver in self.__drivers.values():\n-- \n2.34.1.windows.1\n\n"
  },
  {
    "path": "patches/disable_gpio/v3.82-v3.83/0001-Disable-GPIO-For-TV-Box.patch",
    "content": "From 960b30ec20b370269ee43282f1867861e714e33f Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github.com>\nDate: Thu, 19 May 2022 23:01:20 +0800\nSubject: [PATCH] Disable-GPIO-For-TV-Box\n\n---\n kvmd/apps/kvmd/ugpio.py | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py\nindex cea60bb9..9328496a 100644\n--- a/kvmd/apps/kvmd/ugpio.py\n+++ b/kvmd/apps/kvmd/ugpio.py\n@@ -282,15 +282,15 @@ class UserGpio:\n \n     def sysprep(self) -> None:\n         get_logger(0).info(\"Preparing User-GPIO drivers ...\")\n-        for (_, driver) in tools.sorted_kvs(self.__drivers):\n-            driver.prepare()\n+#        for (_, driver) in tools.sorted_kvs(self.__drivers):\n+#            driver.prepare()\n \n     async def systask(self) -> None:\n         get_logger(0).info(\"Running User-GPIO drivers ...\")\n-        await asyncio.gather(*[\n-            driver.run()\n-            for (_, driver) in tools.sorted_kvs(self.__drivers)\n-        ])\n+#        await asyncio.gather(*[\n+#            driver.run()\n+#            for (_, driver) in tools.sorted_kvs(self.__drivers)\n+#        ])\n \n     async def cleanup(self) -> None:\n         for driver in self.__drivers.values():\n-- \n2.34.1.windows.1\n\n"
  },
  {
    "path": "patches/disable_gpio/v3.84-v3.134/0001-Disable-GPIO-For-TV-Box.patch",
    "content": "From 960b30ec20b370269ee43282f1867861e714e33f Mon Sep 17 00:00:00 2001\nFrom: xe5700 <9338143+xe5700@users.noreply.github.com>\nDate: Thu, 19 May 2022 23:01:20 +0800\nSubject: [PATCH] Disable-GPIO-For-TV-Box\n\n---\n kvmd/apps/kvmd/ugpio.py | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py\nindex cea60bb9..9328496a 100644\n--- a/kvmd/apps/kvmd/ugpio.py\n+++ b/kvmd/apps/kvmd/ugpio.py\n@@ -282,15 +282,16 @@ class UserGpio:\n \n     def sysprep(self) -> None:\n         get_logger(0).info(\"Preparing User-GPIO drivers ...\")\n-        for (_, driver) in tools.sorted_kvs(self.__drivers):\n-            driver.prepare()\n+#        for (_, driver) in tools.sorted_kvs(self.__drivers):\n+#            driver.prepare()\n \n     async def systask(self) -> None:\n         get_logger(0).info(\"Running User-GPIO drivers ...\")\n-        await asyncio.gather(*[\n-            driver.run()\n-            for (_, driver) in tools.sorted_kvs(self.__drivers)\n-        ])\n+#        await asyncio.gather(*[\n+#            driver.run()\n+#            for (_, driver) in tools.sorted_kvs(self.__drivers)\n+#        ])\n+        await asyncio.Event().wait()\n \n     async def cleanup(self) -> None:\n         for driver in self.__drivers.values():\n-- \n2.34.1.windows.1\n\n"
  },
  {
    "path": "patches/genernal/v3.84-v3.92/0001-Allow-skip-some-features-unsupports-on-old-linux-ker.patch",
    "content": "From 840b9af2bc6f65851bd45eeb0cb4d629aed3423a Mon Sep 17 00:00:00 2001\nFrom: Frank Zhang <xe5700@outlook.com>\nDate: Thu, 19 May 2022 22:48:49 +0800\nSubject: [PATCH] Allow skip some features unsupports on old linux kernel\n\n---\n kvmd/apps/otg/__init__.py | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/kvmd/apps/otg/__init__.py b/kvmd/apps/otg/__init__.py\nindex d0ed0554..4aaeb3f8 100644\n--- a/kvmd/apps/otg/__init__.py\n+++ b/kvmd/apps/otg/__init__.py\n@@ -78,8 +78,11 @@ def _unlink(path: str, optional: bool=False) -> None:\n     os.unlink(path)\n \n \n-def _write(path: str, value: Union[str, int]) -> None:\n+def _write(path: str, value: Union[str, int], optional: bool=False) -> None:\n     get_logger().info(\"WRITE --- %s\", path)\n+    if optional and not os.access(path, os.F_OK):\n+        get_logger().info(\"SKIP ---- %s\", path)\n+        return\n     with open(path, \"w\") as param_file:\n         param_file.write(str(value))\n \n@@ -158,9 +161,9 @@ class _GadgetConfig:\n         func = f\"hid.usb{self.__hid_instance}\"\n         func_path = join(self.__gadget_path, \"functions\", func)\n         _mkdir(func_path)\n-        _write(join(func_path, \"no_out_endpoint\"), \"1\")\n+        _write(join(func_path, \"no_out_endpoint\"), \"1\", optional=True)\n         if remote_wakeup:\n-            _write(join(func_path, \"wakeup_on_write\"), \"1\")\n+            _write(join(func_path, \"wakeup_on_write\"), \"1\", optional=True)\n         _write(join(func_path, \"protocol\"), hid.protocol)\n         _write(join(func_path, \"subclass\"), hid.subclass)\n         _write(join(func_path, \"report_length\"), hid.report_length)\n-- \n2.34.1.windows.1\n\n"
  }
]