mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-20 05:10:24 +08:00
Compare commits
505 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a6e7edcd | ||
|
|
50c3e6a32a | ||
|
|
c8305cc65d | ||
|
|
aae4e936db | ||
|
|
45a04f7570 | ||
|
|
53ba69f4aa | ||
|
|
53229a9055 | ||
|
|
f97df0d830 | ||
|
|
8ed5e4abc3 | ||
|
|
1e727ddc1b | ||
|
|
da84a6d09f | ||
|
|
9c35c68eda | ||
|
|
651f9a4f4e | ||
|
|
7777f5e490 | ||
|
|
3ab5e2b431 | ||
|
|
65874c6b43 | ||
|
|
67b943c151 | ||
|
|
593de19df5 | ||
|
|
5296e61281 | ||
|
|
1729badc55 | ||
|
|
9373790f37 | ||
|
|
edb9112435 | ||
|
|
0328163a9e | ||
|
|
0c9d94e1c5 | ||
|
|
d4bd94cb8a | ||
|
|
e7c891353b | ||
|
|
3f8a9e3b2c | ||
|
|
4d4f528178 | ||
|
|
201c615ce2 | ||
|
|
8cc9e22c91 | ||
|
|
892d2b6f41 | ||
|
|
30dd4290ab | ||
|
|
f900c4bb5a | ||
|
|
6299f04127 | ||
|
|
08551e737e | ||
|
|
bbef7bb5c4 | ||
|
|
b94cc14e2a | ||
|
|
ecc27c2be7 | ||
|
|
ccdfd52b75 | ||
|
|
7ccac8bc9e | ||
|
|
6f4cf12c69 | ||
|
|
916a0483b4 | ||
|
|
c262db4a18 | ||
|
|
0b4d83dc93 | ||
|
|
16878dc7ff | ||
|
|
f80e063495 | ||
|
|
d411affca4 | ||
|
|
04b13b1215 | ||
|
|
bdd97c5ea3 | ||
|
|
fafd790b3e | ||
|
|
432c61fd91 | ||
|
|
10fbd0611f | ||
|
|
e87942a5a9 | ||
|
|
19d1c52ac4 | ||
|
|
2c056ca3e3 | ||
|
|
caf3533872 | ||
|
|
187c713424 | ||
|
|
c8d1dcca30 | ||
|
|
0809ab4878 | ||
|
|
678744ce91 | ||
|
|
bd5e17da4b | ||
|
|
fd7bcbd88a | ||
|
|
cfbb6f1be7 | ||
|
|
4a0029bab7 | ||
|
|
6002dfd9c7 | ||
|
|
42efb73c98 | ||
|
|
9b5b6f6152 | ||
|
|
dc7f38a1b6 | ||
|
|
e5cee0ec5e | ||
|
|
776b93cab6 | ||
|
|
43eada0fef | ||
|
|
ec994f4518 | ||
|
|
70c5b9fc4b | ||
|
|
296b1f3bda | ||
|
|
263e252db7 | ||
|
|
9b433a909a | ||
|
|
0cf6f183c8 | ||
|
|
cf6addeb0f | ||
|
|
d57c3c66cd | ||
|
|
49638ed896 | ||
|
|
fbf5e52b0f | ||
|
|
6bdda82822 | ||
|
|
1142cc9d65 | ||
|
|
1b5df61f61 | ||
|
|
b4b1fb8d9a | ||
|
|
f22e05ac88 | ||
|
|
6661efe61d | ||
|
|
a68f860b8e | ||
|
|
e8498858bb | ||
|
|
8b5c87c893 | ||
|
|
824955fb83 | ||
|
|
8560a46f17 | ||
|
|
d4b4cdc492 | ||
|
|
687cea3658 | ||
|
|
12c7566581 | ||
|
|
0e3c821863 | ||
|
|
a5e226e168 | ||
|
|
fe1f821715 | ||
|
|
b28275b042 | ||
|
|
4e4ea9fcea | ||
|
|
735c2e6395 | ||
|
|
f25e5ef2b4 | ||
|
|
0d8b7fd3aa | ||
|
|
91312dd4be | ||
|
|
5bff6cadd4 | ||
|
|
5d2c275f13 | ||
|
|
2a928a4a38 | ||
|
|
37e8aa2cec | ||
|
|
54cb364c2e | ||
|
|
007371d30b | ||
|
|
517e79fd65 | ||
|
|
86f73844dd | ||
|
|
e04381555c | ||
|
|
82f45cd1fd | ||
|
|
2c36d86075 | ||
|
|
6df1e55ffc | ||
|
|
659e8f9169 | ||
|
|
38981a4108 | ||
|
|
10fb78abe6 | ||
|
|
97ea7de7d3 | ||
|
|
56d0d3aa8a | ||
|
|
92f635cdf8 | ||
|
|
4a2c642c49 | ||
|
|
1642ce73a0 | ||
|
|
64c83be0a4 | ||
|
|
6f971a7c54 | ||
|
|
1e3c90e94a | ||
|
|
09884c54c0 | ||
|
|
cd2a801eae | ||
|
|
183a6c2553 | ||
|
|
310b23edad | ||
|
|
625b2aa970 | ||
|
|
741e94f2fd | ||
|
|
ce3af61510 | ||
|
|
bf8761baa9 | ||
|
|
8e2bc47cd3 | ||
|
|
65d1cfd827 | ||
|
|
d7963f3271 | ||
|
|
c3eed7c497 | ||
|
|
70ca478a78 | ||
|
|
49fb9a6f92 | ||
|
|
bd9f5bf9ee | ||
|
|
193eaa48c8 | ||
|
|
47614a5724 | ||
|
|
791e047a6b | ||
|
|
818ff6321e | ||
|
|
53980c0e68 | ||
|
|
1195a9e3be | ||
|
|
18122eff82 | ||
|
|
6910cebc00 | ||
|
|
3b39fcefd5 | ||
|
|
3f309077f8 | ||
|
|
ed447a7cc2 | ||
|
|
93d60ac932 | ||
|
|
39c13d31f3 | ||
|
|
8b97eed743 | ||
|
|
191eb4b430 | ||
|
|
ac240e141b | ||
|
|
af51d79502 | ||
|
|
c551b9ff57 | ||
|
|
df8898684f | ||
|
|
5273199e0b | ||
|
|
eb0fb04b72 | ||
|
|
cfdf225d10 | ||
|
|
76ca81bbfd | ||
|
|
ed7b2e5b33 | ||
|
|
c80532fb73 | ||
|
|
9875d4686f | ||
|
|
1b822c19ff | ||
|
|
1356187771 | ||
|
|
8fb4bc6be7 | ||
|
|
09eb5ebc2f | ||
|
|
bc880009c1 | ||
|
|
3268c62bf3 | ||
|
|
21c83e6fca | ||
|
|
8f19d40566 | ||
|
|
32425c1903 | ||
|
|
6005ed38b9 | ||
|
|
bb0656c0cb | ||
|
|
8d7f89e8f1 | ||
|
|
a65cd7feb5 | ||
|
|
d630e24aa0 | ||
|
|
46ef5fd46b | ||
|
|
c8cf06ee8c | ||
|
|
79d4d99f37 | ||
|
|
0437f487b5 | ||
|
|
59eff99dcc | ||
|
|
334b9f7d7b | ||
|
|
6dea594380 | ||
|
|
fd5196a2ce | ||
|
|
b7715b731e | ||
|
|
7d7edb1c03 | ||
|
|
69d254d80e | ||
|
|
e011a98288 | ||
|
|
63a1933342 | ||
|
|
ebbd55ee17 | ||
|
|
a92a6f2811 | ||
|
|
3d58f6dd21 | ||
|
|
50022e7353 | ||
|
|
1624b0cbf8 | ||
|
|
fa2630250c | ||
|
|
7e185d2ad9 | ||
|
|
16a1dbd9ed | ||
|
|
e66edd45e2 | ||
|
|
86774dfa4e | ||
|
|
866eb2a2c6 | ||
|
|
1984a245e9 | ||
|
|
04209e2a6b | ||
|
|
71617cc62a | ||
|
|
45ff6cb7c7 | ||
|
|
ff4f04d936 | ||
|
|
49695247a5 | ||
|
|
87f78990a5 | ||
|
|
b86f4cd437 | ||
|
|
413fce72ec | ||
|
|
842238009e | ||
|
|
2c4f7f1458 | ||
|
|
ba5df47c97 | ||
|
|
20a7206b0f | ||
|
|
70d134a2ff | ||
|
|
8391b7a467 | ||
|
|
7116da2511 | ||
|
|
a59fe84e26 | ||
|
|
2bdd349fbf | ||
|
|
48fe790897 | ||
|
|
e375e41fb6 | ||
|
|
5014e82177 | ||
|
|
1566f026de | ||
|
|
878bc03a80 | ||
|
|
41e6502904 | ||
|
|
ec9c12ffcc | ||
|
|
9fdb861048 | ||
|
|
97dbc17771 | ||
|
|
e7d4f7fe8c | ||
|
|
1cb5c11239 | ||
|
|
72ef037959 | ||
|
|
182aa0e374 | ||
|
|
876ff22bd8 | ||
|
|
a01ef562a1 | ||
|
|
362b88e92c | ||
|
|
7f6b0a814d | ||
|
|
b3d1291039 | ||
|
|
6a08fab818 | ||
|
|
02740aef37 | ||
|
|
dd3f4c16e3 | ||
|
|
30a82efea4 | ||
|
|
ccbe455ada | ||
|
|
1d0f441cc4 | ||
|
|
8c7f86ac83 | ||
|
|
4b67208cab | ||
|
|
a3e398a1d5 | ||
|
|
c66c97afd4 | ||
|
|
83c352a900 | ||
|
|
de4f1903aa | ||
|
|
800d2724b8 | ||
|
|
dc1c6c0fcf | ||
|
|
4c9c98c6ab | ||
|
|
6ffaa8d6bd | ||
|
|
97b405297b | ||
|
|
302e7c2877 | ||
|
|
75a4aa0736 | ||
|
|
c3dc5b9553 | ||
|
|
79b7788480 | ||
|
|
05519f403f | ||
|
|
c49d712f17 | ||
|
|
375a345820 | ||
|
|
a7c3cdc1ea | ||
|
|
abbd65a9a0 | ||
|
|
ba28f03575 | ||
|
|
ad019f8476 | ||
|
|
0afc81f56c | ||
|
|
84ec99b332 | ||
|
|
54f6d93f63 | ||
|
|
94fe2226f1 | ||
|
|
beb5d541b0 | ||
|
|
1c179da857 | ||
|
|
8c8bf35d0b | ||
|
|
c8df621172 | ||
|
|
1899902860 | ||
|
|
4800f9e486 | ||
|
|
73238e18e9 | ||
|
|
f032b8c798 | ||
|
|
c711683c63 | ||
|
|
06a64725be | ||
|
|
94897ab8c9 | ||
|
|
c6a5ffa0cf | ||
|
|
9da06d3f58 | ||
|
|
b51ea5e374 | ||
|
|
13fff8a88c | ||
|
|
9436bb029d | ||
|
|
7b3335ea94 | ||
|
|
430a3848f7 | ||
|
|
3b5e539012 | ||
|
|
d1a12f1f6a | ||
|
|
697ef549b9 | ||
|
|
4039ae0483 | ||
|
|
06812231c1 | ||
|
|
15dbe6265f | ||
|
|
b2c8ed6818 | ||
|
|
2acd613a38 | ||
|
|
0202a3c2d1 | ||
|
|
be3e97178d | ||
|
|
dafc8e3941 | ||
|
|
6dcc41601e | ||
|
|
b9af5f8825 | ||
|
|
00ed5197b0 | ||
|
|
b2c5305564 | ||
|
|
e9443119ec | ||
|
|
ab5608e3e0 | ||
|
|
78557b0c47 | ||
|
|
f042ed38e0 | ||
|
|
e1e3605630 | ||
|
|
3f3a834c0c | ||
|
|
8631ee8555 | ||
|
|
da4da975ef | ||
|
|
b6c73aceb7 | ||
|
|
d3549ab52b | ||
|
|
965e649f8c | ||
|
|
b49107ff6c | ||
|
|
e9cbf04ba5 | ||
|
|
3cf543a13e | ||
|
|
4d89d6b222 | ||
|
|
e7c06643b4 | ||
|
|
72c9ae3aa0 | ||
|
|
05bced1461 | ||
|
|
464672d1a0 | ||
|
|
1061a6ba01 | ||
|
|
be6843a486 | ||
|
|
f5de6a0f2e | ||
|
|
21b7429ffe | ||
|
|
9ef1a3665a | ||
|
|
10a7ca978b | ||
|
|
4488365dfb | ||
|
|
5a61ddecd3 | ||
|
|
a12163a797 | ||
|
|
43e6cd3e26 | ||
|
|
57518468ad | ||
|
|
5973b9e773 | ||
|
|
e120b50f50 | ||
|
|
f1256ee74a | ||
|
|
9aef70c43f | ||
|
|
f9584929e3 | ||
|
|
7aa963330c | ||
|
|
5d8633556e | ||
|
|
ebda7ea03d | ||
|
|
fed3bf1efd | ||
|
|
d52bb34bb9 | ||
|
|
6c5f0bf09f | ||
|
|
aae529f40b | ||
|
|
253231adac | ||
|
|
e491057891 | ||
|
|
3b5d62dd98 | ||
|
|
38346bece1 | ||
|
|
647d3f3961 | ||
|
|
287244d376 | ||
|
|
56438a372e | ||
|
|
ab08d823c4 | ||
|
|
5db37797ea | ||
|
|
eda7ab3a49 | ||
|
|
af2ee26a2f | ||
|
|
596334735e | ||
|
|
c8385213cc | ||
|
|
c009985247 | ||
|
|
7caa695d79 | ||
|
|
630610bc53 | ||
|
|
e0bbf6968e | ||
|
|
ada1c39eef | ||
|
|
e014cbcedf | ||
|
|
adbd4f242b | ||
|
|
2649a2fa01 | ||
|
|
8cca5a8cc7 | ||
|
|
70452f048b | ||
|
|
be21a420a0 | ||
|
|
e337e8d45c | ||
|
|
8a09505baf | ||
|
|
870af902a1 | ||
|
|
0dd117711d | ||
|
|
ed68449274 | ||
|
|
85a2f2367d | ||
|
|
7fd4dae3c6 | ||
|
|
a32dcd2e00 | ||
|
|
0cf5f8de9e | ||
|
|
7394588279 | ||
|
|
666f0b694a | ||
|
|
b8ddf7c2da | ||
|
|
1b9b27660a | ||
|
|
7c453b8b49 | ||
|
|
5450d7297c | ||
|
|
506d8a4a64 | ||
|
|
8929d0f311 | ||
|
|
d25e43c934 | ||
|
|
3cbeabe2e8 | ||
|
|
eec64ef57c | ||
|
|
baa0f7e226 | ||
|
|
3ec872878e | ||
|
|
6928fab16c | ||
|
|
8fdb7d7cd6 | ||
|
|
433232c845 | ||
|
|
b419641251 | ||
|
|
50819d0a35 | ||
|
|
a37b818039 | ||
|
|
8a81996e52 | ||
|
|
69cb9ac950 | ||
|
|
30378211b5 | ||
|
|
e9e7f9bd05 | ||
|
|
72dce4de89 | ||
|
|
f1503d69e0 | ||
|
|
de5cb73b93 | ||
|
|
0751b519c2 | ||
|
|
0010dd1d11 | ||
|
|
7ef2e16b51 | ||
|
|
1a13760df0 | ||
|
|
d93639ba8d | ||
|
|
1e277c0f06 | ||
|
|
95597b15e4 | ||
|
|
6fbfc2b343 | ||
|
|
b893f27285 | ||
|
|
28167c4b45 | ||
|
|
5aef0a2193 | ||
|
|
0fd1174bc5 | ||
|
|
d4fb640418 | ||
|
|
d6b61cb407 | ||
|
|
8192b1fa95 | ||
|
|
deba110cdf | ||
|
|
936cc21c40 | ||
|
|
47778bc48c | ||
|
|
c02bc53bc4 | ||
|
|
546ac24b93 | ||
|
|
2195acf2ff | ||
|
|
60f413c1f4 | ||
|
|
a84242c9bc | ||
|
|
efa865ec9c | ||
|
|
399712c684 | ||
|
|
1ebc08eae8 | ||
|
|
684b9f629e | ||
|
|
76d70d0838 | ||
|
|
a26aee3543 | ||
|
|
0e4a70e7b9 | ||
|
|
cda32a083f | ||
|
|
11d8f26874 | ||
|
|
2929a925a2 | ||
|
|
b67a232584 | ||
|
|
90d8e745e3 | ||
|
|
3852d0a456 | ||
|
|
f5bebbc43f | ||
|
|
6707cb9932 | ||
|
|
87c887a62b | ||
|
|
40505e7e00 | ||
|
|
c1f408ea1a | ||
|
|
5b0ca351d7 | ||
|
|
b6869cfbec | ||
|
|
1e11678260 | ||
|
|
8c0953aafc | ||
|
|
073f67ca1b | ||
|
|
cb5c1e9e6d | ||
|
|
8ce27dca3f | ||
|
|
f4ba4210e1 | ||
|
|
4e1d9815cd | ||
|
|
8209ee2eb0 | ||
|
|
5ed368769c | ||
|
|
1217144ecd | ||
|
|
842ddc91a1 | ||
|
|
7a53f14456 | ||
|
|
45270a09d7 | ||
|
|
f03ac695bd | ||
|
|
b3e836e553 | ||
|
|
c57334f214 | ||
|
|
b779c18530 | ||
|
|
6ccd91a8d1 | ||
|
|
bd127c3fd3 | ||
|
|
4bc2ca3c90 | ||
|
|
445e2e04e2 | ||
|
|
489601bb96 | ||
|
|
56da910ebe | ||
|
|
40393acf67 | ||
|
|
2123799e51 | ||
|
|
0bb35806ff | ||
|
|
bbbc908af1 | ||
|
|
8113c5748b | ||
|
|
aa1ca3b329 | ||
|
|
508d5fe606 | ||
|
|
bc22a28022 | ||
|
|
80aa9de4cc | ||
|
|
572a75d27b | ||
|
|
864a2af45e | ||
|
|
5f26fa4072 | ||
|
|
af9023e8aa | ||
|
|
5c3ac4c9c1 | ||
|
|
fb9d860cf2 | ||
|
|
5045d8b3d7 | ||
|
|
cc66fbf1df | ||
|
|
9dc2af0356 | ||
|
|
99fcbdda05 | ||
|
|
308911191a | ||
|
|
0c213add4a | ||
|
|
3837e1a1c8 | ||
|
|
8569ed406a | ||
|
|
4772c2b6c3 | ||
|
|
e6b775089f | ||
|
|
721a80ef03 | ||
|
|
a55948bf8e | ||
|
|
39422f37ac | ||
|
|
06b69d3dde | ||
|
|
c9405efa05 | ||
|
|
abedace4b3 |
@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 4.3
|
current_version = 4.94
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
36
.github/ISSUE_TEMPLATE/bug-反馈.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug-反馈.md
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: BUG 反馈
|
||||||
|
about: 反馈你所遇到的软件 BUG 或其他错误
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: BUG
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Bug 反馈**
|
||||||
|
|
||||||
|
**问题描述**
|
||||||
|
请清晰描述您遇到的问题。例如:软件无法启动、特定功能报错或表现异常等。
|
||||||
|
|
||||||
|
**复现步骤**
|
||||||
|
请提供可复现此问题的详细步骤:
|
||||||
|
1. 前往 '...'
|
||||||
|
2. 点击 '....'
|
||||||
|
3. 滚动到 '....'
|
||||||
|
4. 发现错误
|
||||||
|
|
||||||
|
**日志信息**
|
||||||
|
如果程序崩溃或报错,请在此处粘贴相关的日志。
|
||||||
|
- **整合包镜像**: `systemctl status kvmd` 或 `journalctl -xeu kvmd`
|
||||||
|
- **Docker 镜像**: `docker logs kvmd`
|
||||||
|
|
||||||
|
**系统环境**
|
||||||
|
- **运行方式**: (例如:整合包镜像 / Docker)
|
||||||
|
- **镜像版本**: (Docker 镜像请提供版本号)
|
||||||
|
- **操作系统**: (例如:Debian 12)
|
||||||
|
|
||||||
|
**尝试过的解决方法**
|
||||||
|
请简要描述您为解决此问题已尝试过的方法及其结果。如果未尝试,可留空。
|
||||||
|
|
||||||
|
**补充信息**
|
||||||
|
可以附加截图、录屏或其他有助于理解问题的信息。
|
||||||
25
.github/ISSUE_TEMPLATE/功能请求与设备适配.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/功能请求与设备适配.md
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: 功能请求与设备适配
|
||||||
|
about: 请求新的功能或适配新的平台
|
||||||
|
title: "[功能/适配]"
|
||||||
|
labels: 特性
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**功能描述**
|
||||||
|
请详细描述您期望的新功能应该是什么样子。
|
||||||
|
- **对于新功能**:它应该如何工作?有哪些关键特性?
|
||||||
|
- **对于新平台适配**:请提供该平台的具体信息(如设备型号、系统版本、相关链接等)。
|
||||||
|
|
||||||
|
**期望的效果**
|
||||||
|
当该功能实现或平台适配完成后,您期望达到怎样的理想效果?可以像下面这样列出关键点:
|
||||||
|
- [ ] 用户可以...
|
||||||
|
- [ ] 系统能够...
|
||||||
|
- [ ] 解决了之前的...问题
|
||||||
|
|
||||||
|
**我能提供的帮助**
|
||||||
|
为了让这个想法更快成为现实,您可以提供哪些帮助?没有则填写无。
|
||||||
|
- [ ] 我可以参与后续的功能测试
|
||||||
|
- [ ] 我可以提供(临时的)远程调试环境(如 SSH、远程桌面)
|
||||||
|
- [ ] 其他:...
|
||||||
23
.github/workflows/arduino-hid.yml
vendored
23
.github/workflows/arduino-hid.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
name: Arduino HID CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
container:
|
|
||||||
image: python
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Prepare platformio
|
|
||||||
run: pip install platformio
|
|
||||||
|
|
||||||
- name: Build all
|
|
||||||
run: make -C hid/arduino _build_all
|
|
||||||
210
.github/workflows/build_img.yaml
vendored
Normal file
210
.github/workflows/build_img.yaml
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
name: Build One-KVM Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
device_target:
|
||||||
|
description: 'Target device to build'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- onecloud
|
||||||
|
- onecloud-pro
|
||||||
|
- cumebox2
|
||||||
|
- chainedbox
|
||||||
|
- vm
|
||||||
|
- e900v22c
|
||||||
|
- octopus-flanet
|
||||||
|
- orangepi-zero
|
||||||
|
- oec-turbo
|
||||||
|
- all
|
||||||
|
create_release:
|
||||||
|
description: 'Create GitHub Release'
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
release_name:
|
||||||
|
description: 'Custom release name (optional)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILD_DATE: ""
|
||||||
|
GIT_SHA: ""
|
||||||
|
RELEASE_TAG: ""
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
container:
|
||||||
|
image: node:18
|
||||||
|
options: --user root --privileged
|
||||||
|
env:
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
volumes:
|
||||||
|
- /dev:/dev
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Inject TURN config (optional)
|
||||||
|
if: ${{ env.TURN_HOST != '' }}
|
||||||
|
run: |
|
||||||
|
mkdir -p configs/kvmd/override.d
|
||||||
|
cat > configs/kvmd/override.d/turn.yaml <<EOF
|
||||||
|
janus:
|
||||||
|
stun:
|
||||||
|
host: ${TURN_HOST}
|
||||||
|
port: ${TURN_PORT}
|
||||||
|
local_ice_servers:
|
||||||
|
- urls:
|
||||||
|
- "stun:${TURN_HOST}:${TURN_PORT}"
|
||||||
|
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
|
||||||
|
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
|
||||||
|
username: "${TURN_USER}"
|
||||||
|
credential: "${TURN_PASS}"
|
||||||
|
EOF
|
||||||
|
env:
|
||||||
|
TURN_HOST: ${{ secrets.TURN_HOST }}
|
||||||
|
TURN_PORT: ${{ secrets.TURN_PORT }}
|
||||||
|
TURN_USER: ${{ secrets.TURN_USER }}
|
||||||
|
TURN_PASS: ${{ secrets.TURN_PASS }}
|
||||||
|
|
||||||
|
- name: Set build environment
|
||||||
|
id: build_env
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
BUILD_DATE=$(date +%y%m%d-%H%M)
|
||||||
|
# 使用 GitHub 提供的环境变量避免 Git 权限问题
|
||||||
|
GIT_SHA="${GITHUB_SHA:0:7}"
|
||||||
|
GIT_BRANCH="${GITHUB_REF_NAME}"
|
||||||
|
|
||||||
|
echo "BUILD_DATE=$BUILD_DATE" >> $GITHUB_ENV
|
||||||
|
echo "GIT_SHA=$GIT_SHA" >> $GITHUB_ENV
|
||||||
|
echo "GIT_BRANCH=$GIT_BRANCH" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# 生成唯一但不创建新分支的标识符
|
||||||
|
RELEASE_TAG="build-$BUILD_DATE-${{ github.event.inputs.device_target }}-$GIT_SHA"
|
||||||
|
echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
echo "Build environment:"
|
||||||
|
echo "- Date: $BUILD_DATE"
|
||||||
|
echo "- Git SHA: $GIT_SHA"
|
||||||
|
echo "- Git Branch: $GIT_BRANCH"
|
||||||
|
echo "- Release Tag: $RELEASE_TAG"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
sudo tzdata docker.io qemu-utils qemu-user-static binfmt-support parted e2fsprogs \
|
||||||
|
curl tar python3 python3-pip rsync git android-sdk-libsparse-utils coreutils zerofree wget \
|
||||||
|
file tree
|
||||||
|
apt-get clean
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime
|
||||||
|
echo $TZ > /etc/timezone
|
||||||
|
update-binfmts --enable
|
||||||
|
env:
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
id: build
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
echo "=== Build Configuration ==="
|
||||||
|
echo "Target: ${{ github.event.inputs.device_target }}"
|
||||||
|
echo "Build Date: $BUILD_DATE"
|
||||||
|
echo "Git SHA: $GIT_SHA"
|
||||||
|
echo "Git Branch: $GIT_BRANCH"
|
||||||
|
echo "Output Directory: ${{ github.workspace }}/output"
|
||||||
|
echo "=========================="
|
||||||
|
|
||||||
|
mkdir -p "${{ github.workspace }}/output"
|
||||||
|
chmod +x build/build_img.sh
|
||||||
|
|
||||||
|
echo "Starting build process..."
|
||||||
|
if bash build/build_img.sh ${{ github.event.inputs.device_target }}; then
|
||||||
|
echo "BUILD_SUCCESS=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Build completed successfully!"
|
||||||
|
else
|
||||||
|
echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Build failed!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
CI_PROJECT_DIR: ${{ github.workspace }}
|
||||||
|
GITHUB_ACTIONS: true
|
||||||
|
OUTPUTDIR: ${{ github.workspace }}/output
|
||||||
|
|
||||||
|
- name: Collect build artifacts
|
||||||
|
id: artifacts
|
||||||
|
run: |
|
||||||
|
cd "${{ github.workspace }}/output"
|
||||||
|
|
||||||
|
echo "=== Build Artifacts ==="
|
||||||
|
if [ -d "${{ github.workspace }}/output" ]; then
|
||||||
|
find . -name "*.xz" | head -20
|
||||||
|
|
||||||
|
# 统计xz文件信息
|
||||||
|
ARTIFACT_COUNT=$(find . -name "*.xz" | wc -l)
|
||||||
|
TOTAL_SIZE=$(du -sh . | cut -f1)
|
||||||
|
|
||||||
|
echo "ARTIFACT_COUNT=$ARTIFACT_COUNT" >> $GITHUB_OUTPUT
|
||||||
|
echo "TOTAL_SIZE=$TOTAL_SIZE" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "No output directory found!"
|
||||||
|
echo "ARTIFACT_COUNT=0" >> $GITHUB_OUTPUT
|
||||||
|
echo "TOTAL_SIZE=0" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
echo "======================"
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
if: steps.build.outputs.BUILD_SUCCESS == 'true' && github.event.inputs.create_release == 'true'
|
||||||
|
id: release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.RELEASE_TAG }}
|
||||||
|
name: ${{ github.event.inputs.release_name || format('One-KVM {0} 构建镜像 ({1})', github.event.inputs.device_target, env.BUILD_DATE) }}
|
||||||
|
body: |
|
||||||
|
## 📦 GitHub Actions 镜像构建
|
||||||
|
|
||||||
|
### 构建信息
|
||||||
|
- **目标设备**: `${{ github.event.inputs.device_target }}`
|
||||||
|
- **构建时间**: `${{ env.BUILD_DATE }}`
|
||||||
|
- **Git 提交**: `${{ env.GIT_SHA }}` (分支: `${{ env.GIT_BRANCH }}`)
|
||||||
|
- **构建环境**: GitHub Actions (Ubuntu 22.04)
|
||||||
|
- **工作流ID**: `${{ github.run_id }}`
|
||||||
|
|
||||||
|
files: ${{ github.workspace }}/output/*.xz
|
||||||
|
prerelease: true
|
||||||
|
make_latest: false
|
||||||
|
generate_release_notes: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "## 📋 构建摘要" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| 项目 | 值 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|-----|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **目标设备** | \`${{ github.event.inputs.device_target }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **构建时间** | \`${{ env.BUILD_DATE }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Git SHA** | \`${{ env.GIT_SHA }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Git 分支** | \`${{ env.GIT_BRANCH }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **构建状态** | ${{ steps.build.outputs.BUILD_SUCCESS == 'true' && '✅ 成功' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
if [ "${{ steps.build.outputs.BUILD_SUCCESS }}" = "true" ]; then
|
||||||
|
echo "| **构建产物** | ${{ steps.artifacts.outputs.ARTIFACT_COUNT || '0' }} 个文件 (${{ steps.artifacts.outputs.TOTAL_SIZE || '0' }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ "${{ github.event.inputs.create_release }}" = "true" ]; then
|
||||||
|
echo "| **Release** | [${{ env.RELEASE_TAG }}](${{ steps.release.outputs.url }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
fi
|
||||||
240
.github/workflows/docker-build.yaml
vendored
Normal file
240
.github/workflows/docker-build.yaml
vendored
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
build_type:
|
||||||
|
description: 'Build type'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- stage-0
|
||||||
|
- dev
|
||||||
|
- release
|
||||||
|
version:
|
||||||
|
description: 'Version tag (for main image)'
|
||||||
|
required: false
|
||||||
|
default: 'latest'
|
||||||
|
type: string
|
||||||
|
platforms:
|
||||||
|
description: 'Target platforms'
|
||||||
|
required: false
|
||||||
|
default: 'linux/amd64,linux/arm64,linux/arm/v7'
|
||||||
|
type: string
|
||||||
|
enable_aliyun:
|
||||||
|
description: 'Push to Aliyun Registry'
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKERHUB_REGISTRY: docker.io
|
||||||
|
ALIYUN_REGISTRY: registry.cn-hangzhou.aliyuncs.com
|
||||||
|
STAGE0_IMAGE: kvmd-stage-0
|
||||||
|
MAIN_IMAGE: kvmd
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-stage-0:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: github.event.inputs.build_type == 'stage-0'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Inject TURN config (optional)
|
||||||
|
if: ${{ env.TURN_HOST != '' }}
|
||||||
|
run: |
|
||||||
|
mkdir -p configs/kvmd/override.d
|
||||||
|
cat > configs/kvmd/override.d/turn.yaml <<EOF
|
||||||
|
janus:
|
||||||
|
stun:
|
||||||
|
host: ${TURN_HOST}
|
||||||
|
port: ${TURN_PORT}
|
||||||
|
local_ice_servers:
|
||||||
|
- urls:
|
||||||
|
- "stun:${TURN_HOST}:${TURN_PORT}"
|
||||||
|
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
|
||||||
|
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
|
||||||
|
username: "${TURN_USER}"
|
||||||
|
credential: "${TURN_PASS}"
|
||||||
|
EOF
|
||||||
|
env:
|
||||||
|
TURN_HOST: ${{ secrets.TURN_HOST }}
|
||||||
|
TURN_PORT: ${{ secrets.TURN_PORT }}
|
||||||
|
TURN_USER: ${{ secrets.TURN_USER }}
|
||||||
|
TURN_PASS: ${{ secrets.TURN_PASS }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
driver: docker-container
|
||||||
|
platforms: ${{ github.event.inputs.platforms }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to Aliyun Registry
|
||||||
|
if: github.event.inputs.enable_aliyun == 'true'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.ALIYUN_REGISTRY }}
|
||||||
|
username: ${{ secrets.ALIYUN_USERNAME }}
|
||||||
|
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
silentwind0/${{ env.STAGE0_IMAGE }}
|
||||||
|
${{ github.event.inputs.enable_aliyun == 'true' && format('{0}/silentwind/{1}', env.ALIYUN_REGISTRY, env.STAGE0_IMAGE) || '' }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest
|
||||||
|
type=raw,value=latest-{{date 'YYYYMMDD-HHmmss'}}
|
||||||
|
type=sha,prefix={{branch}}-
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.title=One-KVM Stage-0 Base Image
|
||||||
|
org.opencontainers.image.description=Base image for One-KVM build environment
|
||||||
|
org.opencontainers.image.vendor=One-KVM Project
|
||||||
|
|
||||||
|
- name: Build and push stage-0 image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./build/Dockerfile-stage-0
|
||||||
|
platforms: ${{ github.event.inputs.platforms }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha,scope=stage-0
|
||||||
|
cache-to: type=gha,mode=max,scope=stage-0
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
allow: security.insecure
|
||||||
|
|
||||||
|
build-main:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: github.event.inputs.build_type != 'stage-0'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Inject TURN config (optional)
|
||||||
|
if: ${{ env.TURN_HOST != '' }}
|
||||||
|
run: |
|
||||||
|
mkdir -p configs/kvmd/override.d
|
||||||
|
cat > configs/kvmd/override.d/turn.yaml <<EOF
|
||||||
|
janus:
|
||||||
|
stun:
|
||||||
|
host: ${TURN_HOST}
|
||||||
|
port: ${TURN_PORT}
|
||||||
|
local_ice_servers:
|
||||||
|
- urls:
|
||||||
|
- "stun:${TURN_HOST}:${TURN_PORT}"
|
||||||
|
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
|
||||||
|
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
|
||||||
|
username: "${TURN_USER}"
|
||||||
|
credential: "${TURN_PASS}"
|
||||||
|
EOF
|
||||||
|
env:
|
||||||
|
TURN_HOST: ${{ secrets.TURN_HOST }}
|
||||||
|
TURN_PORT: ${{ secrets.TURN_PORT }}
|
||||||
|
TURN_USER: ${{ secrets.TURN_USER }}
|
||||||
|
TURN_PASS: ${{ secrets.TURN_PASS }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
driver: docker-container
|
||||||
|
platforms: ${{ github.event.inputs.platforms }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to Aliyun Registry
|
||||||
|
if: github.event.inputs.enable_aliyun == 'true'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.ALIYUN_REGISTRY }}
|
||||||
|
username: ${{ secrets.ALIYUN_USERNAME }}
|
||||||
|
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set version tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event.inputs.build_type }}" == "dev" ]]; then
|
||||||
|
echo "tag=dev" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.event.inputs.build_type }}" == "release" ]]; then
|
||||||
|
echo "tag=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
silentwind0/${{ env.MAIN_IMAGE }}
|
||||||
|
${{ github.event.inputs.enable_aliyun == 'true' && format('{0}/silentwind/{1}', env.ALIYUN_REGISTRY, env.MAIN_IMAGE) || '' }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=${{ steps.version.outputs.tag }}
|
||||||
|
type=raw,value=${{ steps.version.outputs.tag }}-{{date 'YYYYMMDD-HHmmss'}}
|
||||||
|
type=sha,prefix={{branch}}-
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.title=One-KVM
|
||||||
|
org.opencontainers.image.description=DIY IP-KVM solution based on PiKVM
|
||||||
|
org.opencontainers.image.vendor=One-KVM Project
|
||||||
|
org.opencontainers.image.version=${{ steps.version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Build and push main image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./build/Dockerfile
|
||||||
|
platforms: ${{ github.event.inputs.platforms }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha,scope=main
|
||||||
|
cache-to: type=gha,mode=max,scope=main
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
- name: Build summary
|
||||||
|
run: |
|
||||||
|
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **Build Type**: ${{ github.event.inputs.build_type }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **Version Tag**: ${{ steps.version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **Platforms**: ${{ github.event.inputs.platforms }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **Aliyun Enabled**: ${{ github.event.inputs.enable_aliyun }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' >> $GITHUB_STEP_SUMMARY
|
||||||
41
.github/workflows/pico-hid-release.yml
vendored
41
.github/workflows/pico-hid-release.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
name: Pico HID Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Installing deps ...
|
|
||||||
run: sudo apt-get install cmake gcc-arm-none-eabi build-essential
|
|
||||||
|
|
||||||
- name: Building ...
|
|
||||||
run: make -C hid/pico all
|
|
||||||
|
|
||||||
- name: Releasing ...
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: Release ${{ github.ref }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
- name: Uploading firmware ...
|
|
||||||
id: upload-release-asset
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ./hid/pico/hid.uf2
|
|
||||||
asset_name: pico-hid.uf2
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
20
.github/workflows/pico-hid.yml
vendored
20
.github/workflows/pico-hid.yml
vendored
@ -1,20 +0,0 @@
|
|||||||
name: Pico HID CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Installing deps ...
|
|
||||||
run: sudo apt-get install cmake gcc-arm-none-eabi build-essential
|
|
||||||
|
|
||||||
- name: Running tests ...
|
|
||||||
run: make -C hid/pico all
|
|
||||||
20
.github/workflows/tox.yml
vendored
20
.github/workflows/tox.yml
vendored
@ -1,20 +0,0 @@
|
|||||||
name: TOX CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Building testenv ...
|
|
||||||
run: make testenv
|
|
||||||
|
|
||||||
- name: Running tests ...
|
|
||||||
run: make tox CMD="tox -c testenv/tox.ini"
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
/pkg/
|
/pkg/
|
||||||
/src/
|
/src/**/*.img
|
||||||
|
/src/tmp
|
||||||
/site/
|
/site/
|
||||||
/dist/
|
/dist/
|
||||||
/kvmd.egg-info/
|
/kvmd.egg-info/
|
||||||
@ -20,3 +21,4 @@
|
|||||||
/venv/
|
/venv/
|
||||||
.vscode/settings.j/son
|
.vscode/settings.j/son
|
||||||
kvmd_config/
|
kvmd_config/
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
16
LICENSE
16
LICENSE
@ -1,11 +1,7 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
=======
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
||||||
>>>>>>> origin/dev
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
@ -649,11 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
<<<<<<< HEAD
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
=======
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
>>>>>>> origin/dev
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
@ -672,19 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
|||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<<<<<<< HEAD
|
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
=======
|
|
||||||
<http://www.gnu.org/licenses/>.
|
|
||||||
>>>>>>> origin/dev
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
The GNU General Public License does not permit incorporating your program
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<<<<<<< HEAD
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
=======
|
|
||||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
||||||
>>>>>>> origin/dev
|
|
||||||
|
|||||||
44
Makefile
44
Makefile
@ -4,7 +4,8 @@ TESTENV_IMAGE ?= kvmd-testenv
|
|||||||
TESTENV_HID ?= /dev/ttyS10
|
TESTENV_HID ?= /dev/ttyS10
|
||||||
TESTENV_VIDEO ?= /dev/video0
|
TESTENV_VIDEO ?= /dev/video0
|
||||||
TESTENV_GPIO ?= /dev/gpiochip0
|
TESTENV_GPIO ?= /dev/gpiochip0
|
||||||
TESTENV_RELAY ?= $(if $(shell ls /dev/hidraw0 2>/dev/null || true),/dev/hidraw0,)
|
TESTENV_RELAY ?=
|
||||||
|
#TESTENV_RELAY ?= $(if $(shell ls /dev/hidraw0 2>/dev/null || true),/dev/hidraw0,)
|
||||||
|
|
||||||
LIBGPIOD_VERSION ?= 1.6.3
|
LIBGPIOD_VERSION ?= 1.6.3
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ all:
|
|||||||
@ echo " make testenv # Build test environment"
|
@ echo " make testenv # Build test environment"
|
||||||
@ echo " make tox # Run tests and linters"
|
@ echo " make tox # Run tests and linters"
|
||||||
@ echo " make tox E=pytest # Run selected test environment"
|
@ echo " make tox E=pytest # Run selected test environment"
|
||||||
|
@ echo " make tox-local # Run tests and linters locally (no Docker)"
|
||||||
|
@ echo " make tox-local E=flake8 # Run selected test locally"
|
||||||
@ echo " make gpio # Create gpio mockup"
|
@ echo " make gpio # Create gpio mockup"
|
||||||
@ echo " make run # Run kvmd"
|
@ echo " make run # Run kvmd"
|
||||||
@ echo " make run CMD=... # Run specified command inside kvmd environment"
|
@ echo " make run CMD=... # Run specified command inside kvmd environment"
|
||||||
@ -86,7 +89,9 @@ tox: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
&& cd /src \
|
&& cd /src \
|
||||||
@ -94,14 +99,19 @@ tox: testenv
|
|||||||
"
|
"
|
||||||
|
|
||||||
|
|
||||||
|
tox-local:
|
||||||
|
@./check-code.sh $(if $(E),$(E),all)
|
||||||
|
|
||||||
|
|
||||||
$(TESTENV_GPIO):
|
$(TESTENV_GPIO):
|
||||||
test ! -e $(TESTENV_GPIO)
|
test ! -e $(TESTENV_GPIO)
|
||||||
sudo modprobe gpio-mockup gpio_mockup_ranges=0,40
|
sudo modprobe gpio_mockup gpio_mockup_ranges=0,40
|
||||||
test -c $(TESTENV_GPIO)
|
test -c $(TESTENV_GPIO)
|
||||||
|
|
||||||
|
|
||||||
run: testenv $(TESTENV_GPIO)
|
run: testenv $(TESTENV_GPIO)
|
||||||
- $(DOCKER) run --rm --name kvmd \
|
- $(DOCKER) run --rm --name kvmd \
|
||||||
|
--ipc=shareable \
|
||||||
--privileged \
|
--privileged \
|
||||||
--volume `pwd`/testenv/run:/run/kvmd:rw \
|
--volume `pwd`/testenv/run:/run/kvmd:rw \
|
||||||
--volume `pwd`/testenv:/testenv:ro \
|
--volume `pwd`/testenv:/testenv:ro \
|
||||||
@ -128,6 +138,7 @@ run: testenv $(TESTENV_GPIO)
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
||||||
&& ln -s /testenv/web.css /etc/kvmd/web.css \
|
&& ln -s /testenv/web.css /etc/kvmd/web.css \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
@ -155,7 +166,9 @@ run-cfg: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
&& $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \
|
&& $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \
|
||||||
@ -178,6 +191,7 @@ run-ipmi: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
@ -187,6 +201,7 @@ run-ipmi: testenv
|
|||||||
|
|
||||||
run-vnc: testenv
|
run-vnc: testenv
|
||||||
- $(DOCKER) run --rm --name kvmd-vnc \
|
- $(DOCKER) run --rm --name kvmd-vnc \
|
||||||
|
--ipc=container:kvmd \
|
||||||
--volume `pwd`/testenv/run:/run/kvmd:rw \
|
--volume `pwd`/testenv/run:/run/kvmd:rw \
|
||||||
--volume `pwd`/testenv:/testenv:ro \
|
--volume `pwd`/testenv:/testenv:ro \
|
||||||
--volume `pwd`/kvmd:/kvmd:ro \
|
--volume `pwd`/kvmd:/kvmd:ro \
|
||||||
@ -201,6 +216,7 @@ run-vnc: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
|
&& cp /usr/share/kvmd/configs.default/kvmd/edid/v2.hex /etc/kvmd/switch-edid.hex \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
@ -271,36 +287,24 @@ clean-all: testenv clean
|
|||||||
.PHONY: testenv
|
.PHONY: testenv
|
||||||
|
|
||||||
run-stage-0:
|
run-stage-0:
|
||||||
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 \
|
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 -t silentwind0/kvmd-stage-0 \
|
||||||
--allow security.insecure --progress plain \
|
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
|
||||||
-f build/Dockerfile-stage-0 . \
|
|
||||||
--push
|
|
||||||
$(DOCKER) buildx build -t silentwind0/kvmd-stage-0 \
|
|
||||||
--allow security.insecure --progress plain \
|
--allow security.insecure --progress plain \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||||
-f build/Dockerfile-stage-0 . \
|
-f build/Dockerfile-stage-0 . \
|
||||||
--push
|
--push
|
||||||
|
|
||||||
run-build-dev:
|
run-build-dev:
|
||||||
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd:dev \
|
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd:dev -t silentwind0/kvmd:dev \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
|
||||||
-f build/Dockerfile . \
|
|
||||||
--push
|
|
||||||
$(DOCKER) buildx build -t silentwind0/kvmd:dev \
|
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||||
|
--build-arg CACHEBUST=$(date +%s) \
|
||||||
-f build/Dockerfile . \
|
-f build/Dockerfile . \
|
||||||
--push
|
--push
|
||||||
|
|
||||||
run-build-release:
|
run-build-release:
|
||||||
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd \
|
$(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd -t silentwind0/kvmd \
|
||||||
--progress plain \
|
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
|
||||||
-f build/Dockerfile . \
|
|
||||||
--push
|
|
||||||
$(DOCKER) buildx build -t silentwind0/kvmd \
|
|
||||||
--progress plain \
|
--progress plain \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||||
|
--build-arg CACHEBUST=$(date +%s) \
|
||||||
-f build/Dockerfile . \
|
-f build/Dockerfile . \
|
||||||
--push
|
--push
|
||||||
|
|
||||||
@ -331,7 +335,7 @@ run-nogpio: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& ln -s /testenv/web.css /etc/kvmd/web.css \
|
&& ln -s /testenv/web.css /etc/kvmd/web.css \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
|
|||||||
36
PKGBUILD
36
PKGBUILD
@ -39,20 +39,22 @@ for _variant in "${_variants[@]}"; do
|
|||||||
pkgname+=(kvmd-platform-$_platform-$_board)
|
pkgname+=(kvmd-platform-$_platform-$_board)
|
||||||
done
|
done
|
||||||
pkgbase=kvmd
|
pkgbase=kvmd
|
||||||
pkgver=4.3
|
pkgver=4.94
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="The main PiKVM daemon"
|
pkgdesc="The main PiKVM daemon"
|
||||||
url="https://github.com/pikvm/kvmd"
|
url="https://github.com/pikvm/kvmd"
|
||||||
license=(GPL)
|
license=(GPL)
|
||||||
arch=(any)
|
arch=(any)
|
||||||
depends=(
|
depends=(
|
||||||
"python>=3.12"
|
"python>=3.13"
|
||||||
"python<3.13"
|
"python<3.14"
|
||||||
python-yaml
|
python-yaml
|
||||||
python-aiohttp
|
python-aiohttp
|
||||||
python-aiofiles
|
python-aiofiles
|
||||||
python-async-lru
|
python-async-lru
|
||||||
python-passlib
|
python-passlib
|
||||||
|
# python-bcrypt is needed for passlib
|
||||||
|
python-bcrypt
|
||||||
python-pyotp
|
python-pyotp
|
||||||
python-qrcode
|
python-qrcode
|
||||||
python-periphery
|
python-periphery
|
||||||
@ -66,7 +68,7 @@ depends=(
|
|||||||
python-dbus
|
python-dbus
|
||||||
python-dbus-next
|
python-dbus-next
|
||||||
python-pygments
|
python-pygments
|
||||||
python-pyghmi
|
"python-pyghmi>=1.6.0-2"
|
||||||
python-pam
|
python-pam
|
||||||
python-pillow
|
python-pillow
|
||||||
python-xlib
|
python-xlib
|
||||||
@ -77,6 +79,10 @@ depends=(
|
|||||||
python-ldap
|
python-ldap
|
||||||
python-zstandard
|
python-zstandard
|
||||||
python-mako
|
python-mako
|
||||||
|
python-luma-oled
|
||||||
|
python-pyusb
|
||||||
|
python-pyudev
|
||||||
|
python-evdev
|
||||||
"libgpiod>=2.1"
|
"libgpiod>=2.1"
|
||||||
freetype2
|
freetype2
|
||||||
"v4l-utils>=1.22.1-1"
|
"v4l-utils>=1.22.1-1"
|
||||||
@ -87,11 +93,11 @@ depends=(
|
|||||||
iproute2
|
iproute2
|
||||||
dnsmasq
|
dnsmasq
|
||||||
ipmitool
|
ipmitool
|
||||||
"janus-gateway-pikvm>=0.14.2-3"
|
"janus-gateway-pikvm>=1.3.0"
|
||||||
certbot
|
certbot
|
||||||
platform-io-access
|
platform-io-access
|
||||||
raspberrypi-utils
|
raspberrypi-utils
|
||||||
"ustreamer>=6.11"
|
"ustreamer>=6.37"
|
||||||
|
|
||||||
# Systemd UDEV bug
|
# Systemd UDEV bug
|
||||||
"systemd>=248.3-2"
|
"systemd>=248.3-2"
|
||||||
@ -117,7 +123,7 @@ depends=(
|
|||||||
# fsck for /boot
|
# fsck for /boot
|
||||||
dosfstools
|
dosfstools
|
||||||
|
|
||||||
# pgrep for kvmd-udev-restart-pass
|
# pgrep for kvmd-udev-restart-pass, sysctl for kvmd-otgnet
|
||||||
procps-ng
|
procps-ng
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
@ -131,6 +137,7 @@ conflicts=(
|
|||||||
python-aiohttp-pikvm
|
python-aiohttp-pikvm
|
||||||
platformio
|
platformio
|
||||||
avrdude-pikvm
|
avrdude-pikvm
|
||||||
|
kvmd-oled
|
||||||
)
|
)
|
||||||
makedepends=(
|
makedepends=(
|
||||||
python-setuptools
|
python-setuptools
|
||||||
@ -159,12 +166,14 @@ package_kvmd() {
|
|||||||
|
|
||||||
install -Dm755 -t "$pkgdir/usr/bin" scripts/kvmd-{bootconfig,gencert,certbot}
|
install -Dm755 -t "$pkgdir/usr/bin" scripts/kvmd-{bootconfig,gencert,certbot}
|
||||||
|
|
||||||
install -Dm644 -t "$pkgdir/usr/lib/systemd/system" configs/os/services/*
|
install -dm755 "$pkgdir/usr/lib/systemd/system"
|
||||||
|
cp -rd configs/os/services -T "$pkgdir/usr/lib/systemd/system"
|
||||||
|
|
||||||
install -DTm644 configs/os/sysusers.conf "$pkgdir/usr/lib/sysusers.d/kvmd.conf"
|
install -DTm644 configs/os/sysusers.conf "$pkgdir/usr/lib/sysusers.d/kvmd.conf"
|
||||||
install -DTm644 configs/os/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/kvmd.conf"
|
install -DTm644 configs/os/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/kvmd.conf"
|
||||||
|
|
||||||
mkdir -p "$pkgdir/usr/share/kvmd"
|
mkdir -p "$pkgdir/usr/share/kvmd"
|
||||||
cp -r {hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd"
|
cp -r {switch,hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd"
|
||||||
find "$pkgdir/usr/share/kvmd/web" -name '*.pug' -exec rm -f '{}' \;
|
find "$pkgdir/usr/share/kvmd/web" -name '*.pug' -exec rm -f '{}' \;
|
||||||
|
|
||||||
local _cfg_default="$pkgdir/usr/share/kvmd/configs.default"
|
local _cfg_default="$pkgdir/usr/share/kvmd/configs.default"
|
||||||
@ -194,6 +203,7 @@ package_kvmd() {
|
|||||||
mkdir -p "$pkgdir/etc/kvmd/override.d"
|
mkdir -p "$pkgdir/etc/kvmd/override.d"
|
||||||
|
|
||||||
mkdir -p "$pkgdir/var/lib/kvmd/"{msd,pst}
|
mkdir -p "$pkgdir/var/lib/kvmd/"{msd,pst}
|
||||||
|
chmod 1775 "$pkgdir/var/lib/kvmd/pst"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -206,7 +216,7 @@ for _variant in "${_variants[@]}"; do
|
|||||||
cd \"kvmd-\$pkgver\"
|
cd \"kvmd-\$pkgver\"
|
||||||
|
|
||||||
pkgdesc=\"PiKVM platform configs - $_platform for $_board\"
|
pkgdesc=\"PiKVM platform configs - $_platform for $_board\"
|
||||||
depends=(kvmd=$pkgver-$pkgrel \"linux-rpi-pikvm>=6.6.21-3\")
|
depends=(kvmd=$pkgver-$pkgrel \"linux-rpi-pikvm>=6.6.45-13\" \"raspberrypi-bootloader-pikvm>=20240818-1\")
|
||||||
|
|
||||||
backup=(
|
backup=(
|
||||||
etc/sysctl.d/99-kvmd.conf
|
etc/sysctl.d/99-kvmd.conf
|
||||||
@ -250,8 +260,12 @@ for _variant in "${_variants[@]}"; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $_platform =~ ^.*-hdmi$ ]]; then
|
if [[ $_platform =~ ^.*-hdmi$ ]]; then
|
||||||
backup=(\"\${backup[@]}\" etc/kvmd/tc358743-edid.hex)
|
backup=(\"\${backup[@]}\" etc/kvmd/tc358743-edid.hex etc/kvmd/switch-edid.hex)
|
||||||
install -DTm444 configs/kvmd/edid/$_base.hex \"\$pkgdir/etc/kvmd/tc358743-edid.hex\"
|
install -DTm444 configs/kvmd/edid/$_base.hex \"\$pkgdir/etc/kvmd/tc358743-edid.hex\"
|
||||||
|
ln -s tc358743-edid.hex \"\$pkgdir/etc/kvmd/switch-edid.hex\"
|
||||||
|
else
|
||||||
|
backup=(\"\${backup[@]}\" etc/kvmd/switch-edid.hex)
|
||||||
|
install -DTm444 configs/kvmd/edid/_no-1920x1200.hex \"\$pkgdir/etc/kvmd/switch-edid.hex\"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p \"\$pkgdir/usr/share/kvmd\"
|
mkdir -p \"\$pkgdir/usr/share/kvmd\"
|
||||||
|
|||||||
307
README.en.md
Normal file
307
README.en.md
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="https://github.com/mofeng-git/Build-Armbian/assets/62919083/add9743a-0987-4e8a-b2cb-62121f236582" alt="One-KVM Logo" width="300">
|
||||||
|
<h1>One-KVM</h1>
|
||||||
|
<p><strong>DIY IP-KVM solution based on PiKVM</strong></p>
|
||||||
|
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/stargazers)
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/network/members)
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/issues)
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/blob/master/LICENSE)
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://one-kvm.mofeng.run">📖 Documentation</a> •
|
||||||
|
<a href="https://kvmd-demo.mofeng.run">🚀 Live Demo</a> •
|
||||||
|
<a href="#quick-start">⚡ Quick Start</a> •
|
||||||
|
<a href="#features">📊 Features</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[简体中文](README.md) | English
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
- [Overview](#project-overview)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [Others](#others)
|
||||||
|
|
||||||
|
## 📖 Project Overview
|
||||||
|
|
||||||
|
**One-KVM** is a DIY IP-KVM solution built upon the open-source [PiKVM](https://github.com/pikvm/pikvm) project. It uses cost-effective hardware to provide BIOS-level remote management for servers and workstations.
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
|
||||||
|
- **Home lab management** – Remotely manage servers and development devices
|
||||||
|
- **Server maintenance** – Perform system maintenance without physical access
|
||||||
|
- **System recovery** – Troubleshoot boot and BIOS/UEFI issues remotely
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 📊 Features
|
||||||
|
|
||||||
|
### Core Capabilities
|
||||||
|
|
||||||
|
| Feature | Description | Benefit |
|
||||||
|
|------|------|------|
|
||||||
|
| **Non-intrusive** | No software/driver required on the target machine | OS-agnostic; access BIOS/UEFI |
|
||||||
|
| **Cost-effective** | Leverages affordable hardware (TV boxes, dev boards) | Lower cost for KVM-over-IP |
|
||||||
|
| **Extendable** | Added utilities on top of PiKVM | Docker, recording, Chinese UI |
|
||||||
|
| **Deployment** | Supports Docker and prebuilt images | Preconfigured images for specific devices |
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
This project is maintained by an individual with limited resources and no commercial plan.
|
||||||
|
|
||||||
|
- No built-in free NAT punching/tunneling service
|
||||||
|
- No 24×7 technical support
|
||||||
|
- No guarantee on stability/compliance; use at your own risk
|
||||||
|
- User experience is optimized, but basic technical skills are still required
|
||||||
|
|
||||||
|
### Feature Comparison
|
||||||
|
|
||||||
|
> 💡 **Note:** The table below compares One-KVM with other PiKVM-based projects for reference only. If there are omissions or inaccuracies, please open an issue to help improve it.
|
||||||
|
|
||||||
|
| Feature | One-KVM | PiKVM | ArmKVM | BLIKVM |
|
||||||
|
|:--------:|:-------:|:-----:|:------:|:------:|
|
||||||
|
| Simplified Chinese WebUI | ✅ | ❌ | ✅ | ✅ |
|
||||||
|
| Remote video stream | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 |
|
||||||
|
| H.264 encoding | CPU | GPU | Unknown | GPU |
|
||||||
|
| Remote audio | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Remote mouse/keyboard | OTG/CH9329 | OTG/CH9329/Pico/Bluetooth | OTG | OTG |
|
||||||
|
| VNC control | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| ATX power control | GPIO/USB relay | GPIO | GPIO | GPIO |
|
||||||
|
| Virtual drive mounting | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Web terminal | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Docker deployment | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| Commercial offering | ❌ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
|
## ⚡ Quick Start
|
||||||
|
|
||||||
|
### Method 1: Docker (Recommended)
|
||||||
|
|
||||||
|
The Docker variant supports OTG or CH9329 as virtual HID and runs on Linux for amd64/arm64/armv7.
|
||||||
|
|
||||||
|
#### One-liner Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://one-kvm.mofeng.run/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manual Deployment
|
||||||
|
|
||||||
|
It is recommended to use the `--net=host` network mode for better WOL functionality and WebRTC communication support.
|
||||||
|
|
||||||
|
Docker host network mode:
|
||||||
|
|
||||||
|
Port 8080: HTTP Web service
|
||||||
|
Port 4430: HTTPS Web service
|
||||||
|
Port 5900: VNC service
|
||||||
|
Port 623: IPMI service
|
||||||
|
Ports 20000-40000: WebRTC communication port range for low-latency video
|
||||||
|
Port 9 (UDP): Wake-on-LAN (WOL)
|
||||||
|
|
||||||
|
Docker host mode:
|
||||||
|
|
||||||
|
**Using OTG as virtual HID:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --name kvmd -itd --privileged=true \
|
||||||
|
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
|
||||||
|
-v /sys/kernel/config:/sys/kernel/config -e OTG=1 \
|
||||||
|
--net=host \
|
||||||
|
silentwind0/kvmd
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using CH9329 as virtual HID:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --name kvmd -itd \
|
||||||
|
--device /dev/video0:/dev/video0 \
|
||||||
|
--device /dev/ttyUSB0:/dev/ttyUSB0 \
|
||||||
|
--net=host \
|
||||||
|
silentwind0/kvmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker bridge mode:
|
||||||
|
|
||||||
|
**Using OTG as virtual HID:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --name kvmd -itd --privileged=true \
|
||||||
|
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
|
||||||
|
-v /sys/kernel/config:/sys/kernel/config -e OTG=1 \
|
||||||
|
-p 8080:8080 -p 4430:4430 -p 5900:5900 -p 623:623 \
|
||||||
|
silentwind0/kvmd
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using CH9329 as virtual HID:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --name kvmd -itd \
|
||||||
|
--device /dev/video0:/dev/video0 \
|
||||||
|
--device /dev/ttyUSB0:/dev/ttyUSB0 \
|
||||||
|
-p 8080:8080 -p 4430:4430 -p 5900:5900 -p 623:623 \
|
||||||
|
silentwind0/kvmd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Flash Prebuilt One-KVM Images
|
||||||
|
|
||||||
|
Preconfigured images are provided for specific hardware platforms to simplify deployment and enable out-of-the-box experience.
|
||||||
|
|
||||||
|
#### Download
|
||||||
|
|
||||||
|
**GitHub:**
|
||||||
|
- **GitHub Releases:** [https://github.com/mofeng-git/One-KVM/releases](https://github.com/mofeng-git/One-KVM/releases)
|
||||||
|
|
||||||
|
**Other mirrors:**
|
||||||
|
- **No-login mirror:** [https://pan.huang1111.cn/s/mxkx3T1](https://pan.huang1111.cn/s/mxkx3T1)
|
||||||
|
- **Baidu Netdisk:** [https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj](https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj) (code: o9aj)
|
||||||
|
|
||||||
|
#### Supported Hardware Platforms
|
||||||
|
|
||||||
|
| Firmware | Codename | Hardware | Latest | Status |
|
||||||
|
|:--------:|:--------:|:--------:|:------:|:----:|
|
||||||
|
| OneCloud | Onecloud | USB capture card, OTG | 241018 | ✅ |
|
||||||
|
| CumeBox 2 | Cumebox2 | USB capture card, OTG | 241004 | ✅ |
|
||||||
|
| Vmare | Vmare-uefi | USB capture card, CH9329 | 241004 | ✅ |
|
||||||
|
| VirtualBox | Virtualbox-uefi | USB capture card, CH9329 | 241004 | ✅ |
|
||||||
|
| s905l3a Generic | E900v22c | USB capture card, OTG | 241004 | ✅ |
|
||||||
|
| Chainedbox | Chainedbox | USB capture card, OTG | 241004 | ✅ |
|
||||||
|
| Loongson 2K0300 | 2k0300 | USB capture card, CH9329 | 241025 | ✅ |
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions of all kinds are welcome!
|
||||||
|
|
||||||
|
### How to Contribute
|
||||||
|
|
||||||
|
1. **Fork this repo**
|
||||||
|
2. **Create a feature branch:** `git checkout -b feature/AmazingFeature`
|
||||||
|
3. **Commit your changes:** `git commit -m 'Add some AmazingFeature'`
|
||||||
|
4. **Push to the branch:** `git push origin feature/AmazingFeature`
|
||||||
|
5. **Open a Pull Request**
|
||||||
|
|
||||||
|
### Report Issues
|
||||||
|
|
||||||
|
If you find bugs or have suggestions:
|
||||||
|
1. Open an issue via [GitHub Issues](https://github.com/mofeng-git/One-KVM/issues)
|
||||||
|
2. Provide detailed error logs and reproduction steps
|
||||||
|
3. Include your hardware and system information
|
||||||
|
|
||||||
|
### Sponsorship
|
||||||
|
|
||||||
|
This project builds upon many great open-source projects and requires considerable time for testing and maintenance. If you find it helpful, consider supporting via **[Afdian](https://afdian.com/a/silentwind)**.
|
||||||
|
|
||||||
|
#### Thanks
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Click to view the thank-you list</strong></summary>
|
||||||
|
|
||||||
|
- 浩龙的电子嵌入式之路
|
||||||
|
|
||||||
|
- Tsuki
|
||||||
|
|
||||||
|
- H_xiaoming
|
||||||
|
|
||||||
|
- 0蓝蓝0
|
||||||
|
|
||||||
|
- fairybl
|
||||||
|
|
||||||
|
- Will
|
||||||
|
|
||||||
|
- 浩龙的电子嵌入式之路
|
||||||
|
|
||||||
|
- 自.知
|
||||||
|
|
||||||
|
- 观棋不语٩ ི۶
|
||||||
|
|
||||||
|
- 爱发电用户_a57a4
|
||||||
|
|
||||||
|
- 爱发电用户_2c769
|
||||||
|
|
||||||
|
- 霜序
|
||||||
|
|
||||||
|
- 远方(闲鱼用户名:小远技术店铺)
|
||||||
|
|
||||||
|
- 爱发电用户_399fc
|
||||||
|
|
||||||
|
- 斐斐の
|
||||||
|
|
||||||
|
- 爱发电用户_09451
|
||||||
|
|
||||||
|
- 超高校级的錆鱼
|
||||||
|
|
||||||
|
- 爱发电用户_08cff
|
||||||
|
|
||||||
|
- guoke
|
||||||
|
|
||||||
|
- mgt
|
||||||
|
|
||||||
|
- 姜沢掵
|
||||||
|
|
||||||
|
- ui_beam
|
||||||
|
|
||||||
|
- 爱发电用户_c0dd7
|
||||||
|
|
||||||
|
- 爱发电用户_dnjK
|
||||||
|
|
||||||
|
- 忍者胖猪
|
||||||
|
|
||||||
|
- 永遠の願い
|
||||||
|
|
||||||
|
- 爱发电用户_GBrF
|
||||||
|
|
||||||
|
- 爱发电用户_fd65c
|
||||||
|
|
||||||
|
- 爱发电用户_vhNa
|
||||||
|
|
||||||
|
- 爱发电用户_Xu6S
|
||||||
|
|
||||||
|
- moss
|
||||||
|
|
||||||
|
- woshididi
|
||||||
|
|
||||||
|
- 爱发电用户_a0fd1
|
||||||
|
|
||||||
|
- 爱发电用户_f6bH
|
||||||
|
|
||||||
|
- 码农
|
||||||
|
|
||||||
|
- 爱发电用户_6639f
|
||||||
|
|
||||||
|
- jeron
|
||||||
|
|
||||||
|
- 爱发电用户_CN7y
|
||||||
|
|
||||||
|
- 爱发电用户_Up6w
|
||||||
|
|
||||||
|
- 爱发电用户_e3202
|
||||||
|
|
||||||
|
- ......
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Sponsors
|
||||||
|
|
||||||
|
This project is supported by the following sponsors:
|
||||||
|
|
||||||
|
**CDN & Security:**
|
||||||
|
- **[Tencent EdgeOne](https://edgeone.ai/zh?from=github)** – CDN acceleration and security protection
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**File Storage:**
|
||||||
|
- **[Huang1111公益计划](https://pan.huang1111.cn/s/mxkx3T1)** – No-login download service
|
||||||
|
|
||||||
|
## 📚 Others
|
||||||
|
|
||||||
|
### Open-source Projects Used
|
||||||
|
|
||||||
|
This project is built upon the following excellent open-source projects:
|
||||||
|
|
||||||
|
- [PiKVM](https://github.com/pikvm/pikvm) – Open-source DIY IP-KVM solution
|
||||||
|
|
||||||
|
|
||||||
310
README.md
310
README.md
@ -1,24 +1,136 @@
|
|||||||
<h3 align=center><img src="https://github.com/mofeng-git/Build-Armbian/assets/62919083/add9743a-0987-4e8a-b2cb-62121f236582" alt="logo" width="300"><br></h3>
|
<div align="center">
|
||||||
<h3 align=center><a href="https://github.com/mofeng-git/One-KVM/blob/master/README.md">简体中文</a> </h3>
|
<img src="https://github.com/mofeng-git/Build-Armbian/assets/62919083/add9743a-0987-4e8a-b2cb-62121f236582" alt="One-KVM Logo" width="300">
|
||||||
<p align=right> </p>
|
<h1>One-KVM</h1>
|
||||||
|
<p><strong>基于 PiKVM 的 DIY IP-KVM 解决方案</strong></p>
|
||||||
|
|
||||||
### 介绍
|
<p><a href="README.md">简体中文</a> | <a href="README.en.md">English</a></p>
|
||||||
|
|
||||||
One-KVM 是基于廉价计算机硬件和 [PiKVM]((https://github.com/pikvm/pikvm)) 软件二次开发的 BIOS 级远程控制项目。可以实现远程管理服务器或工作站,无需在被控机安装软件调整设置,实现无侵入式控制,适用范围广泛。
|
[](https://github.com/mofeng-git/One-KVM/stargazers)
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/network/members)
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/issues)
|
||||||
|
[](https://github.com/mofeng-git/One-KVM/blob/master/LICENSE)
|
||||||
|
|
||||||
演示网站:[https://kvmd-demo.mofeng.run](https://kvmd-demo.mofeng.run)
|
<p>
|
||||||
|
<a href="https://docs.one-kvm.cn">📖 详细文档</a> •
|
||||||
|
<a href="https://demo.one-kvm.cn/">🚀 在线演示</a> •
|
||||||
|
<a href="#快速开始">⚡ 快速开始</a> •
|
||||||
|
<a href="#功能介绍">📊 功能介绍</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||

|
---
|
||||||
|
|
||||||
|
## 📋 目录
|
||||||
|
|
||||||
### 快速开始
|
- [项目概述](#项目概述)
|
||||||
|
- [功能介绍](#功能介绍)
|
||||||
|
- [快速开始](#快速开始)
|
||||||
|
- [贡献指南](#贡献指南)
|
||||||
|
- [其他](#其他)
|
||||||
|
|
||||||
**方式一:Docker 镜像部署(推荐)**
|
## 📖 项目概述
|
||||||
|
|
||||||
Docker 版本可以使用 OTG 或 CH9329 作为虚拟 HID ,支持 amd64、arm64、armv7 架构的 Linux 系统安装。
|
**One-KVM** 是基于开源 [PiKVM](https://github.com/pikvm/pikvm) 项目进行二次开发的 DIY IP-KVM 解决方案。该方案利用成本较低的硬件设备,实现 BIOS 级别的远程服务器或工作站管理功能。
|
||||||
|
|
||||||
|
> 本项目目前并无适配树莓派的计划。这是因为树莓派平台本质上属于 PiKVM 官方硬件生态和盈利的一部分。我们非常尊重和感谢上游项目 PiKVM ,因此 One-KVM 的设备适配主要聚焦于补充性场景,尽量避免与 PiKVM 官方产品产生重叠,以支持其可持续发展。
|
||||||
|
|
||||||
|
### 应用场景
|
||||||
|
|
||||||
|
- **家庭实验室主机管理** - 远程管理服务器和开发设备
|
||||||
|
- **服务器远程维护** - 无需物理接触即可进行系统维护
|
||||||
|
- **系统故障处理** - 远程解决系统启动和 BIOS 相关问题
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 📊 功能介绍
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
| 特性 | 描述 | 优势 |
|
||||||
|
|------|------|------|
|
||||||
|
| **无侵入性** | 无需在目标机器上安装软件或驱动 | 不依赖操作系统,可访问 BIOS/UEFI 设置 |
|
||||||
|
| **成本效益** | 利用常见硬件设备(如电视盒子、开发板等) | 降低 KVM over IP 的实现成本 |
|
||||||
|
| **功能扩展** | 在 PiKVM 基础上增加实用功能 | Docker 部署、视频录制、中文界面 |
|
||||||
|
| **部署方式** | 支持 Docker 部署和硬件整合包 | 为特定硬件平台提供预配置方案 |
|
||||||
|
|
||||||
|
### 项目限制
|
||||||
|
|
||||||
|
本项目为个人维护的开源项目,资源有限,无商业运营计划
|
||||||
|
|
||||||
|
- 不提供内置免费内网穿透服务,相关问题请自行解决
|
||||||
|
- 不提供24×7小时技术支持服务
|
||||||
|
- 不承诺系统稳定性和合规性,使用风险需自行承担
|
||||||
|
- 尽力优化用户体验,但仍需要一定的技术基础
|
||||||
|
|
||||||
|
### 功能对比
|
||||||
|
|
||||||
|
> 💡 **说明:** 以下表格展示了 One-KVM 与其他基于 PiKVM 项目的功能对比,仅供参考。如有遗漏或错误,欢迎联系更正。
|
||||||
|
|
||||||
|
| 功能特性 | One-KVM | PiKVM | ArmKVM | BLIKVM |
|
||||||
|
|:--------:|:-------:|:-----:|:------:|:------:|
|
||||||
|
| 简体中文 WebUI | ✅ | ❌ | ✅ | ✅ |
|
||||||
|
| 远程视频流 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 | MJPEG/H.264 |
|
||||||
|
| H.264 视频编码 | CPU/GPU | GPU | 未知 | GPU |
|
||||||
|
| 远程音频流 | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| 远程鼠键控制 | OTG/CH9329 | OTG/CH9329/Pico/Bluetooth | OTG | OTG |
|
||||||
|
| VNC 控制 | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| ATX 电源控制 | GPIO/USB 继电器 | GPIO | GPIO | GPIO |
|
||||||
|
| 虚拟存储驱动器挂载 | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| 网页终端 | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Docker 部署 | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| 商业化运营 | ❌ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
|
## ⚡ 快速开始
|
||||||
|
|
||||||
|
### 方式一:Docker 镜像部署(推荐)
|
||||||
|
|
||||||
|
Docker 版本支持 OTG 或 CH9329 作为虚拟 HID,兼容 amd64、arm64、armv7 架构的 Linux 系统。
|
||||||
|
|
||||||
|
#### 一键脚本部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://docs.one-kvm.cn/quick_start.sh -o quick_start.sh && bash quick_start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 手动部署
|
||||||
|
|
||||||
|
推荐使用 --net=host 网络模式以获得更好的 wol 功能和 webrtc 通信支持。
|
||||||
|
|
||||||
|
docker host 网络模式:
|
||||||
|
|
||||||
|
端口 8080:HTTP Web 服务
|
||||||
|
端口 4430:HTTPS Web 服务
|
||||||
|
端口 5900:VNC 服务
|
||||||
|
端口 623:IPMI 服务
|
||||||
|
端口 20000-40000:WebRTC 通信端口范围,用于低延迟视频传输
|
||||||
|
端口 9(UDP):Wake-on-LAN(WOL)唤醒功能
|
||||||
|
|
||||||
|
docker host 模式:
|
||||||
|
|
||||||
|
**使用 OTG 作为虚拟 HID:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --name kvmd -itd --privileged=true \
|
||||||
|
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
|
||||||
|
-v /sys/kernel/config:/sys/kernel/config -e OTG=1 \
|
||||||
|
--net=host \
|
||||||
|
silentwind0/kvmd
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用 CH9329 作为虚拟 HID:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --name kvmd -itd \
|
||||||
|
--device /dev/video0:/dev/video0 \
|
||||||
|
--device /dev/ttyUSB0:/dev/ttyUSB0 \
|
||||||
|
--net=host \
|
||||||
|
silentwind0/kvmd
|
||||||
|
```
|
||||||
|
|
||||||
|
docker bridge 模式:
|
||||||
|
|
||||||
|
**使用 OTG 作为虚拟 HID:**
|
||||||
|
|
||||||
如果使用 OTG 作为虚拟 HID,可以使用如下部署命令:
|
|
||||||
```bash
|
```bash
|
||||||
sudo docker run --name kvmd -itd --privileged=true \
|
sudo docker run --name kvmd -itd --privileged=true \
|
||||||
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
|
-v /lib/modules:/lib/modules:ro -v /dev:/dev \
|
||||||
@ -27,7 +139,8 @@ sudo docker run --name kvmd -itd --privileged=true \
|
|||||||
silentwind0/kvmd
|
silentwind0/kvmd
|
||||||
```
|
```
|
||||||
|
|
||||||
如果使用 CH9329,可以使用如下部署命令:
|
**使用 CH9329 作为虚拟 HID:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo docker run --name kvmd -itd \
|
sudo docker run --name kvmd -itd \
|
||||||
--device /dev/video0:/dev/video0 \
|
--device /dev/video0:/dev/video0 \
|
||||||
@ -36,65 +149,180 @@ sudo docker run --name kvmd -itd \
|
|||||||
silentwind0/kvmd
|
silentwind0/kvmd
|
||||||
```
|
```
|
||||||
|
|
||||||
部署完成访问 https://IP:4430 ,点击信任自签证书,即可开始使用,默认账号密码:admin/admin。
|
### 方式二:直刷 One-KVM 整合包
|
||||||
|
|
||||||
如无法访问可以使用 `sudo docker logs kvmd` 命令查看日志尝试修复、提交 issue 或在 QQ 群内寻求帮助。
|
针对特定硬件平台,提供了预配置的 One-KVM 打包镜像,简化部署流程,实现开箱即用。
|
||||||
|
|
||||||
详细内容可以查阅 [One-KVM文档](https://one-kvm.mofeng.run/)。
|
#### 固件下载
|
||||||
|
|
||||||
**方式二:直刷 One-KVM 镜像**
|
**GitHub 下载:**
|
||||||
|
- **GitHub Releases:** [https://github.com/mofeng-git/One-KVM/releases](https://github.com/mofeng-git/One-KVM/releases)
|
||||||
|
|
||||||
对于玩客云设备,本项目 Releases 页可以找到适配玩客云的 One-KVM 预编译镜像。镜像名称带 One-KVM 前缀、burn 后缀的为线刷镜像,可使用 USB_Burning_Tool 软件线刷至玩客云。预编译线刷镜像为开箱即用,刷好后启动设备就可以开始使用 One-KVM。
|
**其他下载方式:**
|
||||||
|
- **免登录高速下载:** [http://sd1.files.one-kvm.cn/](http://sd1.files.one-kvm.cn/)(由群友赞助,支持直链,接入 EdgeOne CDN,建议使用多线程下载工具下载获取最高速度)
|
||||||
|
- **免登录下载:** [https://pan.huang1111.cn/s/mxkx3T1](https://pan.huang1111.cn/s/mxkx3T1) (由 Huang1111公益计划 提供)
|
||||||
|
- **百度网盘:** [https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj](https://pan.baidu.com/s/166-2Y8PBF4SbHXFkGmFJYg?pwd=o9aj) (提取码:o9aj)
|
||||||
|
|
||||||
|
#### 支持的硬件平台
|
||||||
|
|
||||||
**赞助**
|
| 固件型号 | 固件代号 | 硬件配置 | 最新版本 | 状态 |
|
||||||
|
|:--------:|:--------:|:--------:|:--------:|:----:|
|
||||||
|
| 玩客云 | Onecloud | USB 采集卡、OTG | 241018 | ✅ |
|
||||||
|
| 私家云二代 | Cumebox2 | USB 采集卡、OTG | 241004 | ✅ |
|
||||||
|
| Vmare | Vmare-uefi | USB 采集卡、CH9329 | 241004 | ✅ |
|
||||||
|
| Virtualbox | Virtualbox-uefi | USB 采集卡、CH9329 | 241004 | ✅ |
|
||||||
|
| s905l3a 通用包 | E900v22c | USB 采集卡、OTG | 241004 | ✅ |
|
||||||
|
| 我家云 | Chainedbox | USB 采集卡、OTG | 241004 | ✅ |
|
||||||
|
| 龙芯久久派 | 2k0300 | USB 采集卡、CH9329 | 241025 | ❌ |
|
||||||
|
|
||||||
这个项目基于众多开源项目二次开发,作者为此花费了大量的时间和精力进行测试和维护。若此项目对您有用,您可以考虑通过 [为爱发电](https://afdian.com/a/silentwind) 赞助一笔小钱支持作者。作者将能够购买新的硬件(玩客云和周边设备)来测试和维护 One-KVM 的各种配置,并在项目上投入更多的时间。
|
### 报告问题
|
||||||
|
|
||||||
**感谢名单**
|
如果您发现了问题,请:
|
||||||
|
1. 使用 [GitHub Issues](https://github.com/mofeng-git/One-KVM/issues) 报告
|
||||||
|
2. 提供详细的错误信息和复现步骤
|
||||||
|
3. 包含您的硬件配置和系统信息
|
||||||
|
|
||||||
|
### 赞助支持
|
||||||
|
|
||||||
|
本项目基于多个优秀开源项目进行二次开发,作者投入了大量时间进行测试和维护。如果您觉得这个项目有价值,欢迎通过 **[为爱发电](https://afdian.com/a/silentwind)** 支持项目发展。
|
||||||
|
|
||||||
|
#### 感谢名单
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
<summary><strong>点击查看感谢名单</strong></summary>
|
||||||
|
|
||||||
浩龙的电子嵌入式之路(赞助)
|
- 浩龙的电子嵌入式之路
|
||||||
|
|
||||||
Tsuki(赞助)
|
- Tsuki
|
||||||
|
|
||||||
H_xiaoming
|
- H_xiaoming
|
||||||
|
|
||||||
0蓝蓝0
|
- 0蓝蓝0
|
||||||
|
|
||||||
fairybl
|
- fairybl
|
||||||
|
|
||||||
Will
|
- Will
|
||||||
|
|
||||||
浩龙的电子嵌入式之路
|
- 浩龙的电子嵌入式之路
|
||||||
|
|
||||||
自.知
|
- 自.知
|
||||||
|
|
||||||
观棋不语٩ ི۶
|
- 观棋不语٩ ི۶
|
||||||
|
|
||||||
爱发电用户_a57a4
|
- 爱发电用户_a57a4
|
||||||
|
|
||||||
爱发电用户_2c769
|
- 爱发电用户_2c769
|
||||||
|
|
||||||
霜序
|
- 霜序
|
||||||
|
|
||||||
[远方](https://runyf.cn/)
|
- 远方(闲鱼用户名:小远技术店铺)
|
||||||
|
|
||||||
爱发电用户_399fc
|
- 爱发电用户_399fc
|
||||||
|
|
||||||
[斐斐の](https://www.mmuaa.com/)
|
- 斐斐の
|
||||||
|
|
||||||
|
- 爱发电用户_09451
|
||||||
|
|
||||||
|
- 超高校级的錆鱼
|
||||||
|
|
||||||
|
- 爱发电用户_08cff
|
||||||
|
|
||||||
|
- guoke
|
||||||
|
|
||||||
|
- mgt
|
||||||
|
|
||||||
|
- 姜沢掵
|
||||||
|
|
||||||
|
- ui_beam
|
||||||
|
|
||||||
|
- 爱发电用户_c0dd7
|
||||||
|
|
||||||
|
- 爱发电用户_dnjK
|
||||||
|
|
||||||
|
- 忍者胖猪
|
||||||
|
|
||||||
|
- 永遠の願い
|
||||||
|
|
||||||
|
- 爱发电用户_GBrF
|
||||||
|
|
||||||
|
- 爱发电用户_fd65c
|
||||||
|
|
||||||
|
- 爱发电用户_vhNa
|
||||||
|
|
||||||
|
- 爱发电用户_Xu6S
|
||||||
|
|
||||||
|
- moss
|
||||||
|
|
||||||
|
- woshididi
|
||||||
|
|
||||||
|
- 爱发电用户_a0fd1
|
||||||
|
|
||||||
|
- 爱发电用户_f6bH
|
||||||
|
|
||||||
|
- 码农
|
||||||
|
|
||||||
|
- 爱发电用户_6639f
|
||||||
|
|
||||||
|
- jeron
|
||||||
|
|
||||||
|
- 爱发电用户_CN7y
|
||||||
|
|
||||||
|
- 爱发电用户_Up6w
|
||||||
|
|
||||||
|
- 爱发电用户_e3202
|
||||||
|
|
||||||
|
- 一语念白
|
||||||
|
|
||||||
|
- 云边
|
||||||
|
|
||||||
|
- 爱发电用户_5a711
|
||||||
|
|
||||||
|
- 爱发电用户_9a706
|
||||||
|
|
||||||
|
- T0m9ir1SUKI
|
||||||
|
|
||||||
|
- 爱发电用户_56d52
|
||||||
|
|
||||||
|
- 爱发电用户_3N6F
|
||||||
|
|
||||||
|
- DUSK
|
||||||
|
|
||||||
|
- 飘零
|
||||||
|
|
||||||
|
- .
|
||||||
|
|
||||||
|
- 饭太稀
|
||||||
|
|
||||||
|
- 葱
|
||||||
|
|
||||||
|
- ......
|
||||||
|
|
||||||
......
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
本项目使用了下列开源项目:
|
#### 赞助商
|
||||||
1. [pikvm/pikvm: Open and inexpensive DIY IP-KVM based on Raspberry Pi (github.com)](https://github.com/pikvm/pikvm)
|
|
||||||
|
|
||||||
**状态**
|
本项目得到以下赞助商的支持:
|
||||||
|
|
||||||
[](https://star-history.com/#mofeng-git/One-KVM&Date)
|
**CDN 加速及安全防护:**
|
||||||
|
- **[Tencent EdgeOne](https://edgeone.ai/zh?from=github)** - 提供 CDN 加速及安全防护服务
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
**文件存储服务:**
|
||||||
|
- **[Huang1111公益计划](https://pan.huang1111.cn/s/mxkx3T1)** - 提供免登录下载服务
|
||||||
|
|
||||||
|
**云服务商**
|
||||||
|
|
||||||
|
- **[林枫云](https://www.dkdun.cn)** - 赞助了本项目宁波大带宽服务器
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
|
||||||
|
|
||||||
|
## 📚 其他
|
||||||
|
|
||||||
|
### 使用的开源项目
|
||||||
|
|
||||||
|
本项目基于以下优秀开源项目进行二次开发:
|
||||||
|
|
||||||
|
- [PiKVM](https://github.com/pikvm/pikvm) - 开源的 DIY IP-KVM 解决方案
|
||||||
|
|||||||
122
build/Dockerfile
122
build/Dockerfile
@ -1,41 +1,123 @@
|
|||||||
FROM silentwind0/kvmd-stage-0 AS builder
|
FROM registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 AS builder
|
||||||
|
|
||||||
FROM python:3.12.0rc2-slim-bookworm
|
FROM python:3.11.11-slim-bookworm
|
||||||
|
|
||||||
LABEL maintainer="mofeng654321@hotmail.com"
|
LABEL maintainer="mofeng654321@hotmail.com"
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
COPY --from=builder /tmp/lib/* /tmp/lib/
|
COPY --from=builder /tmp/lib/* /tmp/lib/
|
||||||
COPY --from=builder /tmp/ustreamer/ustreamer /tmp/ustreamer/ustreamer-dump /usr/bin/janus /usr/bin/
|
COPY --from=builder /tmp/ustreamer/ustreamer /tmp/ustreamer/ustreamer-dump /usr/bin/janus /usr/bin/
|
||||||
COPY --from=builder /tmp/wheel/*.whl /tmp/wheel/
|
COPY --from=builder /tmp/wheel/*.whl /tmp/wheel/
|
||||||
COPY --from=builder /tmp/ustreamer/libjanus_ustreamer.so /usr/lib/ustreamer/janus/
|
COPY --from=builder /tmp/ustreamer/libjanus_ustreamer.so /usr/lib/ustreamer/janus/
|
||||||
COPY --from=builder /usr/lib/janus/transports/* /usr/lib/janus/transports/
|
COPY --from=builder /usr/lib/janus/transports/* /usr/lib/janus/transports/
|
||||||
|
COPY --from=builder /tmp/arm64-libs.tar.gz* /tmp/
|
||||||
|
RUN if [ ${TARGETARCH} = arm64 ] && [ -f /tmp/arm64-libs.tar.gz ]; then \
|
||||||
|
cd / && tar -xzf /tmp/arm64-libs.tar.gz && rm -f /tmp/arm64-libs.tar.gz; \
|
||||||
|
fi
|
||||||
|
|
||||||
ARG TARGETARCH
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
TZ=Asia/Shanghai
|
||||||
ENV PYTHONUNBUFFERED=1
|
|
||||||
ENV TZ=Asia/Shanghai
|
|
||||||
|
|
||||||
RUN cp /tmp/lib/* /lib/*-linux-*/ \
|
|
||||||
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check /tmp/wheel/*.whl \
|
|
||||||
&& rm -rf /tmp/lib /tmp/wheel
|
|
||||||
|
|
||||||
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
|
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends libxkbcommon-x11-0 nginx tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim iptables sudo curl kmod \
|
&& apt-get install -y --no-install-recommends \
|
||||||
libmicrohttpd12 libjansson4 libssl3 libsofia-sip-ua0 libglib2.0-0 libopus0 libogg0 libcurl4 libconfig9 libusrsctp2 libwebsockets17 libnss3 libasound2 \
|
libxkbcommon-x11-0 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
nginx \
|
||||||
|
tesseract-ocr \
|
||||||
RUN if [ ${TARGETARCH} = arm ]; then ARCH=armhf; elif [ ${TARGETARCH} = arm64 ]; then ARCH=aarch64; elif [ ${TARGETARCH} = amd64 ]; then ARCH=x86_64; fi \
|
tesseract-ocr-eng \
|
||||||
|
tesseract-ocr-chi-sim \
|
||||||
|
iptables \
|
||||||
|
sudo \
|
||||||
|
curl \
|
||||||
|
kmod \
|
||||||
|
libmicrohttpd12 \
|
||||||
|
libjansson4 \
|
||||||
|
libssl3 \
|
||||||
|
libsofia-sip-ua0 \
|
||||||
|
libglib2.0-0 \
|
||||||
|
libopus0 \
|
||||||
|
libogg0 \
|
||||||
|
libcurl4 \
|
||||||
|
libconfig9 \
|
||||||
|
libusrsctp2 \
|
||||||
|
libwebsockets17 \
|
||||||
|
libnss3 \
|
||||||
|
libasound2 \
|
||||||
|
libdrm2 \
|
||||||
|
libx264-164 \
|
||||||
|
libyuv0 \
|
||||||
|
nano \
|
||||||
|
unzip \
|
||||||
|
&& case ${TARGETARCH} in \
|
||||||
|
amd64) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
libavcodec59 libavformat59 libavutil57 \
|
||||||
|
libswscale6 libavfilter8 libavdevice59 \
|
||||||
|
ffmpeg vainfo \
|
||||||
|
libva2 libva-drm2 libva-x11-2 \
|
||||||
|
mesa-va-drivers mesa-vdpau-drivers \
|
||||||
|
intel-media-va-driver i965-va-driver \
|
||||||
|
;; \
|
||||||
|
arm) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
libavcodec59 libavformat59 libavutil57 \
|
||||||
|
libswscale6 libavfilter8 libavdevice59 \
|
||||||
|
v4l-utils libv4l-0 \
|
||||||
|
;; \
|
||||||
|
arm64) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
v4l-utils libv4l-0 libavutil57 \
|
||||||
|
libstdc++6 libavcodec59 libavformat59 \
|
||||||
|
libswscale6 libavfilter8 libavdevice59 \
|
||||||
|
libva2 libva-drm2 libva-x11-2 \
|
||||||
|
libvdpau1 ocl-icd-libopencl1 \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "Unsupported architecture: ${TARGETARCH}" && exit 1 \
|
||||||
|
;; \
|
||||||
|
esac \
|
||||||
|
&& cp /tmp/lib/* /lib/*-linux-*/ \
|
||||||
|
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check /tmp/wheel/*.whl \
|
||||||
|
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check pyfatfs \
|
||||||
|
&& if [ ${TARGETARCH} = arm ]; then ARCH=armhf; \
|
||||||
|
elif [ ${TARGETARCH} = arm64 ]; then ARCH=aarch64; \
|
||||||
|
elif [ ${TARGETARCH} = amd64 ]; then ARCH=x86_64; \
|
||||||
|
fi \
|
||||||
&& curl https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.$ARCH -L -o /usr/local/bin/ttyd \
|
&& curl https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.$ARCH -L -o /usr/local/bin/ttyd \
|
||||||
&& chmod +x /usr/local/bin/ttyd \
|
&& chmod +x /usr/local/bin/ttyd \
|
||||||
|
&& mkdir -p /tmp/gostc && cd /tmp/gostc \
|
||||||
|
&& case ${TARGETARCH} in \
|
||||||
|
amd64) GOSTC_ARCH=amd64_v1 ;; \
|
||||||
|
arm) GOSTC_ARCH=arm_7 ;; \
|
||||||
|
arm64) GOSTC_ARCH=arm64_v8.0 ;; \
|
||||||
|
*) echo "Unsupported architecture for gostc: ${TARGETARCH}" && exit 1 ;; \
|
||||||
|
esac \
|
||||||
|
&& curl -L https://github.com/mofeng-git/gostc-open/releases/download/v2.0.8-beta.2/gostc_linux_${GOSTC_ARCH}.tar.gz -o gostc.tar.gz \
|
||||||
|
&& tar -xzf gostc.tar.gz \
|
||||||
|
&& mv gostc /usr/bin/ \
|
||||||
|
&& chmod +x /usr/bin/gostc \
|
||||||
|
&& cd / && rm -rf /tmp/gostc \
|
||||||
&& adduser kvmd --gecos "" --disabled-password \
|
&& adduser kvmd --gecos "" --disabled-password \
|
||||||
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
|
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
|
||||||
&& mkdir -p /etc/kvmd_backup/override.d /var/lib/kvmd/msd/images /var/lib/kvmd/msd/meta /var/lib/kvmd/pst/data /opt/vc/bin /run/kvmd /tmp/kvmd-nginx \
|
&& mkdir -p /etc/kvmd_backup/override.d \
|
||||||
&& touch /run/kvmd/ustreamer.sock
|
/var/lib/kvmd/msd/images \
|
||||||
|
/var/lib/kvmd/msd/meta \
|
||||||
|
/var/lib/kvmd/pst/data \
|
||||||
|
/var/lib/kvmd/msd/NormalFiles \
|
||||||
|
/opt/vc/bin \
|
||||||
|
/run/kvmd \
|
||||||
|
/tmp/kvmd-nginx \
|
||||||
|
&& touch /run/kvmd/ustreamer.sock \
|
||||||
|
&& groupadd kvmd-selfauth \
|
||||||
|
&& usermod -a -G kvmd-selfauth root \
|
||||||
|
&& apt clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& rm -rf /tmp/lib /tmp/wheel \
|
||||||
|
&& ustreamer -v
|
||||||
|
|
||||||
|
COPY testenv/fakes/vcgencmd scripts/kvmd* /usr/bin/
|
||||||
COPY testenv/fakes/vcgencmd /usr/bin/
|
|
||||||
COPY extras/ /usr/share/kvmd/extras/
|
COPY extras/ /usr/share/kvmd/extras/
|
||||||
COPY web/ /usr/share/kvmd/web/
|
COPY web/ /usr/share/kvmd/web/
|
||||||
COPY scripts/kvmd-gencert /usr/share/kvmd/
|
COPY scripts/kvmd-gencert /usr/share/kvmd/
|
||||||
|
|||||||
@ -1,70 +1,202 @@
|
|||||||
# syntax = docker/dockerfile:experimental
|
# syntax = docker/dockerfile:experimental
|
||||||
FROM python:3.12.0rc2-slim-bookworm AS builder
|
FROM debian:bookworm-slim AS builder
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
RUSTUP_DIST_SERVER="https://mirrors.tuna.tsinghua.edu.cn/rustup"
|
||||||
|
|
||||||
|
# 更新源并安装依赖
|
||||||
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
|
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends build-essential libssl-dev libffi-dev python3-dev libevent-dev libjpeg-dev \
|
&& apt-get install -y --no-install-recommends \
|
||||||
libbsd-dev libudev-dev git pkg-config wget curl libmicrohttpd-dev libjansson-dev libssl-dev libsofia-sip-ua-dev libglib2.0-dev \
|
python3-full \
|
||||||
libopus-dev libogg-dev libcurl4-openssl-dev liblua5.3-dev libconfig-dev libopus-dev libtool automake autoconf meson cmake \
|
python3-pip \
|
||||||
libx264-dev libyuv-dev libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev \
|
python3-dev \
|
||||||
|
build-essential \
|
||||||
|
libssl-dev \
|
||||||
|
libffi-dev \
|
||||||
|
python3-dev \
|
||||||
|
libevent-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libbsd-dev \
|
||||||
|
libudev-dev \
|
||||||
|
git \
|
||||||
|
pkg-config \
|
||||||
|
wget \
|
||||||
|
curl \
|
||||||
|
libmicrohttpd-dev \
|
||||||
|
libjansson-dev \
|
||||||
|
libsofia-sip-ua-dev \
|
||||||
|
libglib2.0-dev \
|
||||||
|
libopus-dev \
|
||||||
|
libogg-dev \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
liblua5.3-dev \
|
||||||
|
libconfig-dev \
|
||||||
|
libtool \
|
||||||
|
automake \
|
||||||
|
autoconf \
|
||||||
|
meson \
|
||||||
|
cmake \
|
||||||
|
libx264-dev \
|
||||||
|
libyuv-dev \
|
||||||
|
libasound2-dev \
|
||||||
|
libspeex-dev \
|
||||||
|
libspeexdsp-dev \
|
||||||
|
libusb-1.0-0-dev \
|
||||||
|
libldap2-dev \
|
||||||
|
libsasl2-dev \
|
||||||
|
libdrm-dev \
|
||||||
|
mesa-va-drivers \
|
||||||
|
mesa-vdpau-drivers \
|
||||||
|
v4l-utils \
|
||||||
|
libv4l-dev \
|
||||||
|
ffmpeg \
|
||||||
|
libavcodec-dev \
|
||||||
|
libavformat-dev \
|
||||||
|
libavutil-dev \
|
||||||
|
libswscale-dev \
|
||||||
|
libavfilter-dev \
|
||||||
|
libavdevice-dev \
|
||||||
|
&& if [ ${TARGETARCH} != arm ] && [ ${TARGETARCH} != arm64 ]; then \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
vainfo \
|
||||||
|
libva-dev \
|
||||||
|
libva-drm2 \
|
||||||
|
libva-x11-2 \
|
||||||
|
intel-media-va-driver \
|
||||||
|
i965-va-driver; \
|
||||||
|
fi \
|
||||||
|
&& if [ ${TARGETARCH} = arm64 ]; then \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ninja-build \
|
||||||
|
zlib1g-dev \
|
||||||
|
libswresample-dev; \
|
||||||
|
fi \
|
||||||
|
&& apt clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY build/cargo_config /tmp/config
|
COPY build/cargo_config /tmp/config
|
||||||
|
|
||||||
|
# 配置 pip 源并安装 Python 依赖
|
||||||
RUN --security=insecure pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \
|
RUN --security=insecure pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \
|
||||||
&& if [ ${TARGETARCH} = arm ]; then \
|
&& if [ ${TARGETARCH} = arm ]; then \
|
||||||
mkdir -p /root/.cargo \
|
mkdir -p /root/.cargo \
|
||||||
&& chmod 777 /root/.cargo && mount -t tmpfs none /root/.cargo \
|
&& chmod 777 /root/.cargo && mount -t tmpfs none /root/.cargo \
|
||||||
&& export RUSTUP_DIST_SERVER="https://mirrors.tuna.tsinghua.edu.cn/rustup" \
|
&& wget https://sh.rustup.rs -O /root/rustup-init.sh \
|
||||||
#&& export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" \
|
|
||||||
&& wget https://sh.rustup.rs -O /root/rustup-init.sh \
|
|
||||||
&& sh /root/rustup-init.sh -y \
|
&& sh /root/rustup-init.sh -y \
|
||||||
&& export PATH=$PATH:/root/.cargo/bin \
|
&& export PATH=$PATH:/root/.cargo/bin \
|
||||||
&& cp /tmp/config /root/.cargo/config.toml; \
|
&& cp /tmp/config /root/.cargo/config.toml; \
|
||||||
fi \
|
fi \
|
||||||
&& pip wheel --wheel-dir=/tmp/wheel/ cryptography
|
&& pip install --root-user-action=ignore --disable-pip-version-check --upgrade --break-system-packages build setuptools pip \
|
||||||
|
&& pip wheel --wheel-dir=/tmp/wheel/ cryptography \
|
||||||
|
&& pip wheel --wheel-dir=/tmp/wheel/ \
|
||||||
|
aiofiles aiohttp appdirs asn1crypto async_lru async-timeout bottle cffi \
|
||||||
|
chardet click colorama dbus_next gpiod hidapi idna mako marshmallow \
|
||||||
|
more-itertools multidict netifaces packaging passlib pillow ply psutil \
|
||||||
|
pycparser pyelftools pyghmi pygments pyparsing pyotp qrcode requests \
|
||||||
|
semantic-version setproctitle six spidev tabulate urllib3 wrapt xlib \
|
||||||
|
yarl pyserial pyyaml zstandard supervisor pyfatfs pyserial python-periphery \
|
||||||
|
python-ldap python-pam pyrad pyudev pyusb luma.oled pyserial-asyncio \
|
||||||
|
&& rm -rf /root/.cache/pip/* /tmp/pip-* \
|
||||||
|
&& if [ ${TARGETARCH} = arm ]; then \
|
||||||
|
umount /root/.cargo 2>/dev/null || true \
|
||||||
|
&& rm -rf /root/.cargo /root/rustup-init.sh; \
|
||||||
|
fi
|
||||||
|
|
||||||
RUN pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check build \
|
# 编译 python evdev库
|
||||||
&& pip wheel --wheel-dir=/tmp/wheel/ aiofiles aiohttp appdirs asn1crypto async_lru async-timeout bottle cffi chardet click colorama \
|
RUN git clone --depth=1 https://github.com/gvalkov/python-evdev.git /tmp/python-evdev \
|
||||||
dbus_next gpiod hidapi idna mako marshmallow more-itertools multidict netifaces packaging passlib pillow ply psutil pycparser \
|
&& cd /tmp/python-evdev \
|
||||||
pyelftools pyghmi pygments pyparsing pyotp qrcode requests semantic-version setproctitle setuptools six spidev \
|
&& python3 setup.py bdist_wheel --dist-dir /tmp/wheel/ \
|
||||||
tabulate urllib3 wrapt xlib yarl pyserial pyyaml zstandard supervisor
|
&& rm -rf /tmp/python-evdev
|
||||||
|
|
||||||
RUN git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \
|
# 编译安装 libnice、libsrtp、libwebsockets 和 janus-gateway(显式 Release 与按架构优化)
|
||||||
|
RUN export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
|
||||||
|
&& if [ "${TARGETARCH}" = arm64 ]; then export CFLAGS="$COMMON_CFLAGS -march=armv8-a"; \
|
||||||
|
elif [ "${TARGETARCH}" = arm ]; then export CFLAGS="$COMMON_CFLAGS -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -mtune=cortex-a7"; \
|
||||||
|
else export CFLAGS="$COMMON_CFLAGS -march=x86-64 -mtune=generic"; fi \
|
||||||
|
&& export CXXFLAGS="$CFLAGS" LDFLAGS="-Wl,-O1 -Wl,--as-needed" \
|
||||||
|
&& git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \
|
||||||
&& cd /tmp/libnice \
|
&& cd /tmp/libnice \
|
||||||
&& meson --prefix=/usr build && ninja -C build && ninja -C build install
|
&& meson setup build --prefix=/usr --buildtype=release -Doptimization=2 -Dc_args="$CFLAGS" -Dcpp_args="$CXXFLAGS" \
|
||||||
|
&& ninja -C build && ninja -C build install \
|
||||||
RUN curl https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -L -o /tmp/libsrtp-2.2.0.tar.gz \
|
&& rm -rf /tmp/libnice \
|
||||||
|
&& curl https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -L -o /tmp/libsrtp-2.2.0.tar.gz \
|
||||||
&& cd /tmp \
|
&& cd /tmp \
|
||||||
&& tar xfv libsrtp-2.2.0.tar.gz \
|
&& tar xf libsrtp-2.2.0.tar.gz \
|
||||||
&& cd libsrtp-2.2.0 \
|
&& cd libsrtp-2.2.0 \
|
||||||
&& ./configure --prefix=/usr --enable-openssl \
|
&& CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure --prefix=/usr --enable-openssl \
|
||||||
&& make shared_library && make install
|
&& make shared_library -j$(nproc) && make install \
|
||||||
|
&& cd /tmp \
|
||||||
RUN git clone --depth=1 https://libwebsockets.org/repo/libwebsockets /tmp/libwebsockets \
|
&& rm -rf /tmp/libsrtp* \
|
||||||
|
&& git clone --depth=1 https://github.com/warmcat/libwebsockets /tmp/libwebsockets \
|
||||||
&& cd /tmp/libwebsockets \
|
&& cd /tmp/libwebsockets \
|
||||||
&& mkdir build && cd build \
|
&& mkdir build && cd build \
|
||||||
&& cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_C_FLAGS="-fpic" .. \
|
&& cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr \
|
||||||
&& make && make install
|
-DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="$CFLAGS -fPIC" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS -fPIC" .. \
|
||||||
|
&& make -j$(nproc) && make install \
|
||||||
RUN git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \
|
&& cd /tmp \
|
||||||
|
&& rm -rf /tmp/libwebsockets \
|
||||||
|
&& git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \
|
||||||
&& cd /tmp/janus-gateway \
|
&& cd /tmp/janus-gateway \
|
||||||
&& sh autogen.sh \
|
&& sh autogen.sh \
|
||||||
&& ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \
|
&& CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \
|
||||||
--disable-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins --disable-all-loggers \
|
--disable-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins \
|
||||||
--prefix=/usr \
|
--disable-all-loggers --prefix=/usr \
|
||||||
&& make && make install
|
&& make -j$(nproc) && make install \
|
||||||
|
&& cd /tmp \
|
||||||
|
&& rm -rf /tmp/janus-gateway
|
||||||
|
|
||||||
|
# 编译 Rockchip MPP、RGA(仅 arm64,显式 Release 与按架构优化)
|
||||||
|
RUN if [ ${TARGETARCH} = arm64 ]; then \
|
||||||
|
export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
|
||||||
|
&& export CFLAGS="$COMMON_CFLAGS -march=armv8-a" \
|
||||||
|
&& export CXXFLAGS="$CFLAGS" \
|
||||||
|
&& git clone --depth=1 https://github.com/rockchip-linux/mpp.git /tmp/rkmpp \
|
||||||
|
&& mkdir -p /tmp/rkmpp/rkmpp_build && cd /tmp/rkmpp/rkmpp_build \
|
||||||
|
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DBUILD_TEST=OFF \
|
||||||
|
-DCMAKE_C_FLAGS_RELEASE="$CFLAGS" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS" .. \
|
||||||
|
&& make -j$(nproc) \
|
||||||
|
&& make install \
|
||||||
|
&& git clone -b jellyfin-rga --depth=1 https://github.com/nyanmisaka/rk-mirrors.git /tmp/rkrga \
|
||||||
|
&& cd /tmp/ \
|
||||||
|
&& meson setup rkrga rkrga_build --prefix=/usr --libdir=lib --buildtype=release -Doptimization=2 \
|
||||||
|
-Dc_args="$CFLAGS" -Dcpp_args="$CXXFLAGS -fpermissive" -Dlibdrm=false -Dlibrga_demo=false \
|
||||||
|
&& meson configure rkrga_build > /dev/null \
|
||||||
|
&& ninja -C rkrga_build install \
|
||||||
|
&& rm -rf /tmp/rkmpp /tmp/rkrga; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 编译 ustreamer(按架构优化)
|
||||||
RUN sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h \
|
RUN sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h \
|
||||||
&& git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \
|
&& git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \
|
||||||
&& make -j WITH_PYTHON=1 WITH_JANUS=1 WITH_LIBX264=1 -C /tmp/ustreamer \
|
&& export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
|
||||||
&& /tmp/ustreamer/ustreamer -v
|
&& if [ "${TARGETARCH}" = arm64 ]; then export CFLAGS="$COMMON_CFLAGS -march=armv8-a"; \
|
||||||
|
elif [ "${TARGETARCH}" = arm ]; then export CFLAGS="$COMMON_CFLAGS -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -mtune=cortex-a7"; \
|
||||||
|
else export CFLAGS="$COMMON_CFLAGS -march=x86-64 -mtune=generic"; fi \
|
||||||
|
&& export CXXFLAGS="$CFLAGS" \
|
||||||
|
&& if [ ${TARGETARCH} = arm64 ]; then \
|
||||||
|
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_MPP=1 WITH_DRM=1 -C /tmp/ustreamer; \
|
||||||
|
else \
|
||||||
|
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_DRM=1 -C /tmp/ustreamer; \
|
||||||
|
fi \
|
||||||
|
&& /tmp/ustreamer/ustreamer -v \
|
||||||
|
&& /tmp/ustreamer/ustreamer-dump -v \
|
||||||
|
&& cp /tmp/ustreamer/python/dist/*.whl /tmp/wheel/
|
||||||
|
|
||||||
|
# 复制必要的库文件
|
||||||
RUN mkdir /tmp/lib \
|
RUN mkdir /tmp/lib \
|
||||||
&& cd /lib/*-linux-*/ \
|
&& cd /lib/*-linux-*/ \
|
||||||
&& cp libevent_core-*.so.7 libbsd.so.0 libevent_pthreads-*.so.7 libspeexdsp.so.1 libevent-*.so.7 libjpeg.so.62 libx264.so.164 libyuv.so.0 \
|
&& cp libevent_core-*.so.* libbsd.so.* libevent_pthreads-*.so.* libspeexdsp.so.* \
|
||||||
libnice.so.10 /usr/lib/libsrtp2.so.1 /usr/lib/libwebsockets.so.19 \
|
libevent-*.so.* libjpeg.so.* libyuv.so.* libnice.so.* \
|
||||||
/tmp/lib/ \
|
/tmp/lib/ \
|
||||||
&& cp /tmp/ustreamer/python/dist/*.whl /tmp/wheel/
|
&& find /usr/lib -name "libsrtp2.so.*" -exec cp {} /tmp/lib/ \; \
|
||||||
|
&& find /usr/lib -name "libwebsockets.so.*" -exec cp {} /tmp/lib/ \; \
|
||||||
|
&& [ "${TARGETARCH}" = "arm64" ] && \
|
||||||
|
find /usr/lib -name "libsw*.so.*" -exec cp {} /tmp/lib/ \; && \
|
||||||
|
find /usr/lib -name "libpostproc.so.*" -exec cp {} /tmp/lib/ \; && \
|
||||||
|
find /usr/lib -name "librockchip*" -exec cp {} /tmp/lib/ \; && \
|
||||||
|
find /usr/lib -name "librga.so.*" -exec cp {} /tmp/lib/ \; || true
|
||||||
|
|||||||
303
build/build_img.sh
Normal file → Executable file
303
build/build_img.sh
Normal file → Executable file
@ -1,129 +1,208 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
#File List
|
# --- 配置 ---
|
||||||
#src
|
# 允许通过环境变量覆盖默认路径
|
||||||
#└── image
|
SRCPATH="${SRCPATH:-/mnt/src}"
|
||||||
# ├── cumebox2
|
BOOTFS="${BOOTFS:-/tmp/bootfs}"
|
||||||
# │ └── Armbian_24.8.1_Khadas-vim1_bookworm_current_6.6.47_minimal.img
|
ROOTFS="${ROOTFS:-/tmp/rootfs}"
|
||||||
# └── onecloud
|
OUTPUTDIR="${OUTPUTDIR:-/mnt/output}"
|
||||||
# ├── AmlImg_v0.3.1_linux_amd64
|
TMPDIR="${TMPDIR:-$SRCPATH/tmp}"
|
||||||
# ├── Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal.burn.img
|
|
||||||
# └── rc.local
|
|
||||||
|
|
||||||
#预处理镜像文件
|
# 远程文件下载配置
|
||||||
SRCPATH=../src
|
REMOTE_PREFIX="${REMOTE_PREFIX:-https://files.mofeng.run/src}"
|
||||||
ROOTFS=/tmp/rootfs
|
|
||||||
$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64 unpack $SRCPATH/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal.burn.img $SRCPATH/tmp
|
|
||||||
simg2img $SRCPATH/tmp/7.rootfs.PARTITION.sparse $SRCPATH/tmp/rootfs.img
|
|
||||||
dd if=/dev/zero of=/tmp/add.img bs=1M count=800 && cat /tmp/add.img >> $SRCPATH/tmp/rootfs.img && rm /tmp/add.img
|
|
||||||
e2fsck -f $SRCPATH/tmp/rootfs.img && resize2fs $SRCPATH/tmp/rootfs.img
|
|
||||||
|
|
||||||
#挂载镜像文件
|
export LC_ALL=C
|
||||||
mkdir $ROOTFS
|
|
||||||
sudo mount $SRCPATH/tmp/rootfs.img $ROOTFS || exit -1
|
|
||||||
sudo mount -t proc proc $ROOTFS/proc || exit -1
|
|
||||||
sudo mount -t sysfs sys $ROOTFS/sys || exit -1
|
|
||||||
sudo mount -o bind /dev $ROOTFS/dev || exit -1
|
|
||||||
|
|
||||||
#准备文件
|
# 全局变量
|
||||||
sudo mkdir -p $ROOTFS/etc/kvmd/override.d $ROOTFS/etc/kvmd/vnc $ROOTFS/var/lib/kvmd/msd $ROOTFS/opt/vc/bin $ROOTFS/usr/share/kvmd \
|
LOOPDEV=""
|
||||||
$ROOTFS/usr/share/janus/javascript $ROOTFS/usr/lib/ustreamer/janus $ROOTFS/run/kvmd $ROOTFS/var/lib/kvmd/msd/images $ROOTFS/var/lib/kvmd/msd/meta
|
ROOTFS_MOUNTED=0
|
||||||
sudo cp -r ../One-KVM $ROOTFS/
|
BOOTFS_MOUNTED=0
|
||||||
sudo cp $SRCPATH/image/onecloud/rc.local $ROOTFS/etc/
|
PROC_MOUNTED=0
|
||||||
sudo cp -r $ROOTFS/One-KVM/configs/kvmd/* $ROOTFS/One-KVM/configs/nginx $ROOTFS/One-KVM/configs/janus \
|
SYS_MOUNTED=0
|
||||||
$ROOTFS/etc/kvmd
|
DEV_MOUNTED=0
|
||||||
sudo cp -r $ROOTFS/One-KVM/web $ROOTFS/One-KVM/extras $ROOTFS/One-KVM/contrib/keymaps $ROOTFS/usr/share/kvmd
|
DOCKER_CONTAINER_NAME="to_build_rootfs_$$"
|
||||||
sudo cp $ROOTFS/One-KVM/build/platform/onecloud $ROOTFS/usr/share/kvmd/platform
|
PREBUILT_DIR="/tmp/prebuilt_binaries"
|
||||||
sudo cp $ROOTFS/One-KVM/testenv/fakes/vcgencmd $ROOTFS/usr/bin/
|
|
||||||
sudo cp -r $ROOTFS/One-KVM/testenv/js/* $ROOTFS/usr/share/janus/javascript/
|
|
||||||
|
|
||||||
#安装依赖
|
# --- 引入模块化脚本 ---
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
apt update \
|
source "$SCRIPT_DIR/functions/common.sh"
|
||||||
&& apt install -y python3-aiofiles python3-aiohttp python3-appdirs python3-asn1crypto python3-async-timeout \
|
source "$SCRIPT_DIR/functions/devices.sh"
|
||||||
python3-bottle python3-cffi python3-chardet python3-click python3-colorama python3-cryptography python3-dateutil \
|
source "$SCRIPT_DIR/functions/install.sh"
|
||||||
python3-dbus python3-dev python3-hidapi python3-idna python3-libgpiod python3-mako python3-marshmallow python3-more-itertools \
|
source "$SCRIPT_DIR/functions/packaging.sh"
|
||||||
python3-multidict python3-netifaces python3-packaging python3-passlib python3-pillow python3-ply python3-psutil \
|
|
||||||
python3-pycparser python3-pyelftools python3-pyghmi python3-pygments python3-pyparsing python3-requests \
|
|
||||||
python3-semantic-version python3-setproctitle python3-setuptools python3-six python3-spidev python3-systemd \
|
|
||||||
python3-tabulate python3-urllib3 python3-wrapt python3-xlib python3-yaml python3-yarl python3-pyotp python3-qrcode \
|
|
||||||
python3-serial python3-zstandard python3-dbus-next \
|
|
||||||
&& apt install -y nginx python3-pip python3-dev python3-build net-tools tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim \
|
|
||||||
git gpiod libxkbcommon0 build-essential janus-dev libssl-dev libffi-dev libevent-dev libjpeg-dev libbsd-dev libudev-dev \
|
|
||||||
pkg-config libx264-dev libyuv-dev libasound2-dev libsndfile-dev libspeexdsp-dev cpufrequtils iptables\
|
|
||||||
&& apt clean "
|
|
||||||
|
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
# 获取日期与Git版本
|
||||||
pip3 config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple \
|
GIT_COMMIT_ID=$(get_git_commit_id)
|
||||||
&& pip3 install --target=/usr/lib/python3/dist-packages --break-system-packages async-lru gpiod \
|
DATE=$(date +%y%m%d)
|
||||||
&& pip3 cache purge "
|
if [ -n "$GIT_COMMIT_ID" ]; then
|
||||||
|
DATE="${DATE}-${GIT_COMMIT_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
sudo chroot --userspec "root:root" $ROOTFS sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h
|
# --- 注册清理函数 ---
|
||||||
|
# 在脚本退出、收到错误信号、中断信号、终止信号时执行 cleanup
|
||||||
|
trap cleanup EXIT ERR INT TERM
|
||||||
|
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
# --- 构建流程函数 ---
|
||||||
git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \
|
|
||||||
&& make -j WITH_PYTHON=1 WITH_JANUS=1 WITH_LIBX264=1 -C /tmp/ustreamer \
|
|
||||||
&& mv /tmp/ustreamer/src/ustreamer.bin /usr/bin/ustreamer \
|
|
||||||
&& mv /tmp/ustreamer/src/ustreamer-dump.bin /usr/bin/ustreamer-dump \
|
|
||||||
&& chmod +x /usr/bin/ustreamer /usr/bin/ustreamer-dump \
|
|
||||||
&& mv /tmp/ustreamer/janus/libjanus_ustreamer.so /usr/lib/ustreamer/janus \
|
|
||||||
&& pip3 install --target=/usr/lib/python3/dist-packages --break-system-packages /tmp/ustreamer/python/dist/*.whl "
|
|
||||||
|
|
||||||
#安装 kvmd 主程序
|
build_target() {
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
local target="$1"
|
||||||
cd /One-KVM \
|
local build_time=$(date "+%Y-%m-%d %H:%M:%S")
|
||||||
&& python3 setup.py install \
|
echo "=================================================="
|
||||||
&& bash scripts/kvmd-gencert --do-the-thing \
|
echo "信息:构建目标: $target"
|
||||||
&& bash scripts/kvmd-gencert --do-the-thing --vnc \
|
echo "信息:构建时间: $build_time"
|
||||||
&& kvmd-nginx-mkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf \
|
echo "=================================================="
|
||||||
&& kvmd -m "
|
|
||||||
|
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
# 设置全局变量,供后续函数使用
|
||||||
curl https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.armhf -L -o /usr/bin/ttyd \
|
TARGET_DEVICE_NAME="$target"
|
||||||
&& chmod +x /usr/bin/ttyd \
|
NEED_PREPARE_DNS=false # 默认不需要准备 DNS
|
||||||
&& systemd-sysusers /One-KVM/configs/os/kvmd-webterm.conf \
|
|
||||||
&& mkdir -p /home/kvmd-webterm \
|
|
||||||
&& chown kvmd-webterm /home/kvmd-webterm "
|
|
||||||
|
|
||||||
|
case "$target" in
|
||||||
|
onecloud)
|
||||||
|
onecloud_rootfs
|
||||||
|
local arch="armhf"
|
||||||
|
local device_type="gpio-onecloud"
|
||||||
|
local network_type="systemd-networkd"
|
||||||
|
;;
|
||||||
|
cumebox2)
|
||||||
|
cumebox2_rootfs
|
||||||
|
local arch="aarch64"
|
||||||
|
local device_type="video1"
|
||||||
|
local network_type="" # 默认 NetworkManager
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
chainedbox)
|
||||||
|
chainedbox_rootfs_and_fix_dtb
|
||||||
|
local arch="aarch64"
|
||||||
|
local device_type="video1"
|
||||||
|
local network_type=""
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
vm)
|
||||||
|
vm_rootfs
|
||||||
|
local arch="amd64"
|
||||||
|
local device_type=""
|
||||||
|
local network_type=""
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
e900v22c)
|
||||||
|
e900v22c_rootfs
|
||||||
|
local arch="aarch64"
|
||||||
|
local device_type="video1"
|
||||||
|
local network_type=""
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
octopus-flanet)
|
||||||
|
octopus_flanet_rootfs
|
||||||
|
local arch="aarch64"
|
||||||
|
local device_type="video1"
|
||||||
|
local network_type=""
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
onecloud-pro)
|
||||||
|
onecloud_pro_rootfs
|
||||||
|
local arch="aarch64"
|
||||||
|
local device_type="gpio-onecloud-pro video1"
|
||||||
|
local network_type="systemd-networkd"
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
orangepi-zero)
|
||||||
|
orangepizero_rootfs
|
||||||
|
local arch="armhf"
|
||||||
|
local device_type=""
|
||||||
|
local network_type=""
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
oec-turbo)
|
||||||
|
oec_turbo_rootfs
|
||||||
|
local arch="aarch64"
|
||||||
|
local device_type="vpu"
|
||||||
|
local network_type=""
|
||||||
|
NEED_PREPARE_DNS=true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "错误:未知或不支持的目标 '$target'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
#服务自启
|
mount_rootfs
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
|
||||||
cat /One-KVM/configs/os/sudoers/v2-hdmiusb >> /etc/sudoers \
|
|
||||||
&& cat /One-KVM/configs/os/udev/v2-hdmiusb-generic.rules > /etc/udev/rules.d/99-kvmd.rules \
|
|
||||||
&& echo 'libcomposite' >> /etc/modules \
|
|
||||||
&& mv /usr/local/bin/kvmd* /usr/bin \
|
|
||||||
&& cp /One-KVM/configs/os/services/* /etc/systemd/system/ \
|
|
||||||
&& cp /One-KVM/configs/os/tmpfiles.conf /usr/lib/tmpfiles.d/ \
|
|
||||||
&& chmod +x /etc/update-motd.d/* \
|
|
||||||
&& echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/gpio.sh' >> /etc/sudoers \
|
|
||||||
&& echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/usbrelay_hid.sh' >> /etc/sudoers \
|
|
||||||
&& systemd-sysusers /One-KVM/configs/os/sysusers.conf \
|
|
||||||
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
|
|
||||||
&& sed -i 's/ch9329/otg/g' /etc/kvmd/override.yaml \
|
|
||||||
&& sed -i 's/device: \/dev\/ttyUSB0//g' /etc/kvmd/override.yaml \
|
|
||||||
&& sed -i 's/8080/80/g' /etc/kvmd/override.yaml \
|
|
||||||
&& sed -i 's/4430/443/g' /etc/kvmd/override.yaml \
|
|
||||||
&& sed -i 's/#type: otg/type: otg/g' /etc/kvmd/override.yaml \
|
|
||||||
&& chown kvmd -R /var/lib/kvmd/msd/ \
|
|
||||||
&& sed -i 's/localhost.localdomain/onecloud/g' /etc/kvmd/meta.yaml \
|
|
||||||
&& systemctl enable kvmd kvmd-otg kvmd-nginx kvmd-vnc kvmd-ipmi kvmd-webterm kvmd-janus \
|
|
||||||
&& systemctl disable nginx janus \
|
|
||||||
&& rm -r /One-KVM "
|
|
||||||
|
|
||||||
|
install_and_configure_kvmd "$arch" "$device_type" "$network_type"
|
||||||
|
|
||||||
sudo chroot --userspec "root:root" $ROOTFS bash -c " \
|
write_meta "$target"
|
||||||
sed -i '2c ATX=GPIO' /etc/kvmd/atx.sh \
|
|
||||||
&& sed -i 's/SHUTDOWNPIN/gpiochip1 7/g' /etc/kvmd/custom_atx/gpio.sh \
|
|
||||||
&& sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh "
|
|
||||||
|
|
||||||
#卸载镜像
|
unmount_all
|
||||||
sudo umount $ROOTFS/sys
|
|
||||||
sudo umount $ROOTFS/dev
|
|
||||||
sudo umount $ROOTFS/proc
|
|
||||||
sudo umount $ROOTFS
|
|
||||||
|
|
||||||
#打包镜像
|
case "$target" in
|
||||||
sudo rm $SRCPATH/tmp/7.rootfs.PARTITION.sparse
|
onecloud)
|
||||||
sudo img2simg $SRCPATH/tmp/rootfs.img $SRCPATH/tmp/7.rootfs.PARTITION.sparse
|
pack_img_onecloud
|
||||||
sudo $SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64 pack $SRCPATH/output/One-KVM_by-SilentWind_Onecloud_241004.burn.img $SRCPATH/tmp/
|
;;
|
||||||
sudo rm $SRCPATH/tmp/*
|
vm)
|
||||||
|
pack_img "Vm"
|
||||||
|
;;
|
||||||
|
cumebox2)
|
||||||
|
pack_img "Cumebox2"
|
||||||
|
;;
|
||||||
|
chainedbox)
|
||||||
|
pack_img "Chainedbox"
|
||||||
|
;;
|
||||||
|
e900v22c)
|
||||||
|
pack_img "E900v22c"
|
||||||
|
;;
|
||||||
|
octopus-flanet)
|
||||||
|
pack_img "Octopus-Flanet"
|
||||||
|
;;
|
||||||
|
onecloud-pro)
|
||||||
|
pack_img "Onecloud-Pro"
|
||||||
|
;;
|
||||||
|
orangepi-zero)
|
||||||
|
pack_img "Orangepi-Zero"
|
||||||
|
;;
|
||||||
|
oec-turbo)
|
||||||
|
pack_img "OEC-Turbo"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "错误:未知的打包类型 for '$target'" >&2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 在 GitHub Actions 环境中清理下载的文件
|
||||||
|
cleanup_downloaded_files
|
||||||
|
|
||||||
|
echo "=================================================="
|
||||||
|
echo "信息:目标 $target 构建完成!"
|
||||||
|
echo "=================================================="
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 主逻辑 ---
|
||||||
|
|
||||||
|
# 检查是否提供了目标参数
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "用法: $0 <target|all>"
|
||||||
|
echo "可用目标: onecloud, cumebox2, chainedbox, vm, e900v22c, octopus-flanet, onecloud-pro, orangepi-zero, oec-turbo"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置脚本立即退出模式
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# 检查必要的外部工具
|
||||||
|
check_required_tools "$1"
|
||||||
|
|
||||||
|
# 执行构建
|
||||||
|
if [ "$1" = "all" ]; then
|
||||||
|
echo "信息:开始构建所有目标..."
|
||||||
|
build_target "onecloud"
|
||||||
|
build_target "cumebox2"
|
||||||
|
build_target "chainedbox"
|
||||||
|
build_target "vm"
|
||||||
|
build_target "e900v22c"
|
||||||
|
build_target "octopus-flanet"
|
||||||
|
build_target "onecloud-pro"
|
||||||
|
build_target "orangepi-zero"
|
||||||
|
build_target "oec-turbo"
|
||||||
|
echo "信息:所有目标构建完成。"
|
||||||
|
else
|
||||||
|
build_target "$1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
313
build/functions/common.sh
Executable file
313
build/functions/common.sh
Executable file
@ -0,0 +1,313 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- 辅助函数 ---
|
||||||
|
|
||||||
|
# 获取 Git 提交 ID
|
||||||
|
get_git_commit_id() {
|
||||||
|
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||||
|
git rev-parse --short HEAD 2>/dev/null || echo ""
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 查找并设置一个可用的 loop 设备
|
||||||
|
find_loop_device() {
|
||||||
|
echo "信息:查找可用的 loop 设备..."
|
||||||
|
# 只使用 --find 来获取设备名
|
||||||
|
LOOPDEV=$(sudo losetup --find)
|
||||||
|
if [[ -z "$LOOPDEV" || ! -e "$LOOPDEV" ]]; then
|
||||||
|
echo "错误:再次尝试后仍无法找到可用的 loop 设备。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "信息:找到可用 loop 设备名:$LOOPDEV"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查并创建目录
|
||||||
|
ensure_dir() {
|
||||||
|
if [[ ! -d "$1" ]]; then
|
||||||
|
echo "信息:创建目录 $1 ..."
|
||||||
|
sudo mkdir -p "$1" || { echo "错误:创建目录 $1 失败" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 执行 chroot 命令
|
||||||
|
run_in_chroot() {
|
||||||
|
echo "信息:在 chroot 环境 ($ROOTFS) 中执行命令..."
|
||||||
|
sudo chroot --userspec "root:root" "$ROOTFS" bash -ec "$1" || { echo "错误:在 chroot 环境中执行命令失败" >&2; exit 1; }
|
||||||
|
echo "信息:chroot 命令执行完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 清理函数 ---
|
||||||
|
cleanup() {
|
||||||
|
echo "信息:执行清理操作..."
|
||||||
|
# 尝试卸载 chroot 环境下的挂载点
|
||||||
|
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS/dev ..."
|
||||||
|
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
|
||||||
|
DEV_MOUNTED=0
|
||||||
|
fi
|
||||||
|
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS/sys ..."
|
||||||
|
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
|
||||||
|
SYS_MOUNTED=0
|
||||||
|
fi
|
||||||
|
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS/proc ..."
|
||||||
|
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
|
||||||
|
PROC_MOUNTED=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 尝试卸载主根文件系统
|
||||||
|
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS ..."
|
||||||
|
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
|
||||||
|
ROOTFS_MOUNTED=0
|
||||||
|
fi
|
||||||
|
# 尝试卸载引导文件系统 (如果使用)
|
||||||
|
if [[ "$BOOTFS_MOUNTED" -eq 1 && -d "$BOOTFS" ]]; then
|
||||||
|
echo "信息:卸载 $BOOTFS ..."
|
||||||
|
sudo umount "$BOOTFS" || sudo umount -l "$BOOTFS" || echo "警告:卸载 $BOOTFS 失败"
|
||||||
|
BOOTFS_MOUNTED=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 尝试分离 loop 设备
|
||||||
|
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
|
||||||
|
echo "信息:尝试 zerofree $LOOPDEV ..."
|
||||||
|
sudo zerofree "$LOOPDEV" || echo "警告:zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
|
||||||
|
echo "信息:分离 loop 设备 $LOOPDEV ..."
|
||||||
|
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
|
||||||
|
LOOPDEV=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 尝试删除 Docker 容器
|
||||||
|
echo "信息:检查并删除 Docker 容器 $DOCKER_CONTAINER_NAME ..."
|
||||||
|
if sudo docker ps -a --format '{{.Names}}' | grep -q "^${DOCKER_CONTAINER_NAME}$"; then
|
||||||
|
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || echo "警告:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败"
|
||||||
|
else
|
||||||
|
echo "信息:Docker 容器 $DOCKER_CONTAINER_NAME 不存在或已被删除。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时目录和挂载点目录
|
||||||
|
echo "信息:清理临时文件和目录..."
|
||||||
|
sudo rm -rf "$PREBUILT_DIR"
|
||||||
|
# 只删除挂载点目录本身
|
||||||
|
if [[ -d "$ROOTFS" ]]; then
|
||||||
|
sudo rmdir "$ROOTFS" || echo "警告:删除目录 $ROOTFS 失败,可能非空"
|
||||||
|
fi
|
||||||
|
if [[ -d "$BOOTFS" ]]; then
|
||||||
|
sudo rmdir "$BOOTFS" || echo "警告:删除目录 $BOOTFS 失败,可能非空"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:清理完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 在打包镜像前调用此函数,确保干净卸载所有挂载点和loop设备
|
||||||
|
unmount_all() {
|
||||||
|
echo "信息:执行卸载操作,准备打包..."
|
||||||
|
# 卸载 chroot 环境下的挂载点
|
||||||
|
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS/dev ..."
|
||||||
|
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
|
||||||
|
DEV_MOUNTED=0
|
||||||
|
fi
|
||||||
|
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS/sys ..."
|
||||||
|
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
|
||||||
|
SYS_MOUNTED=0
|
||||||
|
fi
|
||||||
|
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS/proc ..."
|
||||||
|
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
|
||||||
|
PROC_MOUNTED=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 卸载主根文件系统
|
||||||
|
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
|
||||||
|
echo "信息:卸载 $ROOTFS ..."
|
||||||
|
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
|
||||||
|
ROOTFS_MOUNTED=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 尝试分离 loop 设备前执行 zerofree(如果文件系统支持)
|
||||||
|
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
|
||||||
|
echo "信息:尝试 zerofree $LOOPDEV ..."
|
||||||
|
sudo zerofree "$LOOPDEV" || echo "警告:zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
|
||||||
|
echo "信息:分离 loop 设备 $LOOPDEV ..."
|
||||||
|
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
|
||||||
|
LOOPDEV=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo rm -rf "$PREBUILT_DIR"
|
||||||
|
|
||||||
|
echo "信息:卸载操作完成,可以安全打包镜像。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 挂载根文件系统
|
||||||
|
mount_rootfs() {
|
||||||
|
echo "信息:挂载根文件系统到 $ROOTFS ..."
|
||||||
|
ensure_dir "$ROOTFS"
|
||||||
|
sudo mount "$LOOPDEV" "$ROOTFS" || { echo "错误:挂载 $LOOPDEV 到 $ROOTFS 失败" >&2; exit 1; }
|
||||||
|
ROOTFS_MOUNTED=1
|
||||||
|
|
||||||
|
echo "信息:挂载 proc, sys, dev 到 chroot 环境..."
|
||||||
|
ensure_dir "$ROOTFS/proc"
|
||||||
|
sudo mount -t proc proc "$ROOTFS/proc" || { echo "错误:挂载 proc 到 $ROOTFS/proc 失败" >&2; exit 1; }
|
||||||
|
PROC_MOUNTED=1
|
||||||
|
|
||||||
|
ensure_dir "$ROOTFS/sys"
|
||||||
|
sudo mount -t sysfs sys "$ROOTFS/sys" || { echo "错误:挂载 sys 到 $ROOTFS/sys 失败" >&2; exit 1; }
|
||||||
|
SYS_MOUNTED=1
|
||||||
|
|
||||||
|
ensure_dir "$ROOTFS/dev"
|
||||||
|
sudo mount -o bind /dev "$ROOTFS/dev" || { echo "错误:绑定挂载 /dev 到 $ROOTFS/dev 失败" >&2; exit 1; }
|
||||||
|
DEV_MOUNTED=1
|
||||||
|
echo "信息:根文件系统及虚拟文件系统挂载完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 设置元数据
|
||||||
|
write_meta() {
|
||||||
|
local hostname="$1"
|
||||||
|
echo "信息:在 chroot 环境中设置主机名/元数据为 $hostname ..."
|
||||||
|
run_in_chroot "sed -i 's/localhost.localdomain/$hostname/g' /etc/kvmd/meta.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检测是否在 GitHub Actions 环境中
|
||||||
|
is_github_actions() {
|
||||||
|
[[ -n "$GITHUB_ACTIONS" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 记录下载的文件列表(仅在 GitHub Actions 环境中)
|
||||||
|
DOWNLOADED_FILES_LIST="/tmp/downloaded_files.txt"
|
||||||
|
|
||||||
|
# 自动下载文件函数
|
||||||
|
download_file_if_missing() {
|
||||||
|
local file_path="$1"
|
||||||
|
local relative_path=""
|
||||||
|
|
||||||
|
# 如果文件已存在,直接返回
|
||||||
|
if [[ -f "$file_path" ]]; then
|
||||||
|
echo "信息:文件已存在: $file_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 计算相对于 SRCPATH 的路径
|
||||||
|
if [[ "$file_path" == "$SRCPATH"/* ]]; then
|
||||||
|
relative_path="${file_path#$SRCPATH/}"
|
||||||
|
else
|
||||||
|
echo "错误:文件路径 $file_path 不在 SRCPATH ($SRCPATH) 下" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:文件不存在,尝试下载: $file_path"
|
||||||
|
echo "信息:相对路径: $relative_path"
|
||||||
|
|
||||||
|
# 确保目标目录存在
|
||||||
|
local target_dir="$(dirname "$file_path")"
|
||||||
|
ensure_dir "$target_dir"
|
||||||
|
|
||||||
|
# 首先尝试直接下载
|
||||||
|
local remote_url="${REMOTE_PREFIX}/${relative_path}"
|
||||||
|
echo "信息:尝试下载: $remote_url"
|
||||||
|
|
||||||
|
if curl -f -L -o "$file_path" "$remote_url" 2>/dev/null; then
|
||||||
|
echo "信息:下载成功: $file_path"
|
||||||
|
# 在 GitHub Actions 环境中记录下载的文件
|
||||||
|
if is_github_actions; then
|
||||||
|
echo "$file_path" >> "$DOWNLOADED_FILES_LIST"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 如果直接下载失败,尝试添加 .xz 后缀
|
||||||
|
echo "信息:直接下载失败,尝试 .xz 压缩版本..."
|
||||||
|
local xz_url="${remote_url}.xz"
|
||||||
|
local xz_file="${file_path}.xz"
|
||||||
|
|
||||||
|
if curl -f -L -o "$xz_file" "$xz_url" 2>/dev/null; then
|
||||||
|
echo "信息:下载 .xz 文件成功,正在解压..."
|
||||||
|
if xz -d "$xz_file"; then
|
||||||
|
echo "信息:解压成功: $file_path"
|
||||||
|
# 在 GitHub Actions 环境中记录下载的文件
|
||||||
|
if is_github_actions; then
|
||||||
|
echo "$file_path" >> "$DOWNLOADED_FILES_LIST"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "错误:解压 .xz 文件失败" >&2
|
||||||
|
rm -f "$xz_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "错误:无法下载文件 $file_path (尝试了原始版本和 .xz 版本)" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 下载 rc.local 文件
|
||||||
|
download_rc_local() {
|
||||||
|
local platform_id="$1"
|
||||||
|
local rc_local_path="$SRCPATH/image/$platform_id/rc.local"
|
||||||
|
local relative_path="image/$platform_id/rc.local"
|
||||||
|
local remote_url="$REMOTE_PREFIX/$relative_path"
|
||||||
|
|
||||||
|
echo "信息:检查是否需要下载 rc.local 文件 ($platform_id)..."
|
||||||
|
|
||||||
|
# 如果本地文件不存在,尝试下载
|
||||||
|
if [ ! -f "$rc_local_path" ]; then
|
||||||
|
echo "信息:本地 rc.local 文件不存在,尝试从远程下载..."
|
||||||
|
ensure_dir "$(dirname "$rc_local_path")"
|
||||||
|
|
||||||
|
if curl -sSL --fail "$remote_url" -o "$rc_local_path"; then
|
||||||
|
echo "信息:成功下载 rc.local 文件:$remote_url"
|
||||||
|
# 在 GitHub Actions 环境中记录下载的文件
|
||||||
|
if is_github_actions; then
|
||||||
|
echo "$rc_local_path" >> "$DOWNLOADED_FILES_LIST"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "信息:远程 rc.local 文件不存在或下载失败:$remote_url"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "信息:使用本地 rc.local 文件:$rc_local_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 清理下载的文件(仅在 GitHub Actions 环境中)
|
||||||
|
cleanup_downloaded_files() {
|
||||||
|
if is_github_actions && [[ -f "$DOWNLOADED_FILES_LIST" ]]; then
|
||||||
|
echo "信息:清理 GitHub Actions 环境中下载的文件..."
|
||||||
|
while IFS= read -r file_path; do
|
||||||
|
if [[ -f "$file_path" ]]; then
|
||||||
|
echo "信息:删除下载的文件: $file_path"
|
||||||
|
rm -f "$file_path"
|
||||||
|
fi
|
||||||
|
done < "$DOWNLOADED_FILES_LIST"
|
||||||
|
rm -f "$DOWNLOADED_FILES_LIST"
|
||||||
|
echo "信息:下载文件清理完成"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查必要的外部工具
|
||||||
|
check_required_tools() {
|
||||||
|
local required_tools="sudo docker losetup mount umount parted e2fsck resize2fs qemu-img curl tar python3 pip3 rsync git simg2img img2simg dd cat rm mkdir mv cp sed chmod chown ln grep printf id xz"
|
||||||
|
|
||||||
|
for cmd in $required_tools; do
|
||||||
|
if ! command -v "$cmd" &> /dev/null; then
|
||||||
|
echo "错误:必需的命令 '$cmd' 未找到。请安装相应软件包。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查特定工具 (如果脚本中使用了)
|
||||||
|
if ! command -v "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" &> /dev/null && [[ "$1" == "onecloud" || "$1" == "all" ]]; then
|
||||||
|
if [ -f "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" ]; then
|
||||||
|
echo "信息:找到 AmlImg 工具,尝试设置执行权限..."
|
||||||
|
sudo chmod +x "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" || echo "警告:设置 AmlImg 执行权限失败"
|
||||||
|
else
|
||||||
|
echo "错误:构建 onecloud 需要 '$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64',但未找到。" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
453
build/functions/devices.sh
Executable file
453
build/functions/devices.sh
Executable file
@ -0,0 +1,453 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- 设备特定的 Rootfs 准备函数 ---
|
||||||
|
|
||||||
|
onecloud_rootfs() {
|
||||||
|
local unpacker="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
|
||||||
|
local source_image="$SRCPATH/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal_support-dvd-emulation.burn.img"
|
||||||
|
local bootfs_img="$TMPDIR/bootfs.img"
|
||||||
|
local rootfs_img="$TMPDIR/rootfs.img"
|
||||||
|
local bootfs_sparse="$TMPDIR/6.boot.PARTITION.sparse"
|
||||||
|
local rootfs_sparse="$TMPDIR/7.rootfs.PARTITION.sparse"
|
||||||
|
local bootfs_loopdev="" # 存储 bootfs 使用的 loop 设备
|
||||||
|
local add_size_mb=600
|
||||||
|
|
||||||
|
echo "信息:准备 Onecloud Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"
|
||||||
|
ensure_dir "$BOOTFS"
|
||||||
|
|
||||||
|
# 自动下载 AmlImg 工具(如果不存在)
|
||||||
|
download_file_if_missing "$unpacker" || { echo "错误:下载 AmlImg 工具失败" >&2; exit 1; }
|
||||||
|
sudo chmod +x "$unpacker" || { echo "错误:设置 AmlImg 工具执行权限失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Onecloud 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:解包 Onecloud burn 镜像..."
|
||||||
|
sudo "$unpacker" unpack "$source_image" "$TMPDIR" || { echo "错误:解包失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:转换 bootfs 和 rootfs sparse 镜像到 raw 格式..."
|
||||||
|
sudo simg2img "$bootfs_sparse" "$bootfs_img" || { echo "错误:转换 bootfs sparse 镜像失败" >&2; exit 1; }
|
||||||
|
sudo simg2img "$rootfs_sparse" "$rootfs_img" || { echo "错误:转换 rootfs sparse 镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:挂载 bootfs 并修复 DTB..."
|
||||||
|
find_loop_device # 查找一个 loop 设备给 bootfs
|
||||||
|
bootfs_loopdev="$LOOPDEV" # 保存这个设备名
|
||||||
|
echo "信息:将 $bootfs_img 关联到 $bootfs_loopdev..."
|
||||||
|
sudo losetup "$bootfs_loopdev" "$bootfs_img" || { echo "错误:关联 bootfs 镜像到 $bootfs_loopdev 失败" >&2; exit 1; }
|
||||||
|
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 bootfs ($bootfs_loopdev) 失败" >&2; exit 1; }
|
||||||
|
BOOTFS_MOUNTED=1
|
||||||
|
|
||||||
|
# 自动下载 DTB 文件(如果不存在)
|
||||||
|
local dtb_file="$SRCPATH/image/onecloud/meson8b-onecloud-fix.dtb"
|
||||||
|
download_file_if_missing "$dtb_file" || { echo "错误:下载 Onecloud DTB 文件失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
sudo cp "$dtb_file" "$BOOTFS/dtb/meson8b-onecloud.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
|
||||||
|
sudo umount "$BOOTFS" || { echo "警告:卸载 bootfs ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; } # 卸载失败不应中断流程
|
||||||
|
BOOTFS_MOUNTED=0
|
||||||
|
echo "信息:分离 bootfs loop 设备 $bootfs_loopdev..."
|
||||||
|
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 bootfs loop 设备 $bootfs_loopdev 失败" >&2; }
|
||||||
|
# bootfs_loopdev 对应的设备现在是空闲的
|
||||||
|
|
||||||
|
echo "信息:扩展 rootfs 镜像 (${add_size_mb}MB)..."
|
||||||
|
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$rootfs_img" || { echo "错误:扩展 rootfs 镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:检查并调整 rootfs 文件系统大小 (在文件上)..."
|
||||||
|
# 注意:e2fsck/resize2fs 现在直接操作镜像文件,而不是 loop 设备
|
||||||
|
sudo e2fsck -f -y "$rootfs_img" || { echo "警告:e2fsck 检查 rootfs 镜像文件失败" >&2; exit 1; }
|
||||||
|
sudo resize2fs "$rootfs_img" || { echo "错误:resize2fs 调整 rootfs 镜像文件大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:设置 rootfs loop 设备..."
|
||||||
|
find_loop_device # 重新查找一个可用的 loop 设备 (可能是刚才释放的那个)
|
||||||
|
echo "信息:将 $rootfs_img 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup "$LOOPDEV" "$rootfs_img" || { echo "错误:关联 rootfs 镜像到 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Onecloud Rootfs 准备完成。 Loop 设备 $LOOPDEV 已关联 $rootfs_img"
|
||||||
|
}
|
||||||
|
|
||||||
|
cumebox2_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/cumebox2/Armbian_24.8.1_Khadas-vim1_bookworm_current_6.6.47_minimal.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local offset=$((8192 * 512))
|
||||||
|
local add_size_mb=900
|
||||||
|
|
||||||
|
echo "信息:准备 Cumebox2 Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Cumebox2 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 Cumebox2 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
|
||||||
|
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:调整镜像分区大小..."
|
||||||
|
sudo parted -s "$target_image" resizepart 1 100% || { echo "错误:使用 parted 调整分区大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:设置带偏移量的 loop 设备..."
|
||||||
|
find_loop_device # 查找设备名
|
||||||
|
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
|
||||||
|
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告:e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
sudo resize2fs "$LOOPDEV" || { echo "错误:resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Cumebox2 Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
chainedbox_rootfs_and_fix_dtb() {
|
||||||
|
local source_image="$SRCPATH/image/chainedbox/Armbian_24.11.0_rockchip_chainedbox_bookworm_6.1.112_server_2024.10.02_add800m.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local boot_offset=$((32768 * 512))
|
||||||
|
local rootfs_offset=$((1081344 * 512))
|
||||||
|
local bootfs_loopdev=""
|
||||||
|
|
||||||
|
echo "信息:准备 Chainedbox Rootfs 并修复 DTB..."
|
||||||
|
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Chainedbox 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 Chainedbox 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:挂载 boot 分区并修复 DTB..."
|
||||||
|
find_loop_device # 找 loop 给 boot
|
||||||
|
bootfs_loopdev="$LOOPDEV"
|
||||||
|
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
|
||||||
|
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
|
||||||
|
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
|
||||||
|
BOOTFS_MOUNTED=1
|
||||||
|
|
||||||
|
# 自动下载 DTB 文件(如果不存在)
|
||||||
|
local dtb_file="$SRCPATH/image/chainedbox/rk3328-l1pro-1296mhz-fix.dtb"
|
||||||
|
download_file_if_missing "$dtb_file" || { echo "错误:下载 Chainedbox DTB 文件失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
sudo cp "$dtb_file" "$BOOTFS/dtb/rockchip/rk3328-l1pro-1296mhz.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
|
||||||
|
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
|
||||||
|
BOOTFS_MOUNTED=0
|
||||||
|
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
|
||||||
|
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
|
||||||
|
|
||||||
|
echo "信息:设置 rootfs 分区的 loop 设备..."
|
||||||
|
find_loop_device # 找 loop 给 rootfs
|
||||||
|
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Chainedbox Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/vm/Armbian_25.2.1_Uefi-x86_bookworm_current_6.12.13_minimal.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local offset=$((540672 * 512))
|
||||||
|
|
||||||
|
echo "信息:准备 Vm Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Vm 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 Vm 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:设置带偏移量的 loop 设备..."
|
||||||
|
find_loop_device # 查找设备名
|
||||||
|
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Vm Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
e900v22c_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/e900v22c/Armbian_23.08.0_amlogic_s905l3a_bookworm_5.15.123_server_2023.08.01.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local offset=$((532480 * 512))
|
||||||
|
local add_size_mb=600
|
||||||
|
|
||||||
|
echo "信息:准备 E900V22C Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 E900V22C 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 E900V22C 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
|
||||||
|
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:调整镜像分区大小 (分区 2)..."
|
||||||
|
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:设置带偏移量的 loop 设备..."
|
||||||
|
find_loop_device # 查找设备名
|
||||||
|
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
|
||||||
|
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告:e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
sudo resize2fs "$LOOPDEV" || { echo "错误:resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:E900V22C Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
octopus_flanet_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/octopus-flanet/Armbian_25.05.0_amlogic_s912_bookworm_6.1.129_server_2025.03.02.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local boot_offset=$((8192 * 512))
|
||||||
|
local rootfs_offset=$((1056768 * 512))
|
||||||
|
local add_size_mb=600
|
||||||
|
local bootfs_loopdev=""
|
||||||
|
|
||||||
|
echo "信息:准备 Octopus-Planet Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Octopus-Planet 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 Octopus-Planet 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:挂载 boot 分区并修改 uEnv.txt (使用 VIM2 DTB)..."
|
||||||
|
find_loop_device # 找 loop 给 boot
|
||||||
|
bootfs_loopdev="$LOOPDEV"
|
||||||
|
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
|
||||||
|
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
|
||||||
|
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
|
||||||
|
BOOTFS_MOUNTED=1
|
||||||
|
|
||||||
|
# 自动下载 Octopus-Planet 相关文件
|
||||||
|
local dtb_file="$SRCPATH/image/octopus-flanet/meson-gxm-octopus-planet.dtb"
|
||||||
|
download_file_if_missing "$dtb_file" || echo "警告:下载 Octopus-Planet DTB 失败"
|
||||||
|
sudo cp "$dtb_file" "$BOOTFS/dtb/amlogic/meson-gxm-octopus-planet.dtb" || echo "警告:复制 Octopus-Planet DTB 失败"
|
||||||
|
|
||||||
|
sudo sed -i "s/meson-gxm-octopus-planet.dtb/meson-gxm-khadas-vim2.dtb/g" "$BOOTFS/uEnv.txt" || { echo "错误:修改 uEnv.txt 失败" >&2; exit 1; }
|
||||||
|
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
|
||||||
|
BOOTFS_MOUNTED=0
|
||||||
|
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
|
||||||
|
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
|
||||||
|
|
||||||
|
echo "信息:调整镜像分区大小 (分区 2)..."
|
||||||
|
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:设置 rootfs 分区的 loop 设备..."
|
||||||
|
find_loop_device # 找 loop 给 rootfs
|
||||||
|
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
|
||||||
|
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告:e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
sudo resize2fs "$LOOPDEV" || { echo "错误:resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Octopus-Planet Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
onecloud_pro_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/onecloud-pro/Armbian-by-SilentWind_24.5.0_amlogic_Onecloud-Pro_jammy_6.6.28_server.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local boot_offset=$((8192 * 512))
|
||||||
|
local rootfs_offset=$((1056768 * 512))
|
||||||
|
local add_size_mb=600
|
||||||
|
local bootfs_loopdev=""
|
||||||
|
|
||||||
|
echo "信息:准备 Octopus-Planet Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
|
||||||
|
|
||||||
|
# 自动下载源镜像文件(如果不存在)
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Octopus-Planet 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 Octopus-Planet 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:调整镜像分区大小 (分区 2)..."
|
||||||
|
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:设置 rootfs 分区的 loop 设备..."
|
||||||
|
find_loop_device # 找 loop 给 rootfs
|
||||||
|
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
|
||||||
|
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
|
||||||
|
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告:e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
|
||||||
|
sudo resize2fs "$LOOPDEV" || { echo "错误:resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Octopus-Planet Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
orangepizero_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/orangepi-zero/Armbian_community_25.11.0-trunk.208_Orangepizero_bookworm_current_6.12.47_minimal.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local offset=$((8192 * 512))
|
||||||
|
local add_size_mb=600
|
||||||
|
|
||||||
|
echo "信息:准备 Orange Pi Zero Rootfs..."
|
||||||
|
ensure_dir "$TMPDIR"
|
||||||
|
|
||||||
|
echo "信息:下载或使用本地 Orange Pi Zero 原始镜像..."
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 Orange Pi Zero 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 Orange Pi Zero 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
|
||||||
|
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:调整镜像分区大小..."
|
||||||
|
sudo parted -s "$target_image" resizepart 1 100% || { echo "错误:使用 parted 调整分区大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
find_loop_device
|
||||||
|
sudo losetup -P "$LOOPDEV" "$target_image" || { echo "错误:设置 loop 设备失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:检查并调整文件系统大小..."
|
||||||
|
sudo e2fsck -y -f "${LOOPDEV}p1" || { echo "错误:文件系统检查失败" >&2; exit 1; }
|
||||||
|
sudo resize2fs "${LOOPDEV}p1" || { echo "错误:调整文件系统大小失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
# 重新设置 LOOPDEV 为分区
|
||||||
|
sudo losetup -d "$LOOPDEV"
|
||||||
|
sudo losetup "$LOOPDEV" "$target_image" -o "$offset" || { echo "错误:重新设置 loop 设备失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:Orange Pi Zero Rootfs 准备完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 特定设备的文件配置函数 ---
|
||||||
|
|
||||||
|
config_cumebox2_files() {
|
||||||
|
echo "信息:为 Cumebox2 配置特定文件 (OLED, DTB)..."
|
||||||
|
ensure_dir "$ROOTFS/etc/oled"
|
||||||
|
|
||||||
|
# 自动下载 Cumebox2 相关文件(如果不存在)
|
||||||
|
local dtb_file="$SRCPATH/image/cumebox2/v-fix.dtb"
|
||||||
|
local ssd_file="$SRCPATH/image/cumebox2/ssd"
|
||||||
|
local config_file="$SRCPATH/image/cumebox2/config.json"
|
||||||
|
|
||||||
|
download_file_if_missing "$dtb_file" || echo "警告:下载 Cumebox2 DTB 失败"
|
||||||
|
download_file_if_missing "$ssd_file" || echo "警告:下载 Cumebox2 ssd 脚本失败"
|
||||||
|
download_file_if_missing "$config_file" || echo "警告:下载 Cumebox2 配置文件失败"
|
||||||
|
|
||||||
|
sudo cp "$dtb_file" "$ROOTFS/boot/dtb/amlogic/meson-gxl-s905x-khadas-vim.dtb" || echo "警告:复制 Cumebox2 DTB 失败"
|
||||||
|
sudo cp "$ssd_file" "$ROOTFS/usr/bin/" || echo "警告:复制 Cumebox2 ssd 脚本失败"
|
||||||
|
sudo chmod +x "$ROOTFS/usr/bin/ssd" || echo "警告:设置 ssd 脚本执行权限失败"
|
||||||
|
sudo cp "$config_file" "$ROOTFS/etc/oled/config.json" || echo "警告:复制 OLED 配置文件失败"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_octopus_flanet_files() {
|
||||||
|
echo "信息:为 Octopus-Planet 配置特定文件 (model_database.conf)..."
|
||||||
|
|
||||||
|
# 自动下载 Octopus-Planet 相关文件(如果不存在)
|
||||||
|
local config_file="$SRCPATH/image/octopus-flanet/model_database.conf"
|
||||||
|
|
||||||
|
download_file_if_missing "$config_file" || echo "警告:下载 Octopus-Planet 配置文件失败"
|
||||||
|
|
||||||
|
sudo cp "$config_file" "$ROOTFS/etc/model_database.conf" || echo "警告:复制 model_database.conf 失败"
|
||||||
|
|
||||||
|
echo "信息:为 Octopus-Planet 添加 DRM 设备支持..."
|
||||||
|
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card0\\\"\" /etc/kvmd/override.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_orangepi_zero_files() {
|
||||||
|
echo "信息:配置 Orange Pi Zero 特定文件..."
|
||||||
|
|
||||||
|
# 清空 modules.conf 文件,避免加载不必要的模块
|
||||||
|
run_in_chroot "echo 'libcomposite' > /etc/modules-load.d/modules.conf"
|
||||||
|
|
||||||
|
echo "信息:Orange Pi Zero 特定配置完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_onecloud_pro_files() {
|
||||||
|
echo "信息:配置 Onecloud Pro 特定文件..."
|
||||||
|
|
||||||
|
echo "信息:为 Onecloud Pro 添加 DRM 设备支持..."
|
||||||
|
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card0\\\"\" /etc/kvmd/override.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_onecloud_files() {
|
||||||
|
echo "信息:配置 Onecloud 特定文件..."
|
||||||
|
|
||||||
|
echo "信息:为 Onecloud 添加 DRM 设备支持..."
|
||||||
|
run_in_chroot "sed -i \"/--device=\\/dev\\/video0/a\\ - \\\"--drm-device=/dev/dri/card1\\\"\" /etc/kvmd/override.yaml"
|
||||||
|
|
||||||
|
echo "信息:Onecloud 特定配置完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
oec_turbo_rootfs() {
|
||||||
|
local source_image="$SRCPATH/image/oec-turbo/Flash_Armbian_25.05.0_rockchip_efused-wxy-oec_bookworm_6.1.99_server_2025.03.20.img"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local rootfs_offset=$((1409024 * 512)) # 根据分区7的起始扇区计算
|
||||||
|
|
||||||
|
echo "信息:准备 OEC-Turbo Rootfs (Debian 12)..."
|
||||||
|
ensure_dir "$TMPDIR"
|
||||||
|
|
||||||
|
echo "信息:下载或使用本地 OEC-Turbo 原始镜像..."
|
||||||
|
download_file_if_missing "$source_image" || { echo "错误:下载 OEC-Turbo 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$source_image" "$target_image" || { echo "错误:复制 OEC-Turbo 原始镜像失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
find_loop_device
|
||||||
|
# 设置 loop 设备指向 rootfs 分区 (分区7)
|
||||||
|
sudo losetup "$LOOPDEV" "$target_image" -o "$rootfs_offset" || { echo "错误:设置 loop 设备失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:OEC-Turbo Rootfs 准备完成,loop 设备 $LOOPDEV 已就绪。"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_oec_turbo_files() {
|
||||||
|
echo "信息:配置 OEC-Turbo 特定文件..."
|
||||||
|
|
||||||
|
# 替换 override.yaml 中的硬件编码配置,启用 RK MPP 硬件编码
|
||||||
|
echo "信息:配置 VPU 硬件编码支持..."
|
||||||
|
run_in_chroot "sed -i 's/--h264-hwenc=disabled/--h264-hwenc=rkmpp/g' /etc/kvmd/override.yaml"
|
||||||
|
|
||||||
|
echo "信息:配置 udev 规则以授权 kvmd 组访问硬件设备..."
|
||||||
|
run_in_chroot "cat > /etc/udev/rules.d/99-kvmd-hw-access.rules <<'EOF'
|
||||||
|
# Generic hardware access for kvmd
|
||||||
|
# Safe on all platforms — rules only apply if device exists
|
||||||
|
|
||||||
|
# Rockchip MPP (rkmpp)
|
||||||
|
KERNEL==\"mpp_service\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
|
||||||
|
# DMA-Heap (used by modern MPP)
|
||||||
|
SUBSYSTEM==\"dma_heap\", KERNEL==\"system\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
SUBSYSTEM==\"dma_heap\", KERNEL==\"system-uncached\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
SUBSYSTEM==\"dma_heap\", KERNEL==\"reserved\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
|
||||||
|
# Optional legacy Rockchip devices
|
||||||
|
KERNEL==\"rkvdec\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
KERNEL==\"rkvenc\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
KERNEL==\"rga\", GROUP=\"kvmd\", MODE=\"0660\"
|
||||||
|
|
||||||
|
EOF"
|
||||||
|
|
||||||
|
# 替换 DTB 文件
|
||||||
|
replace_oec_turbo_dtb
|
||||||
|
|
||||||
|
echo "信息:OEC-Turbo 特定配置完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_oec_turbo_dtb() {
|
||||||
|
local dtb_source="$SRCPATH/image/oec-turbo/rk3566-onething-oec-box.dtb"
|
||||||
|
local target_image="$TMPDIR/rootfs.img"
|
||||||
|
local boot_offset=$((360448 * 512)) # boot 分区6的偏移
|
||||||
|
local boot_mount="$TMPDIR/oec_boot_mount"
|
||||||
|
local dtb_target_path="dtb/rockchip/rk3566-onething-oec-box.dtb"
|
||||||
|
local boot_loopdev=""
|
||||||
|
|
||||||
|
echo "信息:替换 OEC-Turbo DTB 文件..."
|
||||||
|
|
||||||
|
if [ ! -f "$dtb_source" ]; then
|
||||||
|
echo "信息:尝试下载 DTB 文件..."
|
||||||
|
download_file_if_missing "$dtb_source"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:为 boot 分区查找独立的 loop 设备..."
|
||||||
|
# 查找一个新的loop设备用于boot分区
|
||||||
|
boot_loopdev=$(losetup -f)
|
||||||
|
ensure_dir "$boot_mount"
|
||||||
|
|
||||||
|
losetup -o "$boot_offset" "$boot_loopdev" "$target_image"
|
||||||
|
mount "$boot_loopdev" "$boot_mount"
|
||||||
|
|
||||||
|
# 确保目标目录存在并复制 DTB 文件
|
||||||
|
mkdir -p "$boot_mount/$(dirname "$dtb_target_path")"
|
||||||
|
cp "$dtb_source" "$boot_mount/$dtb_target_path"
|
||||||
|
echo "信息:DTB 文件替换成功: $dtb_target_path"
|
||||||
|
|
||||||
|
umount "$boot_mount"
|
||||||
|
losetup -d "$boot_loopdev"
|
||||||
|
rmdir "$boot_mount"
|
||||||
|
}
|
||||||
386
build/functions/install.sh
Executable file
386
build/functions/install.sh
Executable file
@ -0,0 +1,386 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- 预准备 ---
|
||||||
|
|
||||||
|
prepare_dns_and_mirrors() {
|
||||||
|
echo "信息:在 chroot 环境中准备 DNS 和更换软件源..."
|
||||||
|
run_in_chroot "
|
||||||
|
mkdir -p /run/systemd/resolve/ \\
|
||||||
|
&& touch /run/systemd/resolve/stub-resolv.conf \\
|
||||||
|
&& printf '%s\\n' 'nameserver 1.1.1.1' 'nameserver 1.0.0.1' > /etc/resolv.conf \\
|
||||||
|
&& echo '信息:尝试更换镜像源...' \\
|
||||||
|
&& bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh) \\
|
||||||
|
--source mirrors.ustc.edu.cn --upgrade-software false --web-protocol http || echo '警告:更换镜像源脚本执行失败,可能网络不通或脚本已更改'
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_armbian_verify(){
|
||||||
|
echo "信息:在 chroot 环境中修改 Armbian 软件源..."
|
||||||
|
run_in_chroot "echo 'deb http://mirrors.ustc.edu.cn/armbian bullseye main bullseye-utils bullseye-desktop' > /etc/apt/sources.list.d/armbian.list"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_external_binaries() {
|
||||||
|
local platform="$1" # linux/armhf or linux/amd64 or linux/aarch64
|
||||||
|
# 如果在 GitHub Actions 环境下,使用 silentwind0/kvmd-stage-0,否则用阿里云镜像
|
||||||
|
if is_github_actions; then
|
||||||
|
local docker_image="silentwind0/kvmd-stage-0"
|
||||||
|
else
|
||||||
|
local docker_image="registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:准备外部预编译二进制文件 (平台: $platform)..."
|
||||||
|
ensure_dir "$PREBUILT_DIR"
|
||||||
|
|
||||||
|
echo "信息:拉取 Docker 镜像 $docker_image (平台: $platform)..."
|
||||||
|
sudo docker pull --platform "$platform" "$docker_image" || { echo "错误:拉取 Docker 镜像 $docker_image 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:创建 Docker 容器 $DOCKER_CONTAINER_NAME ..."
|
||||||
|
sudo docker create --name "$DOCKER_CONTAINER_NAME" "$docker_image" || { echo "错误:创建 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:从 Docker 容器导出文件到 $PREBUILT_DIR ..."
|
||||||
|
sudo docker export "$DOCKER_CONTAINER_NAME" | sudo tar -xf - -C "$PREBUILT_DIR" || { echo "错误:导出并解压 Docker 容器内容失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:预编译二进制文件准备完成,存放于 $PREBUILT_DIR"
|
||||||
|
|
||||||
|
# 删除 Docker 容器
|
||||||
|
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || { echo "错误:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
config_base_files() {
|
||||||
|
local platform_id="$1" # e.g., "onecloud", "cumebox2"
|
||||||
|
echo "信息:配置基础文件和目录结构 ($platform_id)..."
|
||||||
|
|
||||||
|
echo "信息:创建 KVMD 相关目录..."
|
||||||
|
ensure_dir "$ROOTFS/etc/kvmd/override.d"
|
||||||
|
ensure_dir "$ROOTFS/etc/kvmd/vnc"
|
||||||
|
ensure_dir "$ROOTFS/var/lib/kvmd/msd/images"
|
||||||
|
ensure_dir "$ROOTFS/var/lib/kvmd/msd/meta"
|
||||||
|
ensure_dir "$ROOTFS/opt/vc/bin"
|
||||||
|
ensure_dir "$ROOTFS/usr/share/kvmd"
|
||||||
|
ensure_dir "$ROOTFS/One-KVM"
|
||||||
|
ensure_dir "$ROOTFS/usr/share/janus/javascript"
|
||||||
|
ensure_dir "$ROOTFS/usr/lib/ustreamer/janus"
|
||||||
|
ensure_dir "$ROOTFS/run/kvmd"
|
||||||
|
ensure_dir "$ROOTFS/tmp/wheel/"
|
||||||
|
ensure_dir "$ROOTFS/usr/lib/janus/transports/"
|
||||||
|
ensure_dir "$ROOTFS/usr/lib/janus/loggers"
|
||||||
|
|
||||||
|
echo "信息:复制 One-KVM 源码..."
|
||||||
|
sudo rsync -a --exclude={.git,.github,output,tmp} . "$ROOTFS/One-KVM/" || { echo "错误:复制 One-KVM 源码失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:复制配置文件..."
|
||||||
|
sudo cp -r configs/kvmd/* configs/nginx configs/janus "$ROOTFS/etc/kvmd/"
|
||||||
|
sudo cp -r web extras contrib/keymaps "$ROOTFS/usr/share/kvmd/"
|
||||||
|
sudo cp testenv/fakes/vcgencmd "$ROOTFS/usr/bin/"
|
||||||
|
sudo cp -r testenv/js/* "$ROOTFS/usr/share/janus/javascript/"
|
||||||
|
sudo cp "build/platform/$platform_id" "$ROOTFS/usr/share/kvmd/platform" || { echo "错误:复制平台文件 build/platform/$platform_id 失败" >&2; exit 1; }
|
||||||
|
sudo cp scripts/kvmd-gencert scripts/kvmd-bootconfig scripts/kvmd-certbot scripts/kvmd-udev-hdmiusb-check scripts/kvmd-udev-restart-pass build/scripts/kvmd-firstrun.sh "$ROOTFS/usr/bin/"
|
||||||
|
sudo chmod +x "$ROOTFS/usr/bin/kvmd-gencert" "$ROOTFS/usr/bin/kvmd-bootconfig" "$ROOTFS/usr/bin/kvmd-certbot" "$ROOTFS/usr/bin/kvmd-udev-hdmiusb-check" "$ROOTFS/usr/bin/kvmd-udev-restart-pass" "$ROOTFS/usr/bin/kvmd-firstrun.sh"
|
||||||
|
|
||||||
|
# 尝试下载或使用本地 rc.local 文件
|
||||||
|
download_rc_local "$platform_id" || echo "信息:rc.local 文件不存在,跳过"
|
||||||
|
if [ -f "$SRCPATH/image/$platform_id/rc.local" ]; then
|
||||||
|
echo "信息:复制设备特定的 rc.local 文件..."
|
||||||
|
sudo cp "$SRCPATH/image/$platform_id/rc.local" "$ROOTFS/etc/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:从预编译目录复制二进制文件和库..."
|
||||||
|
sudo cp "$PREBUILT_DIR/tmp/lib/"* "$ROOTFS/lib/"*-linux-*/ || echo "警告:复制 /tmp/lib/* 失败,可能源目录或目标目录不存在或不匹配"
|
||||||
|
sudo cp "$PREBUILT_DIR/tmp/ustreamer/ustreamer" "$PREBUILT_DIR/tmp/ustreamer/ustreamer-dump" "$PREBUILT_DIR/usr/bin/janus" "$ROOTFS/usr/bin/" || { echo "错误:复制 ustreamer/janus 二进制文件失败" >&2; exit 1; }
|
||||||
|
sudo cp "$PREBUILT_DIR/tmp/ustreamer/janus/libjanus_ustreamer.so" "$ROOTFS/usr/lib/ustreamer/janus/" || { echo "错误:复制 libjanus_ustreamer.so 失败" >&2; exit 1; }
|
||||||
|
sudo cp "$PREBUILT_DIR/tmp/wheel/"*.whl "$ROOTFS/tmp/wheel/" || { echo "错误:复制 Python wheel 文件失败" >&2; exit 1; }
|
||||||
|
sudo cp "$PREBUILT_DIR/usr/lib/janus/transports/"* "$ROOTFS/usr/lib/janus/transports/" || { echo "错误:复制 Janus transports 失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
# 禁用 apt-file
|
||||||
|
if [ -f "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" ]; then
|
||||||
|
echo "信息:禁用 apt-file 配置..."
|
||||||
|
sudo mv "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf.disabled"
|
||||||
|
fi
|
||||||
|
echo "信息:基础文件配置完成。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- KVMD 安装与配置 ---
|
||||||
|
|
||||||
|
install_base_packages() {
|
||||||
|
echo "信息:在 chroot 环境中更新源并安装基础软件包..."
|
||||||
|
run_in_chroot "
|
||||||
|
apt-get update && \\
|
||||||
|
apt install -y --no-install-recommends \\
|
||||||
|
libxkbcommon-x11-0 nginx tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim \\
|
||||||
|
iptables network-manager curl kmod libmicrohttpd12 libjansson4 libssl3 \\
|
||||||
|
libsofia-sip-ua0 libglib2.0-0 libopus0 libogg0 libcurl4 libconfig9 \\
|
||||||
|
python3-pip net-tools libavcodec59 libavformat59 libavutil57 libswscale6 \\
|
||||||
|
libavfilter8 libavdevice59 v4l-utils libv4l-0 nano unzip dnsmasq python3-systemd && \\
|
||||||
|
apt clean && \\
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_network() {
|
||||||
|
local network_type="$1" # "systemd-networkd" or others (default network-manager)
|
||||||
|
if [ "$network_type" = "systemd-networkd" ]; then
|
||||||
|
echo "信息:在 chroot 环境中配置 systemd-networkd..."
|
||||||
|
|
||||||
|
# onecloud 与 onecloud-pro 均启用基于 SN 的 MAC 地址生成
|
||||||
|
if [ "$TARGET_DEVICE_NAME" = "onecloud" ] || [ "$TARGET_DEVICE_NAME" = "onecloud-pro" ]; then
|
||||||
|
echo "信息:为 ${TARGET_DEVICE_NAME} 平台配置基于 SN 的 MAC 地址生成机制..."
|
||||||
|
|
||||||
|
# 复制MAC地址生成脚本
|
||||||
|
sudo cp "$SCRIPT_DIR/scripts/generate-random-mac.sh" "$ROOTFS/usr/local/bin/"
|
||||||
|
sudo chmod +x "$ROOTFS/usr/local/bin/generate-random-mac.sh"
|
||||||
|
|
||||||
|
# 复制systemd服务文件
|
||||||
|
sudo cp "$SCRIPT_DIR/services/kvmd-generate-mac.service" "$ROOTFS/etc/systemd/system/"
|
||||||
|
|
||||||
|
# 创建初始网络配置文件(不包含MAC地址,将由脚本生成)
|
||||||
|
run_in_chroot "
|
||||||
|
echo -e '[Match]\\nName=eth0\\n\\n[Network]\\nDHCP=yes' > /etc/systemd/network/99-eth0.network && \\
|
||||||
|
systemctl mask NetworkManager && \\
|
||||||
|
systemctl unmask systemd-networkd && \\
|
||||||
|
systemctl enable systemd-networkd systemd-resolved && \\
|
||||||
|
systemctl enable kvmd-generate-mac.service
|
||||||
|
"
|
||||||
|
echo "信息:${TARGET_DEVICE_NAME} 基于 SN 的 MAC 地址生成机制配置完成"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "信息:使用默认的网络管理器 (NetworkManager)..."
|
||||||
|
# 可能需要确保 NetworkManager 是启用的 (通常默认是)
|
||||||
|
run_in_chroot "systemctl enable NetworkManager"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_python_deps() {
|
||||||
|
echo "信息:在 chroot 环境中安装 Python 依赖 (wheels)..."
|
||||||
|
run_in_chroot "
|
||||||
|
pip3 install --no-cache-dir --break-system-packages /tmp/wheel/*.whl && \\
|
||||||
|
pip3 cache purge && \\
|
||||||
|
rm -rf /tmp/wheel
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_kvmd_core() {
|
||||||
|
echo "信息:在 chroot 环境中安装和配置 KVMD 核心..."
|
||||||
|
|
||||||
|
# 复制KVMD首次运行脚本和服务
|
||||||
|
echo "信息:配置KVMD首次运行初始化服务..."
|
||||||
|
sudo cp "build/services/kvmd-firstrun.service" "$ROOTFS/etc/systemd/system/"
|
||||||
|
|
||||||
|
# 安装KVMD但不执行需要在首次运行时完成的操作
|
||||||
|
run_in_chroot "
|
||||||
|
cd /One-KVM && \\
|
||||||
|
python3 setup.py install && \\
|
||||||
|
systemctl enable kvmd-firstrun.service
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "信息:KVMD核心安装完成,证书生成等初始化操作将在首次开机时执行"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_system() {
|
||||||
|
echo "信息:在 chroot 环境中配置系统级设置 (sudoers, udev, services)..."
|
||||||
|
run_in_chroot "
|
||||||
|
cat /One-KVM/configs/os/sudoers/v2-hdmiusb >> /etc/sudoers && \\
|
||||||
|
cat /One-KVM/configs/os/udev/v2-hdmiusb-rpi4.rules > /etc/udev/rules.d/99-kvmd.rules && \\
|
||||||
|
echo 'libcomposite' >> /etc/modules && \\
|
||||||
|
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-kvmd-extra.conf && \\
|
||||||
|
mv /usr/local/bin/kvmd* /usr/bin/ || echo '信息:/usr/local/bin/kvmd* 未找到或移动失败,可能已在/usr/bin' && \\
|
||||||
|
cp -r /One-KVM/configs/os/services/* /etc/systemd/system/ && \\
|
||||||
|
cp /One-KVM/configs/os/tmpfiles.conf /usr/lib/tmpfiles.d/ && \\
|
||||||
|
chmod +x /etc/update-motd.d/* || echo '警告:chmod /etc/update-motd.d/* 失败' && \\
|
||||||
|
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/gpio.sh' >> /etc/sudoers && \\
|
||||||
|
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/usbrelay_hid.sh' >> /etc/sudoers && \\
|
||||||
|
systemd-sysusers /One-KVM/configs/os/sysusers.conf && \\
|
||||||
|
systemd-sysusers /One-KVM/configs/os/kvmd-webterm.conf && \\
|
||||||
|
ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata || echo '警告:创建 tesseract 链接失败' && \\
|
||||||
|
sed -i 's/8080/80/g' /etc/kvmd/override.yaml && \\
|
||||||
|
sed -i 's/4430/443/g' /etc/kvmd/override.yaml && \\
|
||||||
|
chown kvmd -R /var/lib/kvmd/msd/ && \\
|
||||||
|
rm /etc/resolv.conf && \\
|
||||||
|
printf '%s\\n' 'nameserver 1.1.1.1' 'nameserver 1.0.0.1' > /etc/resolv.conf && \
|
||||||
|
systemctl enable dnsmasq kvmd kvmd-otg kvmd-nginx kvmd-vnc kvmd-ipmi kvmd-webterm kvmd-janus kvmd-media kvmd-gostc && \\
|
||||||
|
systemctl disable nginx systemd-resolved && \\
|
||||||
|
rm -rf /One-KVM
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_webterm() {
|
||||||
|
local arch="$1" # armhf, aarch64, x86_64
|
||||||
|
local ttyd_arch="$arch"
|
||||||
|
|
||||||
|
if [ "$arch" = "armhf" ]; then
|
||||||
|
ttyd_arch="armhf"
|
||||||
|
elif [ "$arch" = "amd64" ]; then
|
||||||
|
ttyd_arch="x86_64"
|
||||||
|
elif [ "$arch" = "aarch64" ]; then
|
||||||
|
ttyd_arch="aarch64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:在 chroot 环境中下载并安装 ttyd ($ttyd_arch)..."
|
||||||
|
run_in_chroot "
|
||||||
|
curl -L https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.${ttyd_arch} -o /usr/bin/ttyd && \\
|
||||||
|
chmod +x /usr/bin/ttyd && \\
|
||||||
|
mkdir -p /home/kvmd-webterm && \\
|
||||||
|
chown kvmd-webterm /home/kvmd-webterm
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_gostc() {
|
||||||
|
local arch="$1" # armhf, aarch64, x86_64
|
||||||
|
local gostc_arch="$arch"
|
||||||
|
local gostc_version="v2.0.8-beta.2"
|
||||||
|
|
||||||
|
# 根据架构映射下载文件名
|
||||||
|
case "$arch" in
|
||||||
|
armhf) gostc_arch="arm_7" ;;
|
||||||
|
aarch64) gostc_arch="arm64_v8.0" ;;
|
||||||
|
x86_64|amd64) gostc_arch="amd64_v1" ;;
|
||||||
|
*) echo "错误:不支持的架构 $arch"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "信息:在 chroot 环境中下载并安装 gostc ($gostc_arch)..."
|
||||||
|
run_in_chroot "
|
||||||
|
mkdir -p /tmp/gostc && cd /tmp/gostc && \\
|
||||||
|
curl -L https://github.com/mofeng-git/gostc-open/releases/download/${gostc_version}/gostc_linux_${gostc_arch}.tar.gz -o gostc.tar.gz && \\
|
||||||
|
tar -xzf gostc.tar.gz && \\
|
||||||
|
mv gostc /usr/bin/ && \\
|
||||||
|
chmod +x /usr/bin/gostc && \\
|
||||||
|
cd / && rm -rf /tmp/gostc
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "信息:创建 gostc systemd 服务文件..."
|
||||||
|
run_in_chroot "
|
||||||
|
cat > /etc/systemd/system/kvmd-gostc.service << 'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=基于FRP开发的内网穿透 客户端/节点
|
||||||
|
ConditionFileIsExecutable=/usr/bin/gostc
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
StartLimitInterval=5
|
||||||
|
StartLimitBurst=10
|
||||||
|
ExecStart=/usr/bin/gostc \"-web-addr\" \"0.0.0.0:18080\"
|
||||||
|
WorkingDirectory=/usr/bin
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
EnvironmentFile=-/etc/sysconfig/gostc
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "信息:gostc 安装和配置完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_kvmd_tweaks() {
|
||||||
|
local arch="$1" # armhf, aarch64, x86_64
|
||||||
|
local device_type="$2" # "gpio" or "video1" or other
|
||||||
|
local atx_setting=""
|
||||||
|
local hid_setting=""
|
||||||
|
|
||||||
|
echo "信息:根据架构 ($arch) 和设备类型 ($device_type) 调整 KVMD 配置..."
|
||||||
|
|
||||||
|
if [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then
|
||||||
|
echo "信息:目标平台为 x86_64/amd64 架构,禁用 OTG,设置 ATX 为 USBRELAY_HID..."
|
||||||
|
run_in_chroot "
|
||||||
|
systemctl disable kvmd-otg && \\
|
||||||
|
sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh && \\
|
||||||
|
sed -i 's/device: \/dev\/ttyUSB0/device: \/dev\/kvmd-hid/g' /etc/kvmd/override.yaml
|
||||||
|
"
|
||||||
|
else
|
||||||
|
echo "信息::目标平台为 ARM 架构 ($arch)..."
|
||||||
|
# ARM 架构,配置 HID 为 OTG
|
||||||
|
hid_setting="otg"
|
||||||
|
run_in_chroot "
|
||||||
|
sed -i 's/#type: otg/type: otg/g' /etc/kvmd/override.yaml && \\
|
||||||
|
sed -i 's/device: \/dev\/ttyUSB0/#device: \/dev\/ttyUSB0/g' /etc/kvmd/override.yaml # 注释掉 ttyUSB0
|
||||||
|
"
|
||||||
|
echo "信息:设置 HID 为 $hid_setting"
|
||||||
|
run_in_chroot "sed -i 's/type: ch9329/type: $hid_setting/g' /etc/kvmd/override.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
# 根据 device_type 配置 ATX
|
||||||
|
if [[ "$device_type" == *"gpio-onecloud-pro"* ]]; then
|
||||||
|
echo "信息:电源控制设备类型为 gpio,设置 ATX 为 GPIO 并配置引脚..."
|
||||||
|
atx_setting="GPIO"
|
||||||
|
run_in_chroot "
|
||||||
|
sed -i 's/^ATX=.*/ATX=GPIO/' /etc/kvmd/atx.sh && \\
|
||||||
|
sed -i 's/SHUTDOWNPIN/gpiochip0 7/g' /etc/kvmd/custom_atx/gpio.sh && \\
|
||||||
|
sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh
|
||||||
|
"
|
||||||
|
elif [[ "$device_type" == *"gpio-onecloud"* ]]; then
|
||||||
|
echo "信息:电源控制设备类型为 gpio,设置 ATX 为 GPIO 并配置引脚..."
|
||||||
|
atx_setting="GPIO"
|
||||||
|
run_in_chroot "
|
||||||
|
sed -i 's/^ATX=.*/ATX=GPIO/' /etc/kvmd/atx.sh && \\
|
||||||
|
sed -i 's/SHUTDOWNPIN/gpiochip1 7/g' /etc/kvmd/custom_atx/gpio.sh && \\
|
||||||
|
sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh
|
||||||
|
"
|
||||||
|
else
|
||||||
|
echo "信息:电源控制设备类型不是 gpio ($device_type),设置 ATX 为 USBRELAY_HID..."
|
||||||
|
atx_setting="USBRELAY_HID"
|
||||||
|
run_in_chroot "sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 配置视频设备
|
||||||
|
if [[ "$device_type" == *"video1"* ]]; then
|
||||||
|
echo "信息:视频设备类型为 video1,设置视频设备为 /dev/video1..."
|
||||||
|
run_in_chroot "sed -i 's|/dev/video0|/dev/video1|g' /etc/kvmd/override.yaml"
|
||||||
|
elif [[ "$device_type" == *"video1"* ]]; then
|
||||||
|
echo "信息:视频设备类型为 kvmd-video,设置视频设备为 /dev/kvmd-video..."
|
||||||
|
run_in_chroot "sed -i 's|/dev/video0|/dev/kvmd-video|g' /etc/kvmd/override.yaml"
|
||||||
|
else
|
||||||
|
echo "信息:使用默认视频设备 /dev/video0..."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "信息:KVMD 配置调整完成。"
|
||||||
|
|
||||||
|
run_in_chroot "apt remove -y --purge systemd-resolved"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 整体安装流程 ---
|
||||||
|
install_and_configure_kvmd() {
|
||||||
|
local arch="$1" # 架构: armhf, aarch64, x86_64/amd64
|
||||||
|
local device_type="$2" # 设备特性: "gpio", "video1", "" (空或其他)
|
||||||
|
local network_type="$3" # 网络配置: "systemd-networkd", "" (默认 network-manager)
|
||||||
|
local host_arch="" # Docker 平台架构: arm, aarch64, amd64
|
||||||
|
|
||||||
|
# 映射架构名称
|
||||||
|
case "$arch" in
|
||||||
|
armhf) host_arch="arm" ;;
|
||||||
|
aarch64) host_arch="arm64" ;; # docker aarch64 平台名是 arm64
|
||||||
|
x86_64|amd64) host_arch="amd64"; arch="x86_64" ;; # 统一内部使用 x86_64
|
||||||
|
*) echo "错误:不支持的架构 $arch"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
prepare_external_binaries "linux/$host_arch"
|
||||||
|
config_base_files "$TARGET_DEVICE_NAME" # 使用全局变量传递设备名
|
||||||
|
|
||||||
|
# 特定设备的额外文件配置 (如果存在)
|
||||||
|
# 将设备名中的连字符转换为下划线以匹配函数名
|
||||||
|
local device_func_name="${TARGET_DEVICE_NAME//-/_}"
|
||||||
|
if declare -f "config_${device_func_name}_files" > /dev/null; then
|
||||||
|
echo "信息:执行特定设备的文件配置函数 config_${device_func_name}_files ..."
|
||||||
|
"config_${device_func_name}_files"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 某些镜像可能需要准备DNS和换源
|
||||||
|
if [[ "$NEED_PREPARE_DNS" = true ]]; then
|
||||||
|
prepare_dns_and_mirrors
|
||||||
|
fi
|
||||||
|
# 可选:强制使用特定armbian源
|
||||||
|
# delete_armbian_verify
|
||||||
|
|
||||||
|
# 执行安装步骤
|
||||||
|
install_base_packages
|
||||||
|
configure_network "$network_type"
|
||||||
|
install_python_deps
|
||||||
|
configure_kvmd_core
|
||||||
|
install_gostc "$arch" # 安装 gostc
|
||||||
|
configure_system
|
||||||
|
install_webterm "$arch" # 传递原始架构名给ttyd下载
|
||||||
|
apply_kvmd_tweaks "$arch" "$device_type"
|
||||||
|
|
||||||
|
run_in_chroot "df -h" # 显示最终磁盘使用情况
|
||||||
|
echo "信息:One-KVM 安装和配置完成。"
|
||||||
|
}
|
||||||
105
build/functions/packaging.sh
Executable file
105
build/functions/packaging.sh
Executable file
@ -0,0 +1,105 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- 压缩函数 ---
|
||||||
|
|
||||||
|
# 压缩镜像文件(仅在 GitHub Actions 环境中)
|
||||||
|
compress_image_file() {
|
||||||
|
local file_path="$1"
|
||||||
|
|
||||||
|
if is_github_actions && [[ -f "$file_path" ]]; then
|
||||||
|
echo "信息:压缩镜像文件: $file_path"
|
||||||
|
if xz -9 -vv "$file_path"; then
|
||||||
|
echo "信息:压缩完成: ${file_path}.xz"
|
||||||
|
else
|
||||||
|
echo "警告:压缩文件 $file_path 失败"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 打包函数 ---
|
||||||
|
|
||||||
|
pack_img() {
|
||||||
|
local device_name_friendly="$1" # e.g., "Vm", "Cumebox2"
|
||||||
|
local target_img_name="One-KVM_by-SilentWind_${device_name_friendly}_${DATE}.img"
|
||||||
|
local source_img="$TMPDIR/rootfs.img"
|
||||||
|
|
||||||
|
echo "信息:开始打包镜像 ($device_name_friendly)..."
|
||||||
|
ensure_dir "$OUTPUTDIR"
|
||||||
|
|
||||||
|
# 确保在打包前已经正确卸载了所有挂载点和loop设备
|
||||||
|
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
|
||||||
|
echo "警告:发现未卸载的挂载点或loop设备,尝试再次卸载..."
|
||||||
|
unmount_all
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:移动镜像文件 $source_img 到 $OUTPUTDIR/$target_img_name ..."
|
||||||
|
sudo mv "$source_img" "$OUTPUTDIR/$target_img_name" || { echo "错误:移动镜像文件失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [ "$device_name_friendly" = "Vm" ]; then
|
||||||
|
echo "信息:为 Vm 目标转换镜像格式 (vmdk, vdi)..."
|
||||||
|
local raw_img="$OUTPUTDIR/$target_img_name"
|
||||||
|
local vmdk_img="$OUTPUTDIR/One-KVM_by-SilentWind_Vmare-uefi_${DATE}.vmdk"
|
||||||
|
local vdi_img="$OUTPUTDIR/One-KVM_by-SilentWind_Virtualbox-uefi_${DATE}.vdi"
|
||||||
|
|
||||||
|
echo "信息:转换为 VMDK..."
|
||||||
|
sudo qemu-img convert -f raw -O vmdk "$raw_img" "$vmdk_img" || echo "警告:转换为 VMDK 失败"
|
||||||
|
echo "信息:转换为 VDI..."
|
||||||
|
sudo qemu-img convert -f raw -O vdi "$raw_img" "$vdi_img" || echo "警告:转换为 VDI 失败"
|
||||||
|
|
||||||
|
# 在 GitHub Actions 环境中压缩 VM 镜像文件
|
||||||
|
if is_github_actions; then
|
||||||
|
echo "信息:在 GitHub Actions 环境中压缩 VM 镜像文件..."
|
||||||
|
compress_image_file "$raw_img"
|
||||||
|
compress_image_file "$vmdk_img"
|
||||||
|
compress_image_file "$vdi_img"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# 在 GitHub Actions 环境中压缩镜像文件
|
||||||
|
if is_github_actions; then
|
||||||
|
echo "信息:在 GitHub Actions 环境中压缩镜像文件..."
|
||||||
|
compress_image_file "$OUTPUTDIR/$target_img_name"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:镜像打包完成: $OUTPUTDIR/$target_img_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
pack_img_onecloud() {
|
||||||
|
local target_img_name="One-KVM_by-SilentWind_Onecloud_${DATE}.burn.img"
|
||||||
|
local rootfs_raw_img="$TMPDIR/rootfs.img"
|
||||||
|
local rootfs_sparse_img="$TMPDIR/7.rootfs.PARTITION.sparse"
|
||||||
|
local aml_packer="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
|
||||||
|
|
||||||
|
echo "信息:开始为 Onecloud 打包 burn 镜像..."
|
||||||
|
ensure_dir "$OUTPUTDIR"
|
||||||
|
|
||||||
|
# 确保在打包前已经正确卸载了所有挂载点和loop设备
|
||||||
|
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
|
||||||
|
echo "警告:发现未卸载的挂载点或loop设备,尝试再次卸载..."
|
||||||
|
unmount_all
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 自动下载 AmlImg 工具(如果不存在)
|
||||||
|
download_file_if_missing "$aml_packer" || { echo "错误:下载 AmlImg 工具失败" >&2; exit 1; }
|
||||||
|
sudo chmod +x "$aml_packer" || { echo "错误:设置 AmlImg 工具执行权限失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:将 raw rootfs 转换为 sparse image..."
|
||||||
|
# 先删除可能存在的旧 sparse 文件
|
||||||
|
sudo rm -f "$rootfs_sparse_img"
|
||||||
|
sudo img2simg "$rootfs_raw_img" "$rootfs_sparse_img" || { echo "错误:img2simg 转换失败" >&2; exit 1; }
|
||||||
|
sudo rm "$rootfs_raw_img" # 删除 raw 文件,因为它已被转换
|
||||||
|
|
||||||
|
echo "信息:使用 AmlImg 工具打包..."
|
||||||
|
sudo "$aml_packer" pack "$OUTPUTDIR/$target_img_name" "$TMPDIR/" || { echo "错误:AmlImg 打包失败" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "信息:清理 Onecloud 临时文件..."
|
||||||
|
sudo rm -f "$TMPDIR/6.boot.PARTITION.sparse" "$TMPDIR/7.rootfs.PARTITION.sparse" "$TMPDIR/dts.img"
|
||||||
|
|
||||||
|
# 在 GitHub Actions 环境中压缩 Onecloud 镜像文件
|
||||||
|
if is_github_actions; then
|
||||||
|
echo "信息:在 GitHub Actions 环境中压缩 Onecloud 镜像文件..."
|
||||||
|
compress_image_file "$OUTPUTDIR/$target_img_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "信息:Onecloud burn 镜像打包完成: $OUTPUTDIR/$target_img_name"
|
||||||
|
}
|
||||||
252
build/init.sh
252
build/init.sh
@ -1,41 +1,123 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
|
|
||||||
|
# 定义颜色代码
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[0;33m'
|
YELLOW='\033[0;33m'
|
||||||
BLUE='\033[0;34m'
|
BLUE='\033[0;34m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
|
||||||
echo -e "${GREEN}One-KVM pre-starting...${NC}"
|
# 输出日志的函数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 初始化检查
|
||||||
|
log_info "One-KVM 正在启动..."
|
||||||
|
|
||||||
|
# 首次初始化配置
|
||||||
if [ ! -f /etc/kvmd/.init_flag ]; then
|
if [ ! -f /etc/kvmd/.init_flag ]; then
|
||||||
echo -e "${GREEN}One-KVM is initializing first...${NC}" \
|
log_info "首次初始化配置..."
|
||||||
&& mkdir -p /etc/kvmd/ \
|
|
||||||
&& mv /etc/kvmd_backup/* /etc/kvmd/ \
|
|
||||||
&& touch /etc/kvmd/.docker_flag \
|
|
||||||
&& sed -i 's/localhost.localdomain/docker/g' /etc/kvmd/meta.yaml \
|
|
||||||
&& sed -i 's/localhost/localhost:4430/g' /etc/kvmd/kvm_input.sh \
|
|
||||||
&& /usr/share/kvmd/kvmd-gencert --do-the-thing \
|
|
||||||
&& /usr/share/kvmd/kvmd-gencert --do-the-thing --vnc \
|
|
||||||
|| echo -e "${RED}One-KVM config moving and self-signed SSL certificates init failed.${NC}"
|
|
||||||
|
|
||||||
if [ "$NOSSL" == 1 ]; then
|
# 创建必要目录并移动配置文件
|
||||||
echo -e "${GREEN}One-KVM self-signed SSL is disabled.${NC}" \
|
if mkdir -p /etc/kvmd/ && \
|
||||||
&& python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf -o nginx/https/enabled=false \
|
mv /etc/kvmd_backup/* /etc/kvmd/ && \
|
||||||
|| echo -e "${RED}One-KVM nginx config init failed.${NC}"
|
touch /etc/kvmd/.docker_flag && \
|
||||||
|
sed -i 's/localhost.localdomain/docker/g' /etc/kvmd/meta.yaml && \
|
||||||
|
sed -i 's/localhost/localhost:4430/g' /etc/kvmd/kvm_input.sh; then
|
||||||
|
log_info "移动配置文件完成"
|
||||||
else
|
else
|
||||||
python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf \
|
log_error "移动配置文件失败"
|
||||||
|| echo -e "${RED}One-KVM nginx config init failed.${NC}"
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# SSL证书配置
|
||||||
|
if ! /usr/share/kvmd/kvmd-gencert --do-the-thing; then
|
||||||
|
log_error "Nginx SSL 证书生成失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! /usr/share/kvmd/kvmd-gencert --do-the-thing --vnc; then
|
||||||
|
log_error "VNC SSL 证书生成失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置用户名和密码
|
||||||
|
if [ ! -z "$USERNAME" ] && [ ! -z "$PASSWORD" ]; then
|
||||||
|
# 设置自定义用户名和密码
|
||||||
|
if python -m kvmd.apps.htpasswd del admin \
|
||||||
|
&& echo "$PASSWORD" | python -m kvmd.apps.htpasswd add -i "$USERNAME" \
|
||||||
|
&& echo "$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/vncpasswd \
|
||||||
|
&& echo "$USERNAME:$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/ipmipasswd; then
|
||||||
|
log_info "用户凭据设置成功"
|
||||||
|
else
|
||||||
|
log_error "用户凭据设置失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ ! -z "$PASSWORD" ] && [ -z "$USERNAME" ]; then
|
||||||
|
# 只设置密码,保持admin用户名
|
||||||
|
if echo "$PASSWORD" | python -m kvmd.apps.htpasswd set -i "admin" \
|
||||||
|
&& echo "$PASSWORD -> admin:$PASSWORD" > /etc/kvmd/vncpasswd \
|
||||||
|
&& echo "admin:$PASSWORD -> admin:$PASSWORD" > /etc/kvmd/ipmipasswd; then
|
||||||
|
log_info "admin 用户密码设置成功"
|
||||||
|
else
|
||||||
|
log_error "admin 用户密码设置失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warn "未设置 USERNAME 和 PASSWORD 环境变量,使用默认值(admin/admin)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSL开关配置
|
||||||
|
if [ "$NOSSL" == 1 ]; then
|
||||||
|
log_info "已禁用SSL"
|
||||||
|
if ! python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf -o nginx/https/enabled=false; then
|
||||||
|
log_error "Nginx 配置失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf; then
|
||||||
|
log_error "Nginx 配置失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 认证配置
|
||||||
if [ "$NOAUTH" == "1" ]; then
|
if [ "$NOAUTH" == "1" ]; then
|
||||||
sed -i "s/enabled: true/enabled: false/g" /etc/kvmd/override.yaml \
|
sed -i "s/enabled: true/enabled: false/g" /etc/kvmd/override.yaml
|
||||||
&& echo -e "${GREEN}One-KVM auth is disabled.${NC}"
|
log_info "已禁用认证"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#add supervisord conf
|
#add supervisord conf
|
||||||
if [ "$NOWEBTERM" == "1" ]; then
|
if [ "$NOWEBTERM" == "1" ]; then
|
||||||
echo -e "${GREEN}One-KVM webterm is disabled.${NC}"
|
log_info "已禁用 WebTerm 功能"
|
||||||
rm -r /usr/share/kvmd/extras/webterm
|
rm -r /usr/share/kvmd/extras/webterm
|
||||||
else
|
else
|
||||||
cat >> /etc/kvmd/supervisord.conf << EOF
|
cat >> /etc/kvmd/supervisord.conf << EOF
|
||||||
@ -58,7 +140,7 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$NOVNC" == "1" ]; then
|
if [ "$NOVNC" == "1" ]; then
|
||||||
echo -e "${GREEN}One-KVM VNC is disabled.${NC}"
|
log_info "已禁用 VNC 功能"
|
||||||
rm -r /usr/share/kvmd/extras/vnc
|
rm -r /usr/share/kvmd/extras/vnc
|
||||||
else
|
else
|
||||||
cat >> /etc/kvmd/supervisord.conf << EOF
|
cat >> /etc/kvmd/supervisord.conf << EOF
|
||||||
@ -77,7 +159,7 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$NOIPMI" == "1" ]; then
|
if [ "$NOIPMI" == "1" ]; then
|
||||||
echo -e "${GREEN}One-KVM IPMI is disabled.${NC}"
|
log_info "已禁用 IPMI 功能"
|
||||||
rm -r /usr/share/kvmd/extras/ipmi
|
rm -r /usr/share/kvmd/extras/ipmi
|
||||||
else
|
else
|
||||||
cat >> /etc/kvmd/supervisord.conf << EOF
|
cat >> /etc/kvmd/supervisord.conf << EOF
|
||||||
@ -95,50 +177,124 @@ redirect_stderr=true
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$NOGOSTC" == "1" ]; then
|
||||||
|
log_info "已禁用 GOSTC 功能"
|
||||||
|
rm -rf /usr/share/kvmd/extras/gostc
|
||||||
|
else
|
||||||
|
cat >> /etc/kvmd/supervisord.conf << EOF
|
||||||
|
|
||||||
|
[program:kvmd-gostc]
|
||||||
|
command=/usr/bin/gostc -web-addr 127.0.0.1:18080
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=5
|
||||||
|
priority=300
|
||||||
|
stopasgroup=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes = 0
|
||||||
|
redirect_stderr=true
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
#switch OTG config
|
#switch OTG config
|
||||||
if [ "$OTG" == "1" ]; then
|
if [ "$OTG" == "1" ]; then
|
||||||
echo -e "${GREEN}One-KVM OTG is enabled.${NC}"
|
log_info "已启用 OTG 功能"
|
||||||
sed -i "s/ch9329/otg/g" /etc/kvmd/override.yaml
|
sed -i "s/ch9329/otg/g" /etc/kvmd/override.yaml
|
||||||
sed -i "s/device: \/dev\/ttyUSB0//g" /etc/kvmd/override.yaml
|
sed -i "s|device: /dev/ttyUSB0||g" /etc/kvmd/override.yaml
|
||||||
|
if [ "$NOMSD" == 1 ]; then
|
||||||
|
log_info "已禁用 MSD 功能"
|
||||||
|
else
|
||||||
|
sed -i "s/#type: otg/type: otg/g" /etc/kvmd/override.yaml
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#if [ ! -z "$SHUTDOWNPIN" ! -z "$REBOOTPIN" ]; then
|
|
||||||
|
|
||||||
if [ ! -z "$VIDEONUM" ]; then
|
if [ ! -z "$VIDEONUM" ]; then
|
||||||
sed -i "s/\/dev\/video0/\/dev\/video$VIDEONUM/g" /etc/kvmd/override.yaml \
|
if sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/override.yaml && \
|
||||||
&& echo -e "${GREEN}One-KVM video device is set to /dev/video$VIDEONUM.${NC}"
|
sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
|
||||||
|
log_info "视频设备已设置为 /dev/video$VIDEONUM"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#set htpasswd
|
if [ ! -z "$AUDIONUM" ]; then
|
||||||
if [ ! -z "$USERNAME" ] && [ ! -z "$PASSWORD" ]; then
|
if sed -i "s/hw:0/hw:$AUDIONUM/g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
|
||||||
python -m kvmd.apps.htpasswd del admin \
|
log_info "音频设备已设置为 hw:$AUDIONUM"
|
||||||
&& echo $PASSWORD | python -m kvmd.apps.htpasswd set -i "$USERNAME" \
|
fi
|
||||||
&& echo "$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/vncpasswd \
|
|
||||||
&& echo "$USERNAME:$PASSWORD -> $USERNAME:$PASSWORD" > /etc/kvmd/ipmipasswd \
|
|
||||||
|| echo -e "${RED}One-KVM htpasswd init failed.${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW} USERNAME and PASSWORD environment variables is not set, using defalut(admin/admin).${NC}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$NOMSD" == 1 ]; then
|
if [ ! -z "$CH9329SPEED" ]; then
|
||||||
echo -e "${GREEN}One-KVM MSD is disabled.${NC}"
|
if sed -i "s/speed: 9600/speed: $CH9329SPEED/g" /etc/kvmd/override.yaml; then
|
||||||
else
|
log_info "CH9329 串口速率已设置为 $CH9329SPEED"
|
||||||
sed -i "s/#type: otg/type: otg/g" /etc/kvmd/override.yaml
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$CH9329NUM" ]; then
|
||||||
|
if sed -i "s|/dev/ttyUSB0|/dev/ttyUSB$CH9329NUM|g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "CH9329 串口设备已设置为 $CH9329NUM"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$CH9329TIMEOUT" ]; then
|
||||||
|
if sed -i "s/read_timeout: 0.3/read_timeout: $CH9329TIMEOUT/g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "CH9329 超时已设置为 $CH9329TIMEOUT 秒"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$H264PRESET" ]; then
|
||||||
|
if sed -i "s/ultrafast/$H264PRESET/g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "H264 预设已设置为 $H264PRESET"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$VIDEOFORMAT" ]; then
|
||||||
|
if sed -i "s/--format=mjpeg/--format=$VIDEOFORMAT/g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "视频输入格式已设置为 $VIDEOFORMAT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$HWENCODER" ]; then
|
||||||
|
if sed -i "s/--h264-hwenc=disabled/--h264-hwenc=$HWENCODER/g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "硬件编码器已设置为 $HWENCODER"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置WEB端口
|
||||||
|
if [ ! -z "$HTTPPORT" ]; then
|
||||||
|
if sed -i "s/port: 8080/port: $HTTPPORT/g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "HTTP 端口已设置为 $HTTPPORT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$HTTPSPORT" ]; then
|
||||||
|
if sed -i "s/port: 4430/port: $HTTPSPORT/g" /etc/kvmd/override.yaml; then
|
||||||
|
log_info "HTTPS 端口已设置为 $HTTPSPORT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
touch /etc/kvmd/.init_flag
|
touch /etc/kvmd/.init_flag
|
||||||
|
log_info "初始化配置完成"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#Trying usb_gadget
|
# OTG设备配置
|
||||||
if [ "$OTG" == "1" ]; then
|
if [ "$OTG" == "1" ]; then
|
||||||
echo "Trying OTG Port..."
|
log_info "正在配置 OTG 设备..."
|
||||||
rm -r /run/kvmd/otg &> /dev/null
|
rm -r /run/kvmd/otg &> /dev/null
|
||||||
modprobe libcomposite || echo -e "${RED}Linux libcomposite module modprobe failed.${NC}"
|
|
||||||
python -m kvmd.apps.otg start \
|
if ! modprobe libcomposite; then
|
||||||
&& ln -s /dev/hidg1 /dev/kvmd-hid-mouse \
|
log_error "加载 libcomposite 模块失败"
|
||||||
&& ln -s /dev/hidg0 /dev/kvmd-hid-keyboard \
|
exit 1
|
||||||
|| echo -e "${RED}OTG Port mount failed.${NC}"
|
fi
|
||||||
|
|
||||||
|
if python -m kvmd.apps.otg start; then
|
||||||
|
ln -s /dev/hidg1 /dev/kvmd-hid-mouse
|
||||||
|
ln -s /dev/hidg0 /dev/kvmd-hid-keyboard
|
||||||
|
ln -s /dev/hidg2 /dev/kvmd-hid-mouse-alt
|
||||||
|
log_info "OTG 设备配置完成"
|
||||||
|
else
|
||||||
|
log_warn "OTG 设备挂载失败"
|
||||||
|
#exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}One-KVM starting...${NC}"
|
log_info "One-KVM 配置文件准备完成,正在启动服务..."
|
||||||
exec supervisord -c /etc/kvmd/supervisord.conf
|
exec supervisord -c /etc/kvmd/supervisord.conf
|
||||||
3
build/platform/chainedbox
Normal file
3
build/platform/chainedbox
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=chainedbox
|
||||||
3
build/platform/cumebox2
Normal file
3
build/platform/cumebox2
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=cumebox2
|
||||||
3
build/platform/e900v22c
Normal file
3
build/platform/e900v22c
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=e900v22c
|
||||||
3
build/platform/octopus-flanet
Normal file
3
build/platform/octopus-flanet
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=octopus-flanet
|
||||||
3
build/platform/oec-turbo
Normal file
3
build/platform/oec-turbo
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=oec-turbo
|
||||||
3
build/platform/onecloud-pro
Normal file
3
build/platform/onecloud-pro
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=onecloud-pro
|
||||||
3
build/platform/orangepi-zero
Normal file
3
build/platform/orangepi-zero
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=orangepi-zero
|
||||||
3
build/platform/vm
Normal file
3
build/platform/vm
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PIKVM_MODEL=v2_model
|
||||||
|
PIKVM_VIDEO=usb_video
|
||||||
|
PIKVM_BOARD=vm
|
||||||
21
build/record.txt
Normal file
21
build/record.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
wget https://github.com/hzyitc/AmlImg/releases/download/v0.3.1/AmlImg_v0.3.1_linux_amd64 -O /mnt/src/image/onecloud/AmlImg_v0.3.1_linux_amd64
|
||||||
|
chmod +x /mnt/src/image/onecloud/AmlImg_v0.3.1_linux_amd64
|
||||||
|
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
# 文件映射脚本
|
||||||
|
# 本地目录前缀:/mnt
|
||||||
|
# 远程URL前缀:https://files.mofeng.run
|
||||||
|
|
||||||
|
LOCAL_PREFIX="/mnt"
|
||||||
|
REMOTE_PREFIX="https://files.mofeng.run"
|
||||||
|
|
||||||
|
# 文件相对路径
|
||||||
|
REL_PATH="src/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal_support-dvd-emulation.burn.img"
|
||||||
|
|
||||||
|
LOCAL_FILE="$LOCAL_PREFIX/$REL_PATH"
|
||||||
|
REMOTE_URL="$REMOTE_PREFIX/$REL_PATH"
|
||||||
|
|
||||||
|
echo "下载 $REMOTE_URL 到 $LOCAL_FILE"
|
||||||
|
mkdir -p "$(dirname "$LOCAL_FILE")"
|
||||||
|
wget -O "$LOCAL_FILE" "$REMOTE_URL"
|
||||||
122
build/scripts/generate-random-mac.sh
Normal file
122
build/scripts/generate-random-mac.sh
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 为玩客云/玩客云Pro 平台生成 MAC 地址的一次性脚本
|
||||||
|
# 此脚本在首次开机时执行,为 eth0 网卡生成并应用基于 SN 的 MAC 地址,失败时回退到随机 MAC
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
NETWORK_CONFIG="/etc/systemd/network/99-eth0.network"
|
||||||
|
LOCK_FILE="/var/lib/kvmd/.mac-generated"
|
||||||
|
PLATFORM_FILE="/usr/share/kvmd/platform"
|
||||||
|
EFUSE_SYSFS_PATH=""
|
||||||
|
SN_PREFIX=""
|
||||||
|
SN_EXPECTED_LENGTH=13
|
||||||
|
|
||||||
|
# 按平台设置 EFUSE 与 SN 参数;未知平台时按 efuse 路径探测
|
||||||
|
detect_platform_params() {
|
||||||
|
local platform=""
|
||||||
|
if [ -f "$PLATFORM_FILE" ]; then
|
||||||
|
platform=$(tr -d '\n' < "$PLATFORM_FILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$platform" in
|
||||||
|
onecloud)
|
||||||
|
EFUSE_SYSFS_PATH="/sys/bus/nvmem/devices/meson8b-efuse0/nvmem"
|
||||||
|
SN_PREFIX="OCP"
|
||||||
|
;;
|
||||||
|
onecloud-pro)
|
||||||
|
EFUSE_SYSFS_PATH="/sys/devices/platform/efuse/efuse0/nvmem"
|
||||||
|
SN_PREFIX="ODC"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "$EFUSE_SYSFS_PATH" ] || [ -z "$SN_PREFIX" ]; then
|
||||||
|
if [ -e "/sys/devices/platform/efuse/efuse0/nvmem" ]; then
|
||||||
|
EFUSE_SYSFS_PATH="/sys/devices/platform/efuse/efuse0/nvmem"
|
||||||
|
SN_PREFIX="ODC"
|
||||||
|
elif [ -e "/sys/bus/nvmem/devices/meson8b-efuse0/nvmem" ]; then
|
||||||
|
EFUSE_SYSFS_PATH="/sys/bus/nvmem/devices/meson8b-efuse0/nvmem"
|
||||||
|
SN_PREFIX="OCP"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查是否已经执行过
|
||||||
|
if [ -f "$LOCK_FILE" ]; then
|
||||||
|
echo "MAC地址已经生成过,跳过执行"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 生成MAC地址函数
|
||||||
|
generate_random_mac() {
|
||||||
|
detect_platform_params
|
||||||
|
# 尝试根据 SN 生成唯一 MAC 地址
|
||||||
|
if [ -f "$EFUSE_SYSFS_PATH" ]; then
|
||||||
|
sn_offset=$(grep --binary-files=text -boP "$SN_PREFIX" "$EFUSE_SYSFS_PATH" | head -n1 | cut -d: -f1)
|
||||||
|
if [ -n "$sn_offset" ]; then
|
||||||
|
sn=$(cat "$EFUSE_SYSFS_PATH" | dd bs=1 skip="$sn_offset" count="$SN_EXPECTED_LENGTH" 2>/dev/null)
|
||||||
|
if [ ${#sn} -eq $SN_EXPECTED_LENGTH ]; then
|
||||||
|
echo "S/N: $sn" >&2 # 输出到 stderr,避免干扰返回值
|
||||||
|
# 使用 SN 的 SHA-256 哈希生成后 5 字节(避免多余管道)
|
||||||
|
sn_hash=$(printf %s "$sn" | sha256sum | cut -d' ' -f1)
|
||||||
|
# 直接用 Bash 子串获取哈希末 10 个字符并插入分隔符
|
||||||
|
mac_hex=${sn_hash: -10}
|
||||||
|
mac_suffix=$(printf "%s:%s:%s:%s:%s" "${mac_hex:0:2}" "${mac_hex:2:2}" "${mac_hex:4:2}" "${mac_hex:6:2}" "${mac_hex:8:2}")
|
||||||
|
printf "02:%s\n" "$mac_suffix"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 若 SN 获取失败,回退到随机逻辑
|
||||||
|
echo "警告: 无法获取 SN,回退到随机 MAC 生成" >&2
|
||||||
|
printf "02:%02x:%02x:%02x:%02x:%02x\n" \
|
||||||
|
$((RANDOM % 256)) \
|
||||||
|
$((RANDOM % 256)) \
|
||||||
|
$((RANDOM % 256)) \
|
||||||
|
$((RANDOM % 256)) \
|
||||||
|
$((RANDOM % 256))
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "正在生成基于 SN 的 MAC 地址..."
|
||||||
|
|
||||||
|
# 生成新的MAC地址
|
||||||
|
NEW_MAC=$(generate_random_mac)
|
||||||
|
echo "生成的MAC地址: $NEW_MAC"
|
||||||
|
|
||||||
|
# 验证 MAC 地址格式
|
||||||
|
if ! [[ $NEW_MAC =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then
|
||||||
|
echo "错误: 生成的 MAC 地址格式无效: $NEW_MAC"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 备份原配置文件
|
||||||
|
if [ -f "$NETWORK_CONFIG" ]; then
|
||||||
|
cp "$NETWORK_CONFIG" "${NETWORK_CONFIG}.backup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 更新网络配置文件
|
||||||
|
cat > "$NETWORK_CONFIG" << EOF
|
||||||
|
[Match]
|
||||||
|
Name=eth0
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
DHCP=yes
|
||||||
|
|
||||||
|
[Link]
|
||||||
|
MACAddress=$NEW_MAC
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "已更新网络配置文件: $NETWORK_CONFIG"
|
||||||
|
|
||||||
|
# 创建锁定文件,防止重复执行
|
||||||
|
mkdir -p "$(dirname "$LOCK_FILE")"
|
||||||
|
echo "MAC地址生成时间: $(date)" > "$LOCK_FILE"
|
||||||
|
|
||||||
|
# 禁用此服务,确保只运行一次
|
||||||
|
systemctl disable kvmd-generate-mac.service
|
||||||
|
|
||||||
|
echo "MAC地址生成完成: $NEW_MAC"
|
||||||
|
echo "服务已自动禁用,下次开机不会再执行"
|
||||||
|
|
||||||
|
exit 0
|
||||||
34
build/scripts/kvmd-firstrun.sh
Normal file
34
build/scripts/kvmd-firstrun.sh
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# KVMD首次运行初始化脚本
|
||||||
|
# 在首次开机时执行KVMD服务启动前的必要初始化操作
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
LOCK_FILE="/var/lib/kvmd/.kvmd-firstrun-completed"
|
||||||
|
|
||||||
|
# 检查是否已经执行过
|
||||||
|
[ -f "$LOCK_FILE" ] && { echo "[KVMD-FirstRun] 初始化已完成,跳过执行"; exit 0; }
|
||||||
|
|
||||||
|
echo "[KVMD-FirstRun] 开始KVMD首次运行初始化..."
|
||||||
|
|
||||||
|
# 1. 生成KVMD主证书
|
||||||
|
echo "[KVMD-FirstRun] 生成KVMD主证书..."
|
||||||
|
kvmd-gencert --do-the-thing
|
||||||
|
|
||||||
|
# 2. 生成VNC证书
|
||||||
|
echo "[KVMD-FirstRun] 生成VNC证书..."
|
||||||
|
kvmd-gencert --do-the-thing --vnc
|
||||||
|
|
||||||
|
# 3. 生成nginx配置文件
|
||||||
|
echo "[KVMD-FirstRun] 生成nginx配置文件..."
|
||||||
|
kvmd-nginx-mkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf || echo "[KVMD-FirstRun] 警告: nginx配置生成失败"
|
||||||
|
|
||||||
|
# 创建锁定文件
|
||||||
|
mkdir -p "$(dirname "$LOCK_FILE")"
|
||||||
|
echo "KVMD首次运行初始化完成 - $(date)" > "$LOCK_FILE"
|
||||||
|
|
||||||
|
# 禁用服务
|
||||||
|
systemctl disable kvmd-firstrun.service || echo "[KVMD-FirstRun] 警告: 服务禁用失败"
|
||||||
|
|
||||||
|
echo "[KVMD-FirstRun] 初始化完成!"
|
||||||
26
build/services/kvmd-firstrun.service
Normal file
26
build/services/kvmd-firstrun.service
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=KVMD First Run Initialization (One-time)
|
||||||
|
Documentation=https://github.com/your-repo/One-KVM
|
||||||
|
Before=kvmd.service
|
||||||
|
Before=kvmd-nginx.service
|
||||||
|
Before=kvmd-otg.service
|
||||||
|
Before=kvmd-vnc.service
|
||||||
|
Before=kvmd-ipmi.service
|
||||||
|
Before=kvmd-webterm.service
|
||||||
|
Before=kvmd-janus.service
|
||||||
|
Before=kvmd-media.service
|
||||||
|
After=local-fs.target
|
||||||
|
After=network.target
|
||||||
|
Wants=local-fs.target
|
||||||
|
ConditionPathExists=!/var/lib/kvmd/.kvmd-firstrun-completed
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/bin/kvmd-firstrun.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
TimeoutStartSec=300
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
18
build/services/kvmd-generate-mac.service
Normal file
18
build/services/kvmd-generate-mac.service
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Generate Random MAC Address for OneCloud (One-time)
|
||||||
|
Documentation=https://github.com/your-repo/One-KVM
|
||||||
|
Before=systemd-networkd.service
|
||||||
|
Before=network-pre.target
|
||||||
|
Wants=network-pre.target
|
||||||
|
After=local-fs.target
|
||||||
|
ConditionPathExists=!/var/lib/kvmd/.mac-generated
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/generate-random-mac.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
82
check-code.sh
Executable file
82
check-code.sh
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 本地代码质量检查脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "🔍 运行代码质量检查..."
|
||||||
|
|
||||||
|
# 检查参数,如果有参数则只运行指定的检查
|
||||||
|
CHECK_TYPE="${1:-all}"
|
||||||
|
|
||||||
|
run_flake8() {
|
||||||
|
echo "📝 运行 flake8 代码风格检查..."
|
||||||
|
flake8 --config=testenv/linters/flake8.ini kvmd testenv/tests *.py
|
||||||
|
}
|
||||||
|
|
||||||
|
run_pylint() {
|
||||||
|
echo "🔎 运行 pylint 代码质量分析..."
|
||||||
|
pylint -j0 --rcfile=testenv/linters/pylint.ini --output-format=colorized --reports=no kvmd testenv/tests *.py || true
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mypy() {
|
||||||
|
echo "🔧 运行 mypy 类型检查..."
|
||||||
|
mypy --config-file=testenv/linters/mypy.ini --cache-dir=testenv/.mypy_cache kvmd testenv/tests *.py || true
|
||||||
|
}
|
||||||
|
|
||||||
|
run_vulture() {
|
||||||
|
echo "💀 运行 vulture 死代码检测..."
|
||||||
|
vulture --ignore-names=_format_P,Plugin --ignore-decorators=@exposed_http,@exposed_ws,@pytest.fixture kvmd testenv/tests *.py testenv/linters/vulture-wl.py || true
|
||||||
|
}
|
||||||
|
|
||||||
|
run_eslint() {
|
||||||
|
echo "📜 运行 eslint JavaScript检查..."
|
||||||
|
if command -v eslint >/dev/null 2>&1; then
|
||||||
|
eslint --cache-location=/tmp --config=testenv/linters/eslintrc.js --color web/share/js || true
|
||||||
|
else
|
||||||
|
echo "⚠️ eslint 未安装,跳过"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_htmlhint() {
|
||||||
|
echo "📄 运行 htmlhint HTML检查..."
|
||||||
|
if command -v htmlhint >/dev/null 2>&1; then
|
||||||
|
htmlhint --config=testenv/linters/htmlhint.json web/*.html web/*/*.html || true
|
||||||
|
else
|
||||||
|
echo "⚠️ htmlhint 未安装,跳过"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_shellcheck() {
|
||||||
|
echo "🐚 运行 shellcheck Shell脚本检查..."
|
||||||
|
if command -v shellcheck >/dev/null 2>&1; then
|
||||||
|
shellcheck --color=always kvmd.install scripts/* || true
|
||||||
|
else
|
||||||
|
echo "⚠️ shellcheck 未安装,跳过"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$CHECK_TYPE" in
|
||||||
|
flake8) run_flake8 ;;
|
||||||
|
pylint) run_pylint ;;
|
||||||
|
mypy) run_mypy ;;
|
||||||
|
vulture) run_vulture ;;
|
||||||
|
eslint) run_eslint ;;
|
||||||
|
htmlhint) run_htmlhint ;;
|
||||||
|
shellcheck) run_shellcheck ;;
|
||||||
|
all)
|
||||||
|
run_flake8
|
||||||
|
run_pylint
|
||||||
|
run_mypy
|
||||||
|
run_vulture
|
||||||
|
run_eslint
|
||||||
|
run_htmlhint
|
||||||
|
run_shellcheck
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "用法: $0 [flake8|pylint|mypy|vulture|eslint|htmlhint|shellcheck|all]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "✅ 代码质量检查完成!"
|
||||||
@ -1,4 +1,7 @@
|
|||||||
video: {
|
video: {
|
||||||
sink = "kvmd::ustreamer::h264"
|
sink = "kvmd::ustreamer::h264"
|
||||||
}
|
}
|
||||||
|
acap: {
|
||||||
|
device = "hw:0,0"
|
||||||
|
tc358743 = "/dev/video0"
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
|
|
||||||
if [ -e /etc/update-motd.d/10-armbian-header ]; then /etc/update-motd.d/10-armbian-header; fi
|
if [ -e /etc/update-motd.d/10-armbian-header ]; then /etc/update-motd.d/10-armbian-header; fi
|
||||||
if [ -e /etc/update-motd.d/30-armbian-sysinfo ]; then /etc/update-motd.d/30-armbian-sysinfo; fi
|
if [ -e /etc/update-motd.d/30-armbian-sysinfo ]; then /etc/update-motd.d/30-armbian-sysinfo; fi
|
||||||
@ -15,8 +35,6 @@ printf "
|
|||||||
|
|
||||||
____________________________________________________________________________
|
____________________________________________________________________________
|
||||||
|
|
||||||
欢迎使用 One-KVM,基于开源程序 PiKVM 的 IP-KVM 应用
|
|
||||||
|
|
||||||
项目链接:
|
项目链接:
|
||||||
* One-KVM:https://github.com/mofeng-git/One-KVM
|
* One-KVM:https://github.com/mofeng-git/One-KVM
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
|
|
||||||
|
ATX=USBRELAY_HID
|
||||||
echo $ATX
|
echo $ATX
|
||||||
case $ATX in
|
case $ATX in
|
||||||
GPIO)
|
GPIO)
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
|
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
case $1 in
|
case $1 in
|
||||||
short)
|
short)
|
||||||
gpioset -m time -s 1 SHUTDOWNPIN=0
|
gpioset -m time -s 1 SHUTDOWNPIN=0
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import hid
|
import hid
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main PiKVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2023-2025 SilentWind <mofeng654321@hotmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
# ========================================================================== #
|
||||||
case $1 in
|
case $1 in
|
||||||
short)
|
short)
|
||||||
python3 /etc/kvmd/custom_atx/usbrelay_hid.py 1 on
|
python3 /etc/kvmd/custom_atx/usbrelay_hid.py 1 on
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
admin:$apr1$.6mu9N8n$xOuGesr4JZZkdiZo/j318.
|
admin:{SSHA512}3zSmw/L9zIkpQdX5bcy6HntTxltAzTuGNP6NjHRRgOcNZkA0K+Lsrj3QplO9Gr3BA5MYVVki9rAVnFNCcIdtYC6FkLJWCmHs
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
# This file describes the credentials for IPMI users. The first pair separated by colon
|
# This file describes the credentials for IPMI users in format "login:password",
|
||||||
# is the login and password with which the user can access to IPMI. The second pair
|
# one per line. The passwords are NOT encrypted.
|
||||||
# is the name and password with which the user can access to KVMD API. The arrow is used
|
|
||||||
# as a separator and shows the direction of user registration in the system.
|
|
||||||
#
|
#
|
||||||
# WARNING! IPMI protocol is completely unsafe by design. In short, the authentication
|
# WARNING! IPMI protocol is completely unsafe by design. In short, the authentication
|
||||||
# process for IPMI 2.0 mandates that the server send a salted SHA1 or MD5 hash of the
|
# process for IPMI 2.0 mandates that the server send a salted SHA1 or MD5 hash of the
|
||||||
# requested user's password to the client, prior to the client authenticating. Never use
|
# requested user's password to the client, prior to the client authenticating.
|
||||||
# the same passwords for KVMD and IPMI users. This default configuration is shown here
|
|
||||||
# for example only.
|
|
||||||
#
|
#
|
||||||
# And even better not to use IPMI. Instead, you can directly use KVMD API via curl.
|
# NEVER use the same passwords for KVMD and IPMI users.
|
||||||
|
# This default configuration is shown here just for the example only.
|
||||||
|
|
||||||
admin:admin -> admin:admin
|
admin:admin
|
||||||
|
|||||||
97
configs/kvmd/main/v4mini-hdmi-rpi4.yaml
Normal file
97
configs/kvmd/main/v4mini-hdmi-rpi4.yaml
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# Don't touch this file otherwise your device may stop working.
|
||||||
|
# Use override.yaml to modify required settings.
|
||||||
|
# You can find a working configuration in /usr/share/kvmd/configs.default/kvmd.
|
||||||
|
|
||||||
|
override: !include [override.d, override.yaml]
|
||||||
|
|
||||||
|
logging: !include logging.yaml
|
||||||
|
|
||||||
|
kvmd:
|
||||||
|
auth: !include auth.yaml
|
||||||
|
|
||||||
|
info:
|
||||||
|
hw:
|
||||||
|
ignore_past: true
|
||||||
|
fan:
|
||||||
|
unix: /run/kvmd/fan.sock
|
||||||
|
|
||||||
|
hid:
|
||||||
|
type: otg
|
||||||
|
|
||||||
|
atx:
|
||||||
|
type: gpio
|
||||||
|
power_led_pin: 4
|
||||||
|
hdd_led_pin: 5
|
||||||
|
power_switch_pin: 23
|
||||||
|
reset_switch_pin: 27
|
||||||
|
|
||||||
|
msd:
|
||||||
|
type: otg
|
||||||
|
|
||||||
|
streamer:
|
||||||
|
h264_bitrate:
|
||||||
|
default: 5000
|
||||||
|
cmd:
|
||||||
|
- "/usr/bin/ustreamer"
|
||||||
|
- "--device=/dev/kvmd-video"
|
||||||
|
- "--persistent"
|
||||||
|
- "--dv-timings"
|
||||||
|
- "--format=uyvy"
|
||||||
|
- "--buffers=6"
|
||||||
|
- "--encoder=m2m-image"
|
||||||
|
- "--workers=3"
|
||||||
|
- "--quality={quality}"
|
||||||
|
- "--desired-fps={desired_fps}"
|
||||||
|
- "--drop-same-frames=30"
|
||||||
|
- "--unix={unix}"
|
||||||
|
- "--unix-rm"
|
||||||
|
- "--unix-mode=0660"
|
||||||
|
- "--exit-on-parent-death"
|
||||||
|
- "--process-name-prefix={process_name_prefix}"
|
||||||
|
- "--notify-parent"
|
||||||
|
- "--no-log-colors"
|
||||||
|
- "--jpeg-sink=kvmd::ustreamer::jpeg"
|
||||||
|
- "--jpeg-sink-mode=0660"
|
||||||
|
- "--h264-sink=kvmd::ustreamer::h264"
|
||||||
|
- "--h264-sink-mode=0660"
|
||||||
|
- "--h264-bitrate={h264_bitrate}"
|
||||||
|
- "--h264-gop={h264_gop}"
|
||||||
|
|
||||||
|
gpio:
|
||||||
|
drivers:
|
||||||
|
__v4_locator__:
|
||||||
|
type: locator
|
||||||
|
|
||||||
|
scheme:
|
||||||
|
__v3_usb_breaker__:
|
||||||
|
pin: 22
|
||||||
|
mode: output
|
||||||
|
initial: true
|
||||||
|
pulse: false
|
||||||
|
|
||||||
|
__v4_locator__:
|
||||||
|
driver: __v4_locator__
|
||||||
|
pin: 12
|
||||||
|
mode: output
|
||||||
|
pulse: false
|
||||||
|
|
||||||
|
__v4_const1__:
|
||||||
|
pin: 6
|
||||||
|
mode: output
|
||||||
|
initial: false
|
||||||
|
switch: false
|
||||||
|
pulse: false
|
||||||
|
|
||||||
|
|
||||||
|
media:
|
||||||
|
memsink:
|
||||||
|
h264:
|
||||||
|
sink: "kvmd::ustreamer::h264"
|
||||||
|
|
||||||
|
|
||||||
|
vnc:
|
||||||
|
memsink:
|
||||||
|
jpeg:
|
||||||
|
sink: "kvmd::ustreamer::jpeg"
|
||||||
|
h264:
|
||||||
|
sink: "kvmd::ustreamer::h264"
|
||||||
98
configs/kvmd/main/v4plus-hdmi-rpi4.yaml
Normal file
98
configs/kvmd/main/v4plus-hdmi-rpi4.yaml
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Don't touch this file otherwise your device may stop working.
|
||||||
|
# Use override.yaml to modify required settings.
|
||||||
|
# You can find a working configuration in /usr/share/kvmd/configs.default/kvmd.
|
||||||
|
|
||||||
|
override: !include [override.d, override.yaml]
|
||||||
|
|
||||||
|
logging: !include logging.yaml
|
||||||
|
|
||||||
|
kvmd:
|
||||||
|
auth: !include auth.yaml
|
||||||
|
|
||||||
|
info:
|
||||||
|
hw:
|
||||||
|
ignore_past: true
|
||||||
|
fan:
|
||||||
|
unix: /run/kvmd/fan.sock
|
||||||
|
|
||||||
|
hid:
|
||||||
|
type: otg
|
||||||
|
|
||||||
|
atx:
|
||||||
|
type: gpio
|
||||||
|
power_led_pin: 4
|
||||||
|
hdd_led_pin: 5
|
||||||
|
power_switch_pin: 23
|
||||||
|
reset_switch_pin: 27
|
||||||
|
|
||||||
|
msd:
|
||||||
|
type: otg
|
||||||
|
|
||||||
|
streamer:
|
||||||
|
h264_bitrate:
|
||||||
|
default: 5000
|
||||||
|
cmd:
|
||||||
|
- "/usr/bin/ustreamer"
|
||||||
|
- "--device=/dev/kvmd-video"
|
||||||
|
- "--persistent"
|
||||||
|
- "--dv-timings"
|
||||||
|
- "--format=uyvy"
|
||||||
|
- "--format-swap-rgb"
|
||||||
|
- "--buffers=8"
|
||||||
|
- "--encoder=m2m-image"
|
||||||
|
- "--workers=3"
|
||||||
|
- "--quality={quality}"
|
||||||
|
- "--desired-fps={desired_fps}"
|
||||||
|
- "--drop-same-frames=30"
|
||||||
|
- "--unix={unix}"
|
||||||
|
- "--unix-rm"
|
||||||
|
- "--unix-mode=0660"
|
||||||
|
- "--exit-on-parent-death"
|
||||||
|
- "--process-name-prefix={process_name_prefix}"
|
||||||
|
- "--notify-parent"
|
||||||
|
- "--no-log-colors"
|
||||||
|
- "--jpeg-sink=kvmd::ustreamer::jpeg"
|
||||||
|
- "--jpeg-sink-mode=0660"
|
||||||
|
- "--h264-sink=kvmd::ustreamer::h264"
|
||||||
|
- "--h264-sink-mode=0660"
|
||||||
|
- "--h264-bitrate={h264_bitrate}"
|
||||||
|
- "--h264-gop={h264_gop}"
|
||||||
|
|
||||||
|
gpio:
|
||||||
|
drivers:
|
||||||
|
__v4_locator__:
|
||||||
|
type: locator
|
||||||
|
|
||||||
|
scheme:
|
||||||
|
__v3_usb_breaker__:
|
||||||
|
pin: 22
|
||||||
|
mode: output
|
||||||
|
initial: true
|
||||||
|
pulse: false
|
||||||
|
|
||||||
|
__v4_locator__:
|
||||||
|
driver: __v4_locator__
|
||||||
|
pin: 12
|
||||||
|
mode: output
|
||||||
|
pulse: false
|
||||||
|
|
||||||
|
__v4_const1__:
|
||||||
|
pin: 6
|
||||||
|
mode: output
|
||||||
|
initial: false
|
||||||
|
switch: false
|
||||||
|
pulse: false
|
||||||
|
|
||||||
|
|
||||||
|
media:
|
||||||
|
memsink:
|
||||||
|
h264:
|
||||||
|
sink: "kvmd::ustreamer::h264"
|
||||||
|
|
||||||
|
|
||||||
|
vnc:
|
||||||
|
memsink:
|
||||||
|
jpeg:
|
||||||
|
sink: "kvmd::ustreamer::jpeg"
|
||||||
|
h264:
|
||||||
|
sink: "kvmd::ustreamer::h264"
|
||||||
@ -4,6 +4,11 @@
|
|||||||
# will be displayed in the web interface.
|
# will be displayed in the web interface.
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: localhost.localdomain
|
host: "@auto"
|
||||||
|
|
||||||
kvm: {}
|
kvm: {
|
||||||
|
base_on: "PiKVM",
|
||||||
|
app_name: "One-KVM",
|
||||||
|
main_version: "241204",
|
||||||
|
author: "SilentWind"
|
||||||
|
}
|
||||||
|
|||||||
@ -2,16 +2,14 @@ kvmd:
|
|||||||
auth:
|
auth:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
server:
|
|
||||||
unix_mode: 0666
|
|
||||||
access_log_format: '[%P / %{X-Real-IP}i] ''%r'' => 响应:%s;大小:%b;来源:''%{Referer}i'';用户代理:''%{User-Agent}i'''
|
|
||||||
|
|
||||||
atx:
|
atx:
|
||||||
type: disabled
|
type: disabled
|
||||||
|
|
||||||
hid:
|
hid:
|
||||||
type: ch9329
|
type: ch9329
|
||||||
device: /dev/ttyUSB0
|
device: /dev/ttyUSB0
|
||||||
|
speed: 9600
|
||||||
|
read_timeout: 0.3
|
||||||
|
|
||||||
jiggler:
|
jiggler:
|
||||||
active: false
|
active: false
|
||||||
@ -23,6 +21,9 @@ kvmd:
|
|||||||
msd:
|
msd:
|
||||||
#type: otg
|
#type: otg
|
||||||
remount_cmd: /bin/true
|
remount_cmd: /bin/true
|
||||||
|
msd_path: /var/lib/kvmd/msd
|
||||||
|
normalfiles_path: NormalFiles
|
||||||
|
normalfiles_size: 256
|
||||||
|
|
||||||
ocr:
|
ocr:
|
||||||
langs:
|
langs:
|
||||||
@ -31,23 +32,23 @@ kvmd:
|
|||||||
|
|
||||||
streamer:
|
streamer:
|
||||||
resolution:
|
resolution:
|
||||||
default: 1280x720
|
default: 1920x1080
|
||||||
|
|
||||||
forever: true
|
forever: true
|
||||||
|
|
||||||
desired_fps:
|
desired_fps:
|
||||||
default: 30
|
default: 60
|
||||||
max: 60
|
max: 60
|
||||||
|
|
||||||
h264_bitrate:
|
h264_bitrate:
|
||||||
default: 2000
|
default: 8000
|
||||||
|
|
||||||
cmd:
|
cmd:
|
||||||
- "/usr/bin/ustreamer"
|
- "/usr/bin/ustreamer"
|
||||||
- "--device=/dev/video0"
|
- "--device=/dev/video0"
|
||||||
- "--persistent"
|
- "--persistent"
|
||||||
- "--format=mjpeg"
|
- "--format=mjpeg"
|
||||||
- "--encoder=LIBX264-VIDEO"
|
- "--encoder=FFMPEG-VIDEO"
|
||||||
- "--resolution={resolution}"
|
- "--resolution={resolution}"
|
||||||
- "--desired-fps={desired_fps}"
|
- "--desired-fps={desired_fps}"
|
||||||
- "--drop-same-frames=30"
|
- "--drop-same-frames=30"
|
||||||
@ -65,6 +66,7 @@ kvmd:
|
|||||||
- "--jpeg-sink-mode=0660"
|
- "--jpeg-sink-mode=0660"
|
||||||
- "--h264-bitrate={h264_bitrate}"
|
- "--h264-bitrate={h264_bitrate}"
|
||||||
- "--h264-gop={h264_gop}"
|
- "--h264-gop={h264_gop}"
|
||||||
|
- "--h264-hwenc=disabled"
|
||||||
- "--slowdown"
|
- "--slowdown"
|
||||||
gpio:
|
gpio:
|
||||||
drivers:
|
drivers:
|
||||||
@ -148,20 +150,26 @@ vnc:
|
|||||||
h264:
|
h264:
|
||||||
sink: "kvmd::ustreamer::h264"
|
sink: "kvmd::ustreamer::h264"
|
||||||
|
|
||||||
|
media:
|
||||||
|
memsink:
|
||||||
|
h264:
|
||||||
|
sink: 'kvmd::ustreamer::h264'
|
||||||
|
|
||||||
|
jpeg:
|
||||||
|
sink: 'kvmd::ustreamer::jpeg'
|
||||||
|
|
||||||
otgnet:
|
otgnet:
|
||||||
commands:
|
commands:
|
||||||
post_start_cmd:
|
post_start_cmd:
|
||||||
- "/bin/true"
|
- "/bin/true"
|
||||||
pre_stop_cmd:
|
pre_stop_cmd:
|
||||||
- "/bin/true"
|
- "/bin/true"
|
||||||
|
sysctl_cmd:
|
||||||
|
#- "/usr/sbin/sysctl"
|
||||||
|
- "/bin/true"
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
http:
|
http:
|
||||||
port: 8080
|
port: 8080
|
||||||
https:
|
https:
|
||||||
port: 4430
|
port: 4430
|
||||||
|
|
||||||
|
|
||||||
languages:
|
|
||||||
console: zh
|
|
||||||
web: zh
|
|
||||||
@ -32,6 +32,16 @@ stdout_logfile=/dev/stdout
|
|||||||
stdout_logfile_maxbytes = 0
|
stdout_logfile_maxbytes = 0
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
|
[program:kvmd-media]
|
||||||
|
command=python -m kvmd.apps.media --run
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
priority=13
|
||||||
|
stopasgroup=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes = 0
|
||||||
|
redirect_stderr=true
|
||||||
|
|
||||||
[program:kvmd-nginx]
|
[program:kvmd-nginx]
|
||||||
command=nginx -c /etc/kvmd/nginx/nginx.conf -g 'daemon off;user root; error_log stderr;'
|
command=nginx -c /etc/kvmd/nginx/nginx.conf -g 'daemon off;user root; error_log stderr;'
|
||||||
autostart=true
|
autostart=true
|
||||||
@ -53,4 +63,3 @@ stopasgroup=true
|
|||||||
stdout_logfile=/dev/stdout
|
stdout_logfile=/dev/stdout
|
||||||
stdout_logfile_maxbytes = 0
|
stdout_logfile_maxbytes = 0
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
# This file describes the credentials for VNCAuth. The left part before arrow is a passphrase
|
# This file contains passwords for the legacy VNCAuth, one per line.
|
||||||
# for VNCAuth. The right part is username and password with which the user can access to KVMD API.
|
# The passwords are NOT encrypted.
|
||||||
# The arrow is used as a separator and shows the relationship of user registrations on the system.
|
|
||||||
#
|
#
|
||||||
# Never use the same passwords for VNC and IPMI users. This default configuration is shown here
|
# WARNING! The VNCAuth method is NOT secure and should not be used at all.
|
||||||
# for example only.
|
# But we support it for compatibility with some clients.
|
||||||
#
|
#
|
||||||
# If this file does not contain any entries, VNCAuth will be disabled and you will only be able
|
# NEVER use the same passwords for KVMD, IPMI and VNCAuth users.
|
||||||
# to login in using your KVMD username and password using VeNCrypt methods.
|
|
||||||
|
|
||||||
# pa$$phr@se -> admin:password
|
|
||||||
admin -> admin:admin
|
admin -> admin:admin
|
||||||
|
|||||||
@ -24,6 +24,7 @@ location @login {
|
|||||||
|
|
||||||
location /login {
|
location /login {
|
||||||
root /usr/share/kvmd/web;
|
root /usr/share/kvmd/web;
|
||||||
|
include /etc/kvmd/nginx/loc-nocache.conf;
|
||||||
auth_request off;
|
auth_request off;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ location /api/hid/print {
|
|||||||
proxy_pass http://kvmd;
|
proxy_pass http://kvmd;
|
||||||
include /etc/kvmd/nginx/loc-proxy.conf;
|
include /etc/kvmd/nginx/loc-proxy.conf;
|
||||||
include /etc/kvmd/nginx/loc-bigpost.conf;
|
include /etc/kvmd/nginx/loc-bigpost.conf;
|
||||||
|
proxy_read_timeout 7d;
|
||||||
auth_request off;
|
auth_request off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,2 @@
|
|||||||
limit_rate 6250k;
|
|
||||||
limit_rate_after 50k;
|
|
||||||
client_max_body_size 0;
|
client_max_body_size 0;
|
||||||
proxy_request_buffering off;
|
proxy_request_buffering off;
|
||||||
|
|||||||
@ -39,9 +39,9 @@ http {
|
|||||||
% if https_enabled:
|
% if https_enabled:
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen ${http_port};
|
listen ${http_ipv4}:${http_port};
|
||||||
% if ipv6_enabled:
|
% if ipv6_enabled:
|
||||||
listen [::]:${http_port};
|
listen [${http_ipv6}]:${http_port};
|
||||||
% endif
|
% endif
|
||||||
include /etc/kvmd/nginx/certbot.ctx-server.conf;
|
include /etc/kvmd/nginx/certbot.ctx-server.conf;
|
||||||
location / {
|
location / {
|
||||||
@ -54,9 +54,9 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen ${https_port} ssl http2;
|
listen ${https_ipv4}:${https_port} ssl;
|
||||||
% if ipv6_enabled:
|
% if ipv6_enabled:
|
||||||
listen [::]:${https_port} ssl http2;
|
listen [${https_ipv6}]:${https_port} ssl;
|
||||||
% endif
|
% endif
|
||||||
include /etc/kvmd/nginx/ssl.conf;
|
include /etc/kvmd/nginx/ssl.conf;
|
||||||
include /etc/kvmd/nginx/kvmd.ctx-server.conf;
|
include /etc/kvmd/nginx/kvmd.ctx-server.conf;
|
||||||
@ -66,9 +66,9 @@ http {
|
|||||||
% else:
|
% else:
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen ${http_port};
|
listen ${http_ipv4}:${http_port};
|
||||||
% if ipv6_enabled:
|
% if ipv6_enabled:
|
||||||
listen [::]:${http_port};
|
listen [${http_ipv6}]:${http_port};
|
||||||
% endif
|
% endif
|
||||||
include /etc/kvmd/nginx/certbot.ctx-server.conf;
|
include /etc/kvmd/nginx/certbot.ctx-server.conf;
|
||||||
include /etc/kvmd/nginx/kvmd.ctx-server.conf;
|
include /etc/kvmd/nginx/kvmd.ctx-server.conf;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
initramfs initramfs-linux.img followkernel
|
initramfs initramfs-linux.img followkernel
|
||||||
|
|
||||||
hdmi_force_hotplug=1
|
hdmi_force_hotplug=1
|
||||||
gpu_mem=128
|
gpu_mem=192
|
||||||
enable_uart=1
|
enable_uart=1
|
||||||
dtoverlay=disable-bt
|
dtoverlay=disable-bt
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
s/rootwait/rootwait cma=128M/g
|
s/rootwait/rootwait cma=192M/g
|
||||||
|
|||||||
16
configs/os/services/kvmd-localhid.service
Normal file
16
configs/os/services/kvmd-localhid.service
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PiKVM - Local HID to KVMD proxy
|
||||||
|
After=kvmd.service systemd-udevd.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=kvmd-localhid
|
||||||
|
Group=kvmd-localhid
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
ExecStart=/usr/bin/kvmd-localhid --run
|
||||||
|
TimeoutStopSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
16
configs/os/services/kvmd-media.service
Normal file
16
configs/os/services/kvmd-media.service
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PiKVM - Media proxy server
|
||||||
|
After=kvmd.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=kvmd-media
|
||||||
|
Group=kvmd-media
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
ExecStart=/usr/bin/kvmd-media --run
|
||||||
|
TimeoutStopSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
12
configs/os/services/kvmd-oled-reboot.service
Normal file
12
configs/os/services/kvmd-oled-reboot.service
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PiKVM - Display reboot message on the OLED
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/bin/bash -c "kill -USR1 `systemctl show -P MainPID kvmd-oled`"
|
||||||
|
ExecStop=/bin/true
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=reboot.target
|
||||||
14
configs/os/services/kvmd-oled-shutdown.service
Normal file
14
configs/os/services/kvmd-oled-shutdown.service
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PiKVM - Display shutdown message on the OLED
|
||||||
|
Conflicts=reboot.target
|
||||||
|
Before=shutdown.target poweroff.target halt.target
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/bin/bash -c "kill -USR2 `systemctl show -P MainPID kvmd-oled`"
|
||||||
|
ExecStop=/bin/true
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=shutdown.target
|
||||||
15
configs/os/services/kvmd-oled.service
Normal file
15
configs/os/services/kvmd-oled.service
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PiKVM - A small OLED daemon
|
||||||
|
After=systemd-modules-load.service
|
||||||
|
ConditionPathExists=/dev/i2c-1
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
ExecStartPre=/usr/bin/kvmd-oled --interval=3 --clear-on-exit --image=@hello.ppm
|
||||||
|
ExecStart=/usr/bin/kvmd-oled
|
||||||
|
TimeoutStopSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@ -1,15 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=PiKVM - Video Passthrough on V4 Plus
|
|
||||||
Wants=dev-kvmd\x2dvideo.device
|
|
||||||
After=dev-kvmd\x2dvideo.device systemd-modules-load.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
Restart=always
|
|
||||||
RestartSec=3
|
|
||||||
|
|
||||||
ExecStart=/usr/bin/ustreamer-v4p --unix-follow /run/kvmd/ustreamer.sock
|
|
||||||
TimeoutStopSec=10
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@ -2,11 +2,11 @@
|
|||||||
Description=PiKVM - EDID loader for TC358743
|
Description=PiKVM - EDID loader for TC358743
|
||||||
Wants=dev-kvmd\x2dvideo.device
|
Wants=dev-kvmd\x2dvideo.device
|
||||||
After=dev-kvmd\x2dvideo.device systemd-modules-load.service
|
After=dev-kvmd\x2dvideo.device systemd-modules-load.service
|
||||||
Before=kvmd.service kvmd-pass.service
|
Before=kvmd.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --set-edid=file=/etc/kvmd/tc358743-edid.hex --fix-edid-checksums --info-edid
|
ExecStart=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --set-edid=file=/etc/kvmd/tc358743-edid.hex --info-edid
|
||||||
ExecStop=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --clear-edid
|
ExecStop=/usr/bin/v4l2-ctl --device=/dev/kvmd-video --clear-edid
|
||||||
RemainAfterExit=true
|
RemainAfterExit=true
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=One-KVM - The main daemon
|
Description=One-KVM - The main daemon
|
||||||
After=network.target network-online.target nss-lookup.target
|
After=network.target network-online.target nss-lookup.target rc-local.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=kvmd
|
User=kvmd
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
# Fix https://github.com/pikvm/pikvm/issues/1514:
|
||||||
|
# Wait for any single network interface, not all configured ones
|
||||||
|
# (Rationale: when user configures Wi-Fi via pikvm.txt or otherwise,
|
||||||
|
# we do not delete the Ethernet config, which means it will remain active
|
||||||
|
# regardless of whether the user ever intended to use Ethernet.)
|
||||||
|
[Service]
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/usr/lib/systemd/systemd-networkd-wait-online --any
|
||||||
@ -1,15 +1,20 @@
|
|||||||
g kvmd - -
|
g kvmd - -
|
||||||
|
g kvmd-selfauth - -
|
||||||
|
g kvmd-media - -
|
||||||
g kvmd-pst - -
|
g kvmd-pst - -
|
||||||
g kvmd-ipmi - -
|
g kvmd-ipmi - -
|
||||||
g kvmd-vnc - -
|
g kvmd-vnc - -
|
||||||
|
g kvmd-localhid - -
|
||||||
g kvmd-nginx - -
|
g kvmd-nginx - -
|
||||||
g kvmd-janus - -
|
g kvmd-janus - -
|
||||||
g kvmd-certbot - -
|
g kvmd-certbot - -
|
||||||
|
|
||||||
u kvmd - "PiKVM - The main daemon" -
|
u kvmd - "PiKVM - The main daemon" -
|
||||||
|
u kvmd-media - "PiKVM - The media proxy"
|
||||||
u kvmd-pst - "PiKVM - Persistent storage" -
|
u kvmd-pst - "PiKVM - Persistent storage" -
|
||||||
u kvmd-ipmi - "PiKVM - IPMI to KVMD proxy" -
|
u kvmd-ipmi - "PiKVM - IPMI to KVMD proxy" -
|
||||||
u kvmd-vnc - "PiKVM - VNC to KVMD/Streamer proxy" -
|
u kvmd-vnc - "PiKVM - VNC to KVMD/Streamer proxy" -
|
||||||
|
u kvmd-localhid - "PiKVM - Local HID to KVMD proxy" -
|
||||||
u kvmd-nginx - "PiKVM - HTTP entrypoint" -
|
u kvmd-nginx - "PiKVM - HTTP entrypoint" -
|
||||||
u kvmd-janus - "PiKVM - Janus WebRTC Gateway" -
|
u kvmd-janus - "PiKVM - Janus WebRTC Gateway" -
|
||||||
u kvmd-certbot - "PiKVM - Certbot-Renew for KVMD-Nginx"
|
u kvmd-certbot - "PiKVM - Certbot-Renew for KVMD-Nginx"
|
||||||
@ -19,18 +24,29 @@ m kvmd gpio
|
|||||||
m kvmd uucp
|
m kvmd uucp
|
||||||
m kvmd spi
|
m kvmd spi
|
||||||
m kvmd systemd-journal
|
m kvmd systemd-journal
|
||||||
|
m kvmd kvmd-media
|
||||||
|
m kvmd kvmd-pst
|
||||||
|
|
||||||
|
m kvmd-media kvmd
|
||||||
|
|
||||||
m kvmd-pst kvmd
|
m kvmd-pst kvmd
|
||||||
|
|
||||||
m kvmd-ipmi kvmd
|
m kvmd-ipmi kvmd
|
||||||
|
m kvmd-ipmi kvmd-selfauth
|
||||||
|
|
||||||
m kvmd-vnc kvmd
|
m kvmd-vnc kvmd
|
||||||
|
m kvmd-vnc kvmd-selfauth
|
||||||
m kvmd-vnc kvmd-certbot
|
m kvmd-vnc kvmd-certbot
|
||||||
|
|
||||||
|
m kvmd-localhid input
|
||||||
|
m kvmd-localhid kvmd
|
||||||
|
m kvmd-localhid kvmd-selfauth
|
||||||
|
|
||||||
m kvmd-janus kvmd
|
m kvmd-janus kvmd
|
||||||
m kvmd-janus audio
|
m kvmd-janus audio
|
||||||
|
|
||||||
m kvmd-nginx kvmd
|
m kvmd-nginx kvmd
|
||||||
|
m kvmd-nginx kvmd-media
|
||||||
m kvmd-nginx kvmd-janus
|
m kvmd-nginx kvmd-janus
|
||||||
m kvmd-nginx kvmd-certbot
|
m kvmd-nginx kvmd-certbot
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,15 @@
|
|||||||
# Here are described some bindings for PiKVM devices.
|
# Here are described some bindings for PiKVM devices.
|
||||||
# Do not edit this file.
|
# Do not edit this file.
|
||||||
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="eda3", SYMLINK+="kvmd-hid-bridge"
|
|
||||||
|
ACTION!="remove", KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="eda3", SYMLINK+="kvmd-hid-bridge"
|
||||||
|
ACTION!="remove", KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="1080", SYMLINK+="kvmd-switch"
|
||||||
|
|
||||||
|
# Disable USB autosuspend for critical devices
|
||||||
|
ACTION!="remove", SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="eda3", GOTO="kvmd-usb"
|
||||||
|
ACTION!="remove", SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="1080", GOTO="kvmd-usb"
|
||||||
|
GOTO="end"
|
||||||
|
|
||||||
|
LABEL="kvmd-usb"
|
||||||
|
ATTR{power/control}="on", ATTR{power/autosuspend_delay_ms}="-1"
|
||||||
|
|
||||||
|
LABEL="end"
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
# https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name
|
|
||||||
# https://wiki.archlinux.org/index.php/Udev#Setting_static_device_names
|
|
||||||
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTR{index}=="0", GROUP="kvmd", SYMLINK+="kvmd-video"
|
|
||||||
KERNEL=="hidg0", GROUP="kvmd", SYMLINK+="kvmd-hid-keyboard"
|
|
||||||
KERNEL=="hidg1", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse"
|
|
||||||
KERNEL=="hidg2", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse-alt"
|
|
||||||
KERNEL=="ttyUSB0", GROUP="kvmd", SYMLINK+="kvmd-hid"
|
|
||||||
@ -4,3 +4,4 @@ KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", PROGRAM="/usr/bin/kvmd-udev-hdm
|
|||||||
KERNEL=="hidg0", GROUP="kvmd", SYMLINK+="kvmd-hid-keyboard"
|
KERNEL=="hidg0", GROUP="kvmd", SYMLINK+="kvmd-hid-keyboard"
|
||||||
KERNEL=="hidg1", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse"
|
KERNEL=="hidg1", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse"
|
||||||
KERNEL=="hidg2", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse-alt"
|
KERNEL=="hidg2", GROUP="kvmd", SYMLINK+="kvmd-hid-mouse-alt"
|
||||||
|
KERNEL=="ttyUSB0", GROUP="kvmd", SYMLINK+="kvmd-hid"
|
||||||
|
|||||||
1663
contrib/keymaps/en-us-colemak
Normal file
1663
contrib/keymaps/en-us-colemak
Normal file
File diff suppressed because it is too large
Load Diff
@ -49,13 +49,15 @@ oneeighth 0x03 shift altgr
|
|||||||
quotedbl 0x04
|
quotedbl 0x04
|
||||||
3 0x04 shift
|
3 0x04 shift
|
||||||
numbersign 0x04 altgr
|
numbersign 0x04 altgr
|
||||||
sterling 0x04 shift altgr
|
# KVMD
|
||||||
|
#sterling 0x04 shift altgr
|
||||||
|
|
||||||
# evdev 5 (0x5), QKeyCode "4", number 0x5
|
# evdev 5 (0x5), QKeyCode "4", number 0x5
|
||||||
apostrophe 0x05
|
apostrophe 0x05
|
||||||
4 0x05 shift
|
4 0x05 shift
|
||||||
braceleft 0x05 altgr
|
braceleft 0x05 altgr
|
||||||
dollar 0x05 shift altgr
|
# KVMD
|
||||||
|
#dollar 0x05 shift altgr
|
||||||
|
|
||||||
# evdev 6 (0x6), QKeyCode "5", number 0x6
|
# evdev 6 (0x6), QKeyCode "5", number 0x6
|
||||||
parenleft 0x06
|
parenleft 0x06
|
||||||
@ -91,7 +93,8 @@ plusminus 0x0a shift altgr
|
|||||||
agrave 0x0b
|
agrave 0x0b
|
||||||
0 0x0b shift
|
0 0x0b shift
|
||||||
at 0x0b altgr
|
at 0x0b altgr
|
||||||
degree 0x0b shift altgr
|
# KVMD
|
||||||
|
#degree 0x0b shift altgr
|
||||||
|
|
||||||
# evdev 12 (0xc), QKeyCode "minus", number 0xc
|
# evdev 12 (0xc), QKeyCode "minus", number 0xc
|
||||||
parenright 0x0c
|
parenright 0x0c
|
||||||
@ -122,7 +125,8 @@ AE 0x10 shift altgr
|
|||||||
z 0x11
|
z 0x11
|
||||||
Z 0x11 shift
|
Z 0x11 shift
|
||||||
guillemotleft 0x11 altgr
|
guillemotleft 0x11 altgr
|
||||||
less 0x11 shift altgr
|
#KVMD
|
||||||
|
#less 0x11 shift altgr
|
||||||
|
|
||||||
# evdev 18 (0x12), QKeyCode "e", number 0x12
|
# evdev 18 (0x12), QKeyCode "e", number 0x12
|
||||||
e 0x12
|
e 0x12
|
||||||
@ -200,7 +204,8 @@ Greek_OMEGA 0x1e shift altgr
|
|||||||
s 0x1f
|
s 0x1f
|
||||||
S 0x1f shift
|
S 0x1f shift
|
||||||
ssharp 0x1f altgr
|
ssharp 0x1f altgr
|
||||||
section 0x1f shift altgr
|
# KVMD
|
||||||
|
#section 0x1f shift altgr
|
||||||
|
|
||||||
# evdev 32 (0x20), QKeyCode "d", number 0x20
|
# evdev 32 (0x20), QKeyCode "d", number 0x20
|
||||||
d 0x20
|
d 0x20
|
||||||
@ -247,7 +252,8 @@ Lstroke 0x26 shift altgr
|
|||||||
# evdev 39 (0x27), QKeyCode "semicolon", number 0x27
|
# evdev 39 (0x27), QKeyCode "semicolon", number 0x27
|
||||||
m 0x27
|
m 0x27
|
||||||
M 0x27 shift
|
M 0x27 shift
|
||||||
mu 0x27 altgr
|
# KVMD
|
||||||
|
#mu 0x27 altgr
|
||||||
masculine 0x27 shift altgr
|
masculine 0x27 shift altgr
|
||||||
|
|
||||||
# evdev 40 (0x28), QKeyCode "apostrophe", number 0x28
|
# evdev 40 (0x28), QKeyCode "apostrophe", number 0x28
|
||||||
@ -280,7 +286,8 @@ Lstroke 0x2c shift altgr
|
|||||||
x 0x2d
|
x 0x2d
|
||||||
X 0x2d shift
|
X 0x2d shift
|
||||||
guillemotright 0x2d altgr
|
guillemotright 0x2d altgr
|
||||||
greater 0x2d shift altgr
|
# KVMD
|
||||||
|
#greater 0x2d shift altgr
|
||||||
|
|
||||||
# evdev 46 (0x2e), QKeyCode "c", number 0x2e
|
# evdev 46 (0x2e), QKeyCode "c", number 0x2e
|
||||||
c 0x2e
|
c 0x2e
|
||||||
|
|||||||
6
extras/gostc/manifest.yaml
Normal file
6
extras/gostc/manifest.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
name: GOSTC
|
||||||
|
description: GOSTC Server
|
||||||
|
icon: share/svg/gostc.svg
|
||||||
|
path: extras/gostc
|
||||||
|
daemon: kvmd-gostc
|
||||||
|
place: 11
|
||||||
7
extras/gostc/nginx.ctx-server.conf
Normal file
7
extras/gostc/nginx.ctx-server.conf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
location /extras/gostc {
|
||||||
|
proxy_pass http://127.0.0.1:18080;
|
||||||
|
include /etc/kvmd/nginx/loc-proxy.conf;
|
||||||
|
include /etc/kvmd/nginx/loc-websocket.conf;
|
||||||
|
include /etc/kvmd/nginx/loc-login.conf;
|
||||||
|
include /etc/kvmd/nginx/loc-nocache.conf;
|
||||||
|
}
|
||||||
5
extras/media/manifest.yaml
Normal file
5
extras/media/manifest.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
name: Media
|
||||||
|
description: KVMD Media Proxy
|
||||||
|
path: media
|
||||||
|
daemon: kvmd-media
|
||||||
|
place: -1
|
||||||
3
extras/media/nginx.ctx-http.conf
Normal file
3
extras/media/nginx.ctx-http.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
upstream media {
|
||||||
|
server unix:/run/kvmd/media.sock fail_timeout=0s max_fails=0;
|
||||||
|
}
|
||||||
7
extras/media/nginx.ctx-server.conf
Normal file
7
extras/media/nginx.ctx-server.conf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
location /api/media/ws {
|
||||||
|
rewrite ^/api/media/ws$ /ws break;
|
||||||
|
rewrite ^/api/media/ws\?(.*)$ /ws?$1 break;
|
||||||
|
proxy_pass http://media;
|
||||||
|
include /etc/kvmd/nginx/loc-proxy.conf;
|
||||||
|
include /etc/kvmd/nginx/loc-websocket.conf;
|
||||||
|
}
|
||||||
@ -69,9 +69,10 @@ class _X11Key:
|
|||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class _KeyMapping:
|
class _KeyMapping:
|
||||||
web_name: str
|
web_name: str
|
||||||
|
evdev_name: str
|
||||||
mcu_code: int
|
mcu_code: int
|
||||||
usb_key: _UsbKey
|
usb_key: _UsbKey
|
||||||
ps2_key: _Ps2Key
|
ps2_key: (_Ps2Key | None)
|
||||||
at1_code: int
|
at1_code: int
|
||||||
x11_keys: set[_X11Key]
|
x11_keys: set[_X11Key]
|
||||||
|
|
||||||
@ -107,7 +108,9 @@ def _parse_usb_key(key: str) -> _UsbKey:
|
|||||||
return _UsbKey(code, is_modifier)
|
return _UsbKey(code, is_modifier)
|
||||||
|
|
||||||
|
|
||||||
def _parse_ps2_key(key: str) -> _Ps2Key:
|
def _parse_ps2_key(key: str) -> (_Ps2Key | None):
|
||||||
|
if ":" not in key:
|
||||||
|
return None
|
||||||
(code_type, raw_code) = key.split(":")
|
(code_type, raw_code) = key.split(":")
|
||||||
return _Ps2Key(
|
return _Ps2Key(
|
||||||
code=int(raw_code, 16),
|
code=int(raw_code, 16),
|
||||||
@ -122,6 +125,7 @@ def _read_keymap_csv(path: str) -> list[_KeyMapping]:
|
|||||||
if len(row) >= 6:
|
if len(row) >= 6:
|
||||||
keymap.append(_KeyMapping(
|
keymap.append(_KeyMapping(
|
||||||
web_name=row["web_name"],
|
web_name=row["web_name"],
|
||||||
|
evdev_name=row["evdev_name"],
|
||||||
mcu_code=int(row["mcu_code"]),
|
mcu_code=int(row["mcu_code"]),
|
||||||
usb_key=_parse_usb_key(row["usb_key"]),
|
usb_key=_parse_usb_key(row["usb_key"]),
|
||||||
ps2_key=_parse_ps2_key(row["ps2_key"]),
|
ps2_key=_parse_ps2_key(row["ps2_key"]),
|
||||||
@ -150,6 +154,7 @@ def main() -> None:
|
|||||||
|
|
||||||
# Fields list:
|
# Fields list:
|
||||||
# - Web
|
# - Web
|
||||||
|
# - Linux/evdev
|
||||||
# - MCU code
|
# - MCU code
|
||||||
# - USB code (^ for the modifier mask)
|
# - USB code (^ for the modifier mask)
|
||||||
# - PS/2 key
|
# - PS/2 key
|
||||||
|
|||||||
@ -24,8 +24,8 @@ upload:
|
|||||||
bash -ex -c " \
|
bash -ex -c " \
|
||||||
current=`cat .current`; \
|
current=`cat .current`; \
|
||||||
if [ '$($@_CURRENT)' == 'spi' ] || [ '$($@_CURRENT)' == 'aum' ]; then \
|
if [ '$($@_CURRENT)' == 'spi' ] || [ '$($@_CURRENT)' == 'aum' ]; then \
|
||||||
gpioset 0 25=1; \
|
gpioset -c gpiochip0 -t 30ms,0 25=1; \
|
||||||
gpioset 0 25=0; \
|
gpioset -c gpiochip0 -t 30ms,0 25=0; \
|
||||||
fi \
|
fi \
|
||||||
"
|
"
|
||||||
platformio run --environment '$($@_CURRENT)' --project-conf 'platformio-$($@_CONFIG).ini' --target upload
|
platformio run --environment '$($@_CURRENT)' --project-conf 'platformio-$($@_CONFIG).ini' --target upload
|
||||||
|
|||||||
@ -2,6 +2,7 @@ programmer
|
|||||||
id = "rpi";
|
id = "rpi";
|
||||||
desc = "RPi SPI programmer";
|
desc = "RPi SPI programmer";
|
||||||
type = "linuxspi";
|
type = "linuxspi";
|
||||||
|
prog_modes = PM_ISP;
|
||||||
reset = 25;
|
reset = 25;
|
||||||
baudrate = 400000;
|
baudrate = 400000;
|
||||||
;
|
;
|
||||||
|
|||||||
@ -148,5 +148,8 @@ void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) {
|
|||||||
case 109: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 19; return; // KanaMode
|
case 109: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 19; return; // KanaMode
|
||||||
case 110: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 100; return; // Convert
|
case 110: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 100; return; // Convert
|
||||||
case 111: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 103; return; // NonConvert
|
case 111: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 103; return; // NonConvert
|
||||||
|
case 112: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 35; return; // AudioVolumeMute
|
||||||
|
case 113: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 50; return; // AudioVolumeUp
|
||||||
|
case 114: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 33; return; // AudioVolumeDown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,9 @@ void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) {
|
|||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
% for km in sorted(keymap, key=operator.attrgetter("mcu_code")):
|
% for km in sorted(keymap, key=operator.attrgetter("mcu_code")):
|
||||||
|
% if km.ps2_key is not None:
|
||||||
case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.web_name}
|
case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.web_name}
|
||||||
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,6 +136,10 @@ uint8_t keymapUsb(uint8_t code) {
|
|||||||
case 109: return 136; // KanaMode
|
case 109: return 136; // KanaMode
|
||||||
case 110: return 138; // Convert
|
case 110: return 138; // Convert
|
||||||
case 111: return 139; // NonConvert
|
case 111: return 139; // NonConvert
|
||||||
|
case 112: return 127; // AudioVolumeMute
|
||||||
|
case 113: return 128; // AudioVolumeUp
|
||||||
|
case 114: return 129; // AudioVolumeDown
|
||||||
|
case 115: return 111; // F20
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,8 +82,6 @@ build_flags =
|
|||||||
-DCDC_DISABLED
|
-DCDC_DISABLED
|
||||||
upload_protocol = custom
|
upload_protocol = custom
|
||||||
upload_flags =
|
upload_flags =
|
||||||
-C
|
|
||||||
$PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf
|
|
||||||
-C
|
-C
|
||||||
+avrdude-rpi.conf
|
+avrdude-rpi.conf
|
||||||
-P
|
-P
|
||||||
|
|||||||
@ -28,11 +28,14 @@ define libdep
|
|||||||
endef
|
endef
|
||||||
.pico-sdk:
|
.pico-sdk:
|
||||||
$(call libdep,pico-sdk,raspberrypi/pico-sdk,6a7db34ff63345a7badec79ebea3aaef1712f374)
|
$(call libdep,pico-sdk,raspberrypi/pico-sdk,6a7db34ff63345a7badec79ebea3aaef1712f374)
|
||||||
|
.pico-sdk.patches: .pico-sdk
|
||||||
|
patch -d .pico-sdk -p1 < patches/pico-sdk.patch
|
||||||
|
touch .pico-sdk.patches
|
||||||
.tinyusb:
|
.tinyusb:
|
||||||
$(call libdep,tinyusb,hathach/tinyusb,d713571cd44f05d2fc72efc09c670787b74106e0)
|
$(call libdep,tinyusb,hathach/tinyusb,d713571cd44f05d2fc72efc09c670787b74106e0)
|
||||||
.ps2x2pico:
|
.ps2x2pico:
|
||||||
$(call libdep,ps2x2pico,No0ne/ps2x2pico,404aaf02949d5bee8013e3b5d0b3239abf6e13bd)
|
$(call libdep,ps2x2pico,No0ne/ps2x2pico,26ce89d597e598bb0ac636622e064202d91a9efc)
|
||||||
deps: .pico-sdk .tinyusb .ps2x2pico
|
deps: .pico-sdk .pico-sdk.patches .tinyusb .ps2x2pico
|
||||||
|
|
||||||
|
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
|
|||||||
10
hid/pico/patches/pico-sdk.patch
Normal file
10
hid/pico/patches/pico-sdk.patch
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
diff --git a/tools/pioasm/CMakeLists.txt b/tools/pioasm/CMakeLists.txt
|
||||||
|
index 322408a..fc8e4b8 100644
|
||||||
|
--- a/tools/pioasm/CMakeLists.txt
|
||||||
|
+++ b/tools/pioasm/CMakeLists.txt
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-cmake_minimum_required(VERSION 3.4)
|
||||||
|
+cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(pioasm CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
@ -19,7 +19,7 @@ target_sources(${target_name} PRIVATE
|
|||||||
${PS2_PATH}/ps2in.c
|
${PS2_PATH}/ps2in.c
|
||||||
${PS2_PATH}/ps2kb.c
|
${PS2_PATH}/ps2kb.c
|
||||||
${PS2_PATH}/ps2ms.c
|
${PS2_PATH}/ps2ms.c
|
||||||
${PS2_PATH}/scancodesets.c
|
${PS2_PATH}/scancodes.c
|
||||||
)
|
)
|
||||||
target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage)
|
target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage)
|
||||||
target_compile_options(${target_name} PRIVATE -Wall -Wextra)
|
target_compile_options(${target_name} PRIVATE -Wall -Wextra)
|
||||||
|
|||||||
@ -53,7 +53,7 @@ static u8 _kbd_keys[6] = {0};
|
|||||||
static u8 _mouse_buttons = 0;
|
static u8 _mouse_buttons = 0;
|
||||||
static s16 _mouse_abs_x = 0;
|
static s16 _mouse_abs_x = 0;
|
||||||
static s16 _mouse_abs_y = 0;
|
static s16 _mouse_abs_y = 0;
|
||||||
#define _MOUSE_CLEAR { _mouse_buttons = 0; _mouse_abs_x = 0; _mouse_abs_y = 0; }
|
#define _MOUSE_CLEAR { _mouse_buttons = 0; }
|
||||||
|
|
||||||
|
|
||||||
static void _kbd_sync_report(bool new);
|
static void _kbd_sync_report(bool new);
|
||||||
@ -193,7 +193,7 @@ void ph_usb_send_clear(void) {
|
|||||||
if (PH_O_IS_MOUSE_USB) {
|
if (PH_O_IS_MOUSE_USB) {
|
||||||
_MOUSE_CLEAR;
|
_MOUSE_CLEAR;
|
||||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||||
_mouse_abs_send_report(0, 0);
|
_mouse_abs_send_report(_mouse_abs_x, _mouse_abs_y);
|
||||||
} else { // PH_O_IS_MOUSE_USB_REL
|
} else { // PH_O_IS_MOUSE_USB_REL
|
||||||
_mouse_rel_send_report(0, 0, 0, 0);
|
_mouse_rel_send_report(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,6 +138,10 @@ inline u8 ph_usb_keymap(u8 key) {
|
|||||||
case 109: return 136; // KanaMode
|
case 109: return 136; // KanaMode
|
||||||
case 110: return 138; // Convert
|
case 110: return 138; // Convert
|
||||||
case 111: return 139; // NonConvert
|
case 111: return 139; // NonConvert
|
||||||
|
case 112: return 127; // AudioVolumeMute
|
||||||
|
case 113: return 128; // AudioVolumeUp
|
||||||
|
case 114: return 129; // AudioVolumeDown
|
||||||
|
case 115: return 111; // F20
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
228
keymap.csv
228
keymap.csv
@ -1,112 +1,116 @@
|
|||||||
web_name,mcu_code,usb_key,ps2_key,at1_code,x11_names
|
web_name,evdev_name,mcu_code,usb_key,ps2_key,at1_code,x11_names
|
||||||
KeyA,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a"
|
KeyA,KEY_A,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a"
|
||||||
KeyB,2,0x05,reg:0x32,0x30,"^XK_B,XK_b"
|
KeyB,KEY_B,2,0x05,reg:0x32,0x30,"^XK_B,XK_b"
|
||||||
KeyC,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c"
|
KeyC,KEY_C,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c"
|
||||||
KeyD,4,0x07,reg:0x23,0x20,"^XK_D,XK_d"
|
KeyD,KEY_D,4,0x07,reg:0x23,0x20,"^XK_D,XK_d"
|
||||||
KeyE,5,0x08,reg:0x24,0x12,"^XK_E,XK_e"
|
KeyE,KEY_E,5,0x08,reg:0x24,0x12,"^XK_E,XK_e"
|
||||||
KeyF,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f"
|
KeyF,KEY_F,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f"
|
||||||
KeyG,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g"
|
KeyG,KEY_G,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g"
|
||||||
KeyH,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h"
|
KeyH,KEY_H,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h"
|
||||||
KeyI,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i"
|
KeyI,KEY_I,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i"
|
||||||
KeyJ,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j"
|
KeyJ,KEY_J,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j"
|
||||||
KeyK,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k"
|
KeyK,KEY_K,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k"
|
||||||
KeyL,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l"
|
KeyL,KEY_L,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l"
|
||||||
KeyM,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m"
|
KeyM,KEY_M,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m"
|
||||||
KeyN,14,0x11,reg:0x31,0x31,"^XK_N,XK_n"
|
KeyN,KEY_N,14,0x11,reg:0x31,0x31,"^XK_N,XK_n"
|
||||||
KeyO,15,0x12,reg:0x44,0x18,"^XK_O,XK_o"
|
KeyO,KEY_O,15,0x12,reg:0x44,0x18,"^XK_O,XK_o"
|
||||||
KeyP,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p"
|
KeyP,KEY_P,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p"
|
||||||
KeyQ,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q"
|
KeyQ,KEY_Q,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q"
|
||||||
KeyR,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r"
|
KeyR,KEY_R,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r"
|
||||||
KeyS,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s"
|
KeyS,KEY_S,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s"
|
||||||
KeyT,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t"
|
KeyT,KEY_T,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t"
|
||||||
KeyU,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u"
|
KeyU,KEY_U,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u"
|
||||||
KeyV,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v"
|
KeyV,KEY_V,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v"
|
||||||
KeyW,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w"
|
KeyW,KEY_W,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w"
|
||||||
KeyX,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x"
|
KeyX,KEY_X,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x"
|
||||||
KeyY,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y"
|
KeyY,KEY_Y,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y"
|
||||||
KeyZ,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z"
|
KeyZ,KEY_Z,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z"
|
||||||
Digit1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam"
|
Digit1,KEY_1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam"
|
||||||
Digit2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at"
|
Digit2,KEY_2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at"
|
||||||
Digit3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign"
|
Digit3,KEY_3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign"
|
||||||
Digit4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar"
|
Digit4,KEY_4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar"
|
||||||
Digit5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent"
|
Digit5,KEY_5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent"
|
||||||
Digit6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum"
|
Digit6,KEY_6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum"
|
||||||
Digit7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand"
|
Digit7,KEY_7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand"
|
||||||
Digit8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk"
|
Digit8,KEY_8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk"
|
||||||
Digit9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft"
|
Digit9,KEY_9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft"
|
||||||
Digit0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright"
|
Digit0,KEY_0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright"
|
||||||
Enter,37,0x28,reg:0x5a,0x1c,XK_Return
|
Enter,KEY_ENTER,37,0x28,reg:0x5a,0x1c,XK_Return
|
||||||
Escape,38,0x29,reg:0x76,0x01,XK_Escape
|
Escape,KEY_ESC,38,0x29,reg:0x76,0x01,XK_Escape
|
||||||
Backspace,39,0x2a,reg:0x66,0x0e,XK_BackSpace
|
Backspace,KEY_BACKSPACE,39,0x2a,reg:0x66,0x0e,XK_BackSpace
|
||||||
Tab,40,0x2b,reg:0x0d,0x0f,XK_Tab
|
Tab,KEY_TAB,40,0x2b,reg:0x0d,0x0f,XK_Tab
|
||||||
Space,41,0x2c,reg:0x29,0x39,XK_space
|
Space,KEY_SPACE,41,0x2c,reg:0x29,0x39,XK_space
|
||||||
Minus,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore"
|
Minus,KEY_MINUS,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore"
|
||||||
Equal,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus"
|
Equal,KEY_EQUAL,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus"
|
||||||
BracketLeft,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft"
|
BracketLeft,KEY_LEFTBRACE,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft"
|
||||||
BracketRight,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright"
|
BracketRight,KEY_RIGHTBRACE,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright"
|
||||||
Backslash,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar"
|
Backslash,KEY_BACKSLASH,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar"
|
||||||
Semicolon,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon"
|
Semicolon,KEY_SEMICOLON,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon"
|
||||||
Quote,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl"
|
Quote,KEY_APOSTROPHE,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl"
|
||||||
Backquote,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde"
|
Backquote,KEY_GRAVE,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde"
|
||||||
Comma,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less"
|
Comma,KEY_COMMA,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less"
|
||||||
Period,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater"
|
Period,KEY_DOT,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater"
|
||||||
Slash,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question"
|
Slash,KEY_SLASH,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question"
|
||||||
CapsLock,53,0x39,reg:0x58,0x3a,XK_Caps_Lock
|
CapsLock,KEY_CAPSLOCK,53,0x39,reg:0x58,0x3a,XK_Caps_Lock
|
||||||
F1,54,0x3a,reg:0x05,0x3b,XK_F1
|
F1,KEY_F1,54,0x3a,reg:0x05,0x3b,XK_F1
|
||||||
F2,55,0x3b,reg:0x06,0x3c,XK_F2
|
F2,KEY_F2,55,0x3b,reg:0x06,0x3c,XK_F2
|
||||||
F3,56,0x3c,reg:0x04,0x3d,XK_F3
|
F3,KEY_F3,56,0x3c,reg:0x04,0x3d,XK_F3
|
||||||
F4,57,0x3d,reg:0x0c,0x3e,XK_F4
|
F4,KEY_F4,57,0x3d,reg:0x0c,0x3e,XK_F4
|
||||||
F5,58,0x3e,reg:0x03,0x3f,XK_F5
|
F5,KEY_F5,58,0x3e,reg:0x03,0x3f,XK_F5
|
||||||
F6,59,0x3f,reg:0x0b,0x40,XK_F6
|
F6,KEY_F6,59,0x3f,reg:0x0b,0x40,XK_F6
|
||||||
F7,60,0x40,reg:0x83,0x41,XK_F7
|
F7,KEY_F7,60,0x40,reg:0x83,0x41,XK_F7
|
||||||
F8,61,0x41,reg:0x0a,0x42,XK_F8
|
F8,KEY_F8,61,0x41,reg:0x0a,0x42,XK_F8
|
||||||
F9,62,0x42,reg:0x01,0x43,XK_F9
|
F9,KEY_F9,62,0x42,reg:0x01,0x43,XK_F9
|
||||||
F10,63,0x43,reg:0x09,0x44,XK_F10
|
F10,KEY_F10,63,0x43,reg:0x09,0x44,XK_F10
|
||||||
F11,64,0x44,reg:0x78,0x57,XK_F11
|
F11,KEY_F11,64,0x44,reg:0x78,0x57,XK_F11
|
||||||
F12,65,0x45,reg:0x07,0x58,XK_F12
|
F12,KEY_F12,65,0x45,reg:0x07,0x58,XK_F12
|
||||||
PrintScreen,66,0x46,print:0xff,0x54,XK_Sys_Req
|
PrintScreen,KEY_SYSRQ,66,0x46,print:0xff,0x54,XK_Sys_Req
|
||||||
Insert,67,0x49,spec:0x70,0xe052,XK_Insert
|
Insert,KEY_INSERT,67,0x49,spec:0x70,0xe052,XK_Insert
|
||||||
Home,68,0x4a,spec:0x6c,0xe047,XK_Home
|
Home,KEY_HOME,68,0x4a,spec:0x6c,0xe047,XK_Home
|
||||||
PageUp,69,0x4b,spec:0x7d,0xe049,XK_Page_Up
|
PageUp,KEY_PAGEUP,69,0x4b,spec:0x7d,0xe049,XK_Page_Up
|
||||||
Delete,70,0x4c,spec:0x71,0xe053,XK_Delete
|
Delete,KEY_DELETE,70,0x4c,spec:0x71,0xe053,XK_Delete
|
||||||
End,71,0x4d,spec:0x69,0xe04f,XK_End
|
End,KEY_END,71,0x4d,spec:0x69,0xe04f,XK_End
|
||||||
PageDown,72,0x4e,spec:0x7a,0xe051,XK_Page_Down
|
PageDown,KEY_PAGEDOWN,72,0x4e,spec:0x7a,0xe051,XK_Page_Down
|
||||||
ArrowRight,73,0x4f,spec:0x74,0xe04d,XK_Right
|
ArrowRight,KEY_RIGHT,73,0x4f,spec:0x74,0xe04d,XK_Right
|
||||||
ArrowLeft,74,0x50,spec:0x6b,0xe04b,XK_Left
|
ArrowLeft,KEY_LEFT,74,0x50,spec:0x6b,0xe04b,XK_Left
|
||||||
ArrowDown,75,0x51,spec:0x72,0xe050,XK_Down
|
ArrowDown,KEY_DOWN,75,0x51,spec:0x72,0xe050,XK_Down
|
||||||
ArrowUp,76,0x52,spec:0x75,0xe048,XK_Up
|
ArrowUp,KEY_UP,76,0x52,spec:0x75,0xe048,XK_Up
|
||||||
ControlLeft,77,^0x01,reg:0x14,0x1d,XK_Control_L
|
ControlLeft,KEY_LEFTCTRL,77,^0x01,reg:0x14,0x1d,XK_Control_L
|
||||||
ShiftLeft,78,^0x02,reg:0x12,0x2a,XK_Shift_L
|
ShiftLeft,KEY_LEFTSHIFT,78,^0x02,reg:0x12,0x2a,XK_Shift_L
|
||||||
AltLeft,79,^0x04,reg:0x11,0x38,XK_Alt_L
|
AltLeft,KEY_LEFTALT,79,^0x04,reg:0x11,0x38,XK_Alt_L
|
||||||
MetaLeft,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L"
|
MetaLeft,KEY_LEFTMETA,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L"
|
||||||
ControlRight,81,^0x10,spec:0x14,0xe01d,XK_Control_R
|
ControlRight,KEY_RIGHTCTRL,81,^0x10,spec:0x14,0xe01d,XK_Control_R
|
||||||
ShiftRight,82,^0x20,reg:0x59,0x36,XK_Shift_R
|
ShiftRight,KEY_RIGHTSHIFT,82,^0x20,reg:0x59,0x36,XK_Shift_R
|
||||||
AltRight,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift"
|
AltRight,KEY_RIGHTALT,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift"
|
||||||
MetaRight,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R"
|
MetaRight,KEY_RIGHTMETA,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R"
|
||||||
Pause,85,0x48,pause:0xff,0xe046,XK_Pause
|
Pause,KEY_PAUSE,85,0x48,pause:0xff,0xe046,XK_Pause
|
||||||
ScrollLock,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock
|
ScrollLock,KEY_SCROLLLOCK,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock
|
||||||
NumLock,87,0x53,reg:0x77,0x45,XK_Num_Lock
|
NumLock,KEY_NUMLOCK,87,0x53,reg:0x77,0x45,XK_Num_Lock
|
||||||
ContextMenu,88,0x65,spec:0x2f,0xe05d,XK_Menu
|
ContextMenu,KEY_CONTEXT_MENU,88,0x65,spec:0x2f,0xe05d,XK_Menu
|
||||||
NumpadDivide,89,0x54,spec:0x4a,0xe035,XK_KP_Divide
|
NumpadDivide,KEY_KPSLASH,89,0x54,spec:0x4a,0xe035,XK_KP_Divide
|
||||||
NumpadMultiply,90,0x55,reg:0x7c,0x37,XK_multiply
|
NumpadMultiply,KEY_KPASTERISK,90,0x55,reg:0x7c,0x37,XK_multiply
|
||||||
NumpadSubtract,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract
|
NumpadSubtract,KEY_KPMINUS,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract
|
||||||
NumpadAdd,92,0x57,reg:0x79,0x4e,XK_KP_Add
|
NumpadAdd,KEY_KPPLUS,92,0x57,reg:0x79,0x4e,XK_KP_Add
|
||||||
NumpadEnter,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter
|
NumpadEnter,KEY_KPENTER,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter
|
||||||
Numpad1,94,0x59,reg:0x69,0x4f,XK_KP_1
|
Numpad1,KEY_KP1,94,0x59,reg:0x69,0x4f,XK_KP_1
|
||||||
Numpad2,95,0x5a,reg:0x72,0x50,XK_KP_2
|
Numpad2,KEY_KP2,95,0x5a,reg:0x72,0x50,XK_KP_2
|
||||||
Numpad3,96,0x5b,reg:0x7a,0x51,XK_KP_3
|
Numpad3,KEY_KP3,96,0x5b,reg:0x7a,0x51,XK_KP_3
|
||||||
Numpad4,97,0x5c,reg:0x6b,0x4b,XK_KP_4
|
Numpad4,KEY_KP4,97,0x5c,reg:0x6b,0x4b,XK_KP_4
|
||||||
Numpad5,98,0x5d,reg:0x73,0x4c,XK_KP_5
|
Numpad5,KEY_KP5,98,0x5d,reg:0x73,0x4c,XK_KP_5
|
||||||
Numpad6,99,0x5e,reg:0x74,0x4d,XK_KP_6
|
Numpad6,KEY_KP6,99,0x5e,reg:0x74,0x4d,XK_KP_6
|
||||||
Numpad7,100,0x5f,reg:0x6c,0x47,XK_KP_7
|
Numpad7,KEY_KP7,100,0x5f,reg:0x6c,0x47,XK_KP_7
|
||||||
Numpad8,101,0x60,reg:0x75,0x48,XK_KP_8
|
Numpad8,KEY_KP8,101,0x60,reg:0x75,0x48,XK_KP_8
|
||||||
Numpad9,102,0x61,reg:0x7d,0x49,XK_KP_9
|
Numpad9,KEY_KP9,102,0x61,reg:0x7d,0x49,XK_KP_9
|
||||||
Numpad0,103,0x62,reg:0x70,0x52,XK_KP_0
|
Numpad0,KEY_KP0,103,0x62,reg:0x70,0x52,XK_KP_0
|
||||||
NumpadDecimal,104,0x63,reg:0x71,0x53,XK_KP_Decimal
|
NumpadDecimal,KEY_KPDOT,104,0x63,reg:0x71,0x53,XK_KP_Decimal
|
||||||
Power,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep
|
Power,KEY_POWER,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep
|
||||||
IntlBackslash,106,0x64,reg:0x61,0x56,""
|
IntlBackslash,KEY_102ND,106,0x64,reg:0x61,0x56,
|
||||||
IntlYen,107,0x89,reg:0x6a,0x7d,""
|
IntlYen,KEY_YEN,107,0x89,reg:0x6a,0x7d,
|
||||||
IntlRo,108,0x87,reg:0x51,0x73,""
|
IntlRo,KEY_RO,108,0x87,reg:0x51,0x73,
|
||||||
KanaMode,109,0x88,reg:0x13,0x70,""
|
KanaMode,KEY_KATAKANA,109,0x88,reg:0x13,0x70,
|
||||||
Convert,110,0x8a,reg:0x64,0x79,""
|
Convert,KEY_HENKAN,110,0x8a,reg:0x64,0x79,
|
||||||
NonConvert,111,0x8b,reg:0x67,0x7b,""
|
NonConvert,KEY_MUHENKAN,111,0x8b,reg:0x67,0x7b,
|
||||||
|
AudioVolumeMute,KEY_MUTE,112,0x7f,spec:0x23,0xe020,
|
||||||
|
AudioVolumeUp,KEY_VOLUMEUP,113,0x80,spec:0x32,0xe030,
|
||||||
|
AudioVolumeDown,KEY_VOLUMEDOWN,114,0x81,spec:0x21,0xe02e,
|
||||||
|
F20,KEY_F20,115,0x6f,,0x5a,
|
||||||
|
|||||||
|
29
kvmd.install
29
kvmd.install
@ -27,7 +27,8 @@ post_upgrade() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
chown kvmd /var/lib/kvmd/msd 2>/dev/null || true
|
chown kvmd /var/lib/kvmd/msd 2>/dev/null || true
|
||||||
chown kvmd-pst /var/lib/kvmd/pst 2>/dev/null || true
|
chown kvmd-pst:kvmd-pst /var/lib/kvmd/pst 2>/dev/null || true
|
||||||
|
chmod 1775 /var/lib/kvmd/pst 2>/dev/null || true
|
||||||
|
|
||||||
if [ ! -e /etc/kvmd/nginx/ssl/server.crt ]; then
|
if [ ! -e /etc/kvmd/nginx/ssl/server.crt ]; then
|
||||||
echo "==> Generating KVMD-Nginx certificate ..."
|
echo "==> Generating KVMD-Nginx certificate ..."
|
||||||
@ -92,6 +93,32 @@ disable_overscan=1
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$(vercmp "$2" 4.4)" -lt 0 ]]; then
|
||||||
|
systemctl disable kvmd-pass || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(vercmp "$2" 4.5)" -lt 0 ]]; then
|
||||||
|
sed -i 's/X-kvmd\.pst-user=kvmd-pst/X-kvmd.pst-user=kvmd-pst,X-kvmd.pst-group=kvmd-pst/g' /etc/fstab
|
||||||
|
touch -t 200701011000 /etc/fstab
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(vercmp "$2" 4.31)" -lt 0 ]]; then
|
||||||
|
if [[ "$(systemctl is-enabled kvmd-janus || true)" = enabled || "$(systemctl is-enabled kvmd-janus-static || true)" = enabled ]]; then
|
||||||
|
systemctl enable kvmd-media || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(vercmp "$2" 4.47)" -lt 0 ]]; then
|
||||||
|
cp /usr/share/kvmd/configs.default/janus/janus.plugin.ustreamer.jcfg /etc/kvmd/janus || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(vercmp "$2" 4.60)" -lt 0 ]]; then
|
||||||
|
if grep -q "^dtoverlay=vc4-kms-v3d" /boot/config.txt; then
|
||||||
|
sed -i -e "s/cma=128M/cma=192M/g" /boot/cmdline.txt || true
|
||||||
|
sed -i -e "s/^gpu_mem=128/gpu_mem=192/g" /boot/config.txt || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Some update deletes /etc/motd, WTF
|
# Some update deletes /etc/motd, WTF
|
||||||
# shellcheck disable=SC2015,SC2166
|
# shellcheck disable=SC2015,SC2166
|
||||||
[ ! -f /etc/motd -a -f /etc/motd.pacsave ] && mv /etc/motd.pacsave /etc/motd || true
|
[ ! -f /etc/motd -a -f /etc/motd.pacsave ] && mv /etc/motd.pacsave /etc/motd || true
|
||||||
|
|||||||
@ -20,4 +20,4 @@
|
|||||||
# ========================================================================== #
|
# ========================================================================== #
|
||||||
|
|
||||||
|
|
||||||
__version__ = "4.3"
|
__version__ = "4.94"
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import typing
|
||||||
|
|
||||||
import gpiod
|
import gpiod
|
||||||
|
|
||||||
@ -83,9 +84,9 @@ class AioReader: # pylint: disable=too-many-instance-attributes
|
|||||||
self.__path,
|
self.__path,
|
||||||
consumer=self.__consumer,
|
consumer=self.__consumer,
|
||||||
config={tuple(pins): gpiod.LineSettings(edge_detection=gpiod.line.Edge.BOTH)},
|
config={tuple(pins): gpiod.LineSettings(edge_detection=gpiod.line.Edge.BOTH)},
|
||||||
) as line_request:
|
) as line_req:
|
||||||
|
|
||||||
line_request.wait_edge_events(0.1)
|
line_req.wait_edge_events(0.1)
|
||||||
self.__values = {
|
self.__values = {
|
||||||
pin: _DebouncedValue(
|
pin: _DebouncedValue(
|
||||||
initial=bool(value.value),
|
initial=bool(value.value),
|
||||||
@ -93,32 +94,33 @@ class AioReader: # pylint: disable=too-many-instance-attributes
|
|||||||
notifier=self.__notifier,
|
notifier=self.__notifier,
|
||||||
loop=self.__loop,
|
loop=self.__loop,
|
||||||
)
|
)
|
||||||
for (pin, value) in zip(pins, line_request.get_values(pins))
|
for (pin, value) in zip(pins, line_req.get_values(pins))
|
||||||
}
|
}
|
||||||
self.__loop.call_soon_threadsafe(self.__notifier.notify)
|
self.__loop.call_soon_threadsafe(self.__notifier.notify)
|
||||||
|
|
||||||
while not self.__stop_event.is_set():
|
while not self.__stop_event.is_set():
|
||||||
if line_request.wait_edge_events(1):
|
if line_req.wait_edge_events(1):
|
||||||
new: dict[int, bool] = {}
|
new: dict[int, bool] = {}
|
||||||
for event in line_request.read_edge_events():
|
for event in line_req.read_edge_events():
|
||||||
(pin, value) = self.__parse_event(event)
|
(pin, state) = self.__parse_event(event)
|
||||||
new[pin] = value
|
new[pin] = state
|
||||||
for (pin, value) in new.items():
|
for (pin, state) in new.items():
|
||||||
self.__values[pin].set(value)
|
self.__values[pin].set(state)
|
||||||
else: # Timeout
|
else: # Timeout
|
||||||
# XXX: Лимит был актуален для 1.6. Надо проверить, поменялось ли это в 2.x.
|
# XXX: Лимит был актуален для 1.6. Надо проверить, поменялось ли это в 2.x.
|
||||||
# Размер буфера ядра - 16 эвентов на линии. При превышении этого числа,
|
# Размер буфера ядра - 16 эвентов на линии. При превышении этого числа,
|
||||||
# новые эвенты потеряются. Это не баг, это фича, как мне объяснили в LKML.
|
# новые эвенты потеряются. Это не баг, это фича, как мне объяснили в LKML.
|
||||||
# Штош. Будем с этим жить и синхронизировать состояния при таймауте.
|
# Штош. Будем с этим жить и синхронизировать состояния при таймауте.
|
||||||
for (pin, value) in zip(pins, line_request.get_values(pins)):
|
for (pin, value) in zip(pins, line_req.get_values(pins)):
|
||||||
self.__values[pin].set(bool(value.value)) # type: ignore
|
self.__values[pin].set(bool(value.value)) # type: ignore
|
||||||
|
|
||||||
def __parse_event(self, event: gpiod.EdgeEvent) -> tuple[int, bool]:
|
def __parse_event(self, event: gpiod.EdgeEvent) -> tuple[int, bool]:
|
||||||
if event.event_type == event.Type.RISING_EDGE:
|
match event.event_type:
|
||||||
return (event.line_offset, True)
|
case event.Type.RISING_EDGE:
|
||||||
elif event.event_type == event.Type.FALLING_EDGE:
|
return (event.line_offset, True)
|
||||||
return (event.line_offset, False)
|
case event.Type.FALLING_EDGE:
|
||||||
raise RuntimeError(f"Invalid event {event} type: {event.type}")
|
return (event.line_offset, False)
|
||||||
|
typing.assert_never(event.event_type)
|
||||||
|
|
||||||
|
|
||||||
class _DebouncedValue:
|
class _DebouncedValue:
|
||||||
|
|||||||
@ -22,8 +22,6 @@
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from .languages import Languages
|
|
||||||
|
|
||||||
from .logging import get_logger
|
from .logging import get_logger
|
||||||
|
|
||||||
from . import tools
|
from . import tools
|
||||||
@ -38,13 +36,13 @@ async def remount(name: str, base_cmd: list[str], rw: bool) -> bool:
|
|||||||
part.format(mode=mode)
|
part.format(mode=mode)
|
||||||
for part in base_cmd
|
for part in base_cmd
|
||||||
]
|
]
|
||||||
logger.info(Languages().gettext("Remounting %s storage to %s: %s ..."), name, mode.upper(), tools.cmdfmt(cmd))
|
logger.info("Remounting %s storage to %s: %s ...", name, mode.upper(), tools.cmdfmt(cmd))
|
||||||
try:
|
try:
|
||||||
proc = await aioproc.log_process(cmd, logger)
|
proc = await aioproc.log_process(cmd, logger)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
assert proc.returncode is not None
|
assert proc.returncode is not None
|
||||||
raise subprocess.CalledProcessError(proc.returncode, cmd)
|
raise subprocess.CalledProcessError(proc.returncode, cmd)
|
||||||
except Exception as err:
|
except Exception as ex:
|
||||||
logger.error(Languages().gettext("Can't remount %s storage: %s"), name, tools.efmt(err))
|
logger.error("Can't remount %s storage: %s", name, tools.efmt(ex))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -59,14 +59,25 @@ def queue_get_last_sync( # pylint: disable=invalid-name
|
|||||||
# =====
|
# =====
|
||||||
class AioProcessNotifier:
|
class AioProcessNotifier:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__queue: "multiprocessing.Queue[None]" = multiprocessing.Queue()
|
self.__queue: "multiprocessing.Queue[int]" = multiprocessing.Queue()
|
||||||
|
|
||||||
def notify(self) -> None:
|
def notify(self, mask: int=0) -> None:
|
||||||
self.__queue.put_nowait(None)
|
self.__queue.put_nowait(mask)
|
||||||
|
|
||||||
async def wait(self) -> None:
|
async def wait(self) -> int:
|
||||||
while not (await queue_get_last(self.__queue, 0.1))[0]:
|
while True:
|
||||||
pass
|
mask = await aiotools.run_async(self.__get)
|
||||||
|
if mask >= 0:
|
||||||
|
return mask
|
||||||
|
|
||||||
|
def __get(self) -> int:
|
||||||
|
try:
|
||||||
|
mask = self.__queue.get(timeout=0.1)
|
||||||
|
while not self.__queue.empty():
|
||||||
|
mask |= self.__queue.get()
|
||||||
|
return mask
|
||||||
|
except queue.Empty:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import asyncio
|
|||||||
import asyncio.subprocess
|
import asyncio.subprocess
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .languages import Languages
|
|
||||||
import setproctitle
|
import setproctitle
|
||||||
|
|
||||||
from .logging import get_logger
|
from .logging import get_logger
|
||||||
@ -86,7 +85,7 @@ async def log_stdout_infinite(proc: asyncio.subprocess.Process, logger: logging.
|
|||||||
else:
|
else:
|
||||||
empty += 1
|
empty += 1
|
||||||
if empty == 100: # asyncio bug
|
if empty == 100: # asyncio bug
|
||||||
raise RuntimeError(Languages().gettext("Asyncio process: too many empty lines"))
|
raise RuntimeError("Asyncio process: too many empty lines")
|
||||||
|
|
||||||
|
|
||||||
async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: logging.Logger) -> None: # pylint: disable=no-member
|
async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: logging.Logger) -> None: # pylint: disable=no-member
|
||||||
@ -101,14 +100,14 @@ async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: lo
|
|||||||
if proc.returncode is not None:
|
if proc.returncode is not None:
|
||||||
raise
|
raise
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
logger.info(Languages().gettext("Process killed: retcode=%d"), proc.returncode)
|
logger.info("Process killed: retcode=%d", proc.returncode)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
if proc.returncode is None:
|
if proc.returncode is None:
|
||||||
logger.exception(Languages().gettext("Can't kill process pid=%d"), proc.pid)
|
logger.exception("Can't kill process pid=%d", proc.pid)
|
||||||
else:
|
else:
|
||||||
logger.info(Languages().gettext("Process killed: retcode=%d"), proc.returncode)
|
logger.info("Process killed: retcode=%d", proc.returncode)
|
||||||
|
|
||||||
|
|
||||||
def rename_process(suffix: str, prefix: str="kvmd") -> None:
|
def rename_process(suffix: str, prefix: str="kvmd") -> None:
|
||||||
@ -117,7 +116,7 @@ def rename_process(suffix: str, prefix: str="kvmd") -> None:
|
|||||||
|
|
||||||
def settle(name: str, suffix: str, prefix: str="kvmd") -> logging.Logger:
|
def settle(name: str, suffix: str, prefix: str="kvmd") -> logging.Logger:
|
||||||
logger = get_logger(1)
|
logger = get_logger(1)
|
||||||
logger.info(Languages().gettext("Started %s pid=%d"), name, os.getpid())
|
logger.info("Started %s pid=%d", name, os.getpid())
|
||||||
os.setpgrp()
|
os.setpgrp()
|
||||||
rename_process(suffix, prefix)
|
rename_process(suffix, prefix)
|
||||||
return logger
|
return logger
|
||||||
|
|||||||
@ -45,6 +45,11 @@ async def read_file(path: str) -> str:
|
|||||||
return (await file.read())
|
return (await file.read())
|
||||||
|
|
||||||
|
|
||||||
|
async def write_file(path: str, text: str) -> None:
|
||||||
|
async with aiofiles.open(path, "w") as file:
|
||||||
|
await file.write(text)
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def run(coro: Coroutine, final: (Coroutine | None)=None) -> None:
|
def run(coro: Coroutine, final: (Coroutine | None)=None) -> None:
|
||||||
# https://github.com/aio-libs/aiohttp/blob/a1d4dac1d/aiohttp/web.py#L515
|
# https://github.com/aio-libs/aiohttp/blob/a1d4dac1d/aiohttp/web.py#L515
|
||||||
@ -112,9 +117,9 @@ def shield_fg(aw: Awaitable): # type: ignore
|
|||||||
if inner.cancelled():
|
if inner.cancelled():
|
||||||
outer.forced_cancel()
|
outer.forced_cancel()
|
||||||
else:
|
else:
|
||||||
err = inner.exception()
|
ex = inner.exception()
|
||||||
if err is not None:
|
if ex is not None:
|
||||||
outer.set_exception(err)
|
outer.set_exception(ex)
|
||||||
else:
|
else:
|
||||||
outer.set_result(inner.result())
|
outer.set_result(inner.result())
|
||||||
|
|
||||||
@ -166,7 +171,7 @@ def create_deadly_task(name: str, coro: Coroutine) -> asyncio.Task:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Unhandled exception in deadly task, killing myself ...")
|
logger.exception("Unhandled exception in deadly task %r, killing myself ...", name)
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
if pid == 1:
|
if pid == 1:
|
||||||
os._exit(1) # Docker workaround # pylint: disable=protected-access
|
os._exit(1) # Docker workaround # pylint: disable=protected-access
|
||||||
@ -206,6 +211,18 @@ async def wait_first(*aws: asyncio.Task) -> tuple[set[asyncio.Task], set[asyncio
|
|||||||
return (await asyncio.wait(list(aws), return_when=asyncio.FIRST_COMPLETED))
|
return (await asyncio.wait(list(aws), return_when=asyncio.FIRST_COMPLETED))
|
||||||
|
|
||||||
|
|
||||||
|
# =====
|
||||||
|
async def spawn_and_follow(*coros: Coroutine) -> None:
|
||||||
|
tasks: list[asyncio.Task] = list(map(asyncio.create_task, coros))
|
||||||
|
try:
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
except Exception:
|
||||||
|
for task in tasks:
|
||||||
|
task.cancel()
|
||||||
|
await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
async def close_writer(writer: asyncio.StreamWriter) -> bool:
|
async def close_writer(writer: asyncio.StreamWriter) -> bool:
|
||||||
closing = writer.is_closing()
|
closing = writer.is_closing()
|
||||||
@ -232,25 +249,26 @@ async def close_writer(writer: asyncio.StreamWriter) -> bool:
|
|||||||
# =====
|
# =====
|
||||||
class AioNotifier:
|
class AioNotifier:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__queue: "asyncio.Queue[None]" = asyncio.Queue()
|
self.__queue: "asyncio.Queue[int]" = asyncio.Queue()
|
||||||
|
|
||||||
def notify(self) -> None:
|
def notify(self, mask: int=0) -> None:
|
||||||
self.__queue.put_nowait(None)
|
self.__queue.put_nowait(mask)
|
||||||
|
|
||||||
async def wait(self, timeout: (float | None)=None) -> None:
|
async def wait(self, timeout: (float | None)=None) -> int:
|
||||||
|
mask = 0
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
await self.__queue.get()
|
mask = await self.__queue.get()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(
|
mask = await asyncio.wait_for(
|
||||||
asyncio.ensure_future(self.__queue.get()),
|
asyncio.ensure_future(self.__queue.get()),
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return # False
|
return -1
|
||||||
while not self.__queue.empty():
|
while not self.__queue.empty():
|
||||||
await self.__queue.get()
|
mask |= await self.__queue.get()
|
||||||
# return True
|
return mask
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
@ -296,7 +314,7 @@ class AioExclusiveRegion:
|
|||||||
def is_busy(self) -> bool:
|
def is_busy(self) -> bool:
|
||||||
return self.__busy
|
return self.__busy
|
||||||
|
|
||||||
async def enter(self) -> None:
|
def enter(self) -> None:
|
||||||
if not self.__busy:
|
if not self.__busy:
|
||||||
self.__busy = True
|
self.__busy = True
|
||||||
try:
|
try:
|
||||||
@ -308,22 +326,22 @@ class AioExclusiveRegion:
|
|||||||
return
|
return
|
||||||
raise self.__exc_type()
|
raise self.__exc_type()
|
||||||
|
|
||||||
async def exit(self) -> None:
|
def exit(self) -> None:
|
||||||
self.__busy = False
|
self.__busy = False
|
||||||
if self.__notifier:
|
if self.__notifier:
|
||||||
self.__notifier.notify()
|
self.__notifier.notify()
|
||||||
|
|
||||||
async def __aenter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
await self.enter()
|
self.enter()
|
||||||
|
|
||||||
async def __aexit__(
|
def __exit__(
|
||||||
self,
|
self,
|
||||||
_exc_type: type[BaseException],
|
_exc_type: type[BaseException],
|
||||||
_exc: BaseException,
|
_exc: BaseException,
|
||||||
_tb: types.TracebackType,
|
_tb: types.TracebackType,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
await self.exit()
|
self.exit()
|
||||||
|
|
||||||
|
|
||||||
async def run_region_task(
|
async def run_region_task(
|
||||||
@ -338,7 +356,7 @@ async def run_region_task(
|
|||||||
|
|
||||||
async def wrapper() -> None:
|
async def wrapper() -> None:
|
||||||
try:
|
try:
|
||||||
async with region:
|
with region:
|
||||||
entered.set_result(None)
|
entered.set_result(None)
|
||||||
await func(*args, **kwargs)
|
await func(*args, **kwargs)
|
||||||
except region.get_exc_type():
|
except region.get_exc_type():
|
||||||
|
|||||||
@ -31,12 +31,8 @@ import pygments
|
|||||||
import pygments.lexers.data
|
import pygments.lexers.data
|
||||||
import pygments.formatters
|
import pygments.formatters
|
||||||
|
|
||||||
from gettext import translation
|
|
||||||
|
|
||||||
from .. import tools
|
from .. import tools
|
||||||
|
|
||||||
from ..mouse import MouseRange
|
|
||||||
|
|
||||||
from ..plugins import UnknownPluginError
|
from ..plugins import UnknownPluginError
|
||||||
from ..plugins.auth import get_auth_service_class
|
from ..plugins.auth import get_auth_service_class
|
||||||
from ..plugins.hid import get_hid_class
|
from ..plugins.hid import get_hid_class
|
||||||
@ -69,6 +65,7 @@ from ..validators.basic import valid_string_list
|
|||||||
|
|
||||||
from ..validators.auth import valid_user
|
from ..validators.auth import valid_user
|
||||||
from ..validators.auth import valid_users_list
|
from ..validators.auth import valid_users_list
|
||||||
|
from ..validators.auth import valid_expire
|
||||||
|
|
||||||
from ..validators.os import valid_abs_path
|
from ..validators.os import valid_abs_path
|
||||||
from ..validators.os import valid_abs_file
|
from ..validators.os import valid_abs_file
|
||||||
@ -77,12 +74,14 @@ from ..validators.os import valid_unix_mode
|
|||||||
from ..validators.os import valid_options
|
from ..validators.os import valid_options
|
||||||
from ..validators.os import valid_command
|
from ..validators.os import valid_command
|
||||||
|
|
||||||
|
from ..validators.net import valid_ip
|
||||||
from ..validators.net import valid_ip_or_host
|
from ..validators.net import valid_ip_or_host
|
||||||
from ..validators.net import valid_net
|
from ..validators.net import valid_net
|
||||||
from ..validators.net import valid_port
|
from ..validators.net import valid_port
|
||||||
from ..validators.net import valid_ports_list
|
from ..validators.net import valid_ports_list
|
||||||
from ..validators.net import valid_mac
|
from ..validators.net import valid_mac
|
||||||
from ..validators.net import valid_ssl_ciphers
|
from ..validators.net import valid_ssl_ciphers
|
||||||
|
from ..validators.net import valid_ice_servers
|
||||||
|
|
||||||
from ..validators.hid import valid_hid_key
|
from ..validators.hid import valid_hid_key
|
||||||
from ..validators.hid import valid_hid_mouse_output
|
from ..validators.hid import valid_hid_mouse_output
|
||||||
@ -105,9 +104,6 @@ from ..validators.hw import valid_otg_gadget
|
|||||||
from ..validators.hw import valid_otg_id
|
from ..validators.hw import valid_otg_id
|
||||||
from ..validators.hw import valid_otg_ethernet
|
from ..validators.hw import valid_otg_ethernet
|
||||||
|
|
||||||
from ..validators.languages import valid_languages
|
|
||||||
|
|
||||||
from ..languages import Languages
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def init(
|
def init(
|
||||||
@ -129,7 +125,6 @@ def init(
|
|||||||
add_help=add_help,
|
add_help=add_help,
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file,
|
parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file,
|
||||||
help="Set config file path", metavar="<file>")
|
help="Set config file path", metavar="<file>")
|
||||||
parser.add_argument("-o", "--set-options", default=[], nargs="+",
|
parser.add_argument("-o", "--set-options", default=[], nargs="+",
|
||||||
@ -153,18 +148,9 @@ def init(
|
|||||||
))
|
))
|
||||||
raise SystemExit()
|
raise SystemExit()
|
||||||
config = _init_config(options.config, options.set_options, **load)
|
config = _init_config(options.config, options.set_options, **load)
|
||||||
|
|
||||||
logging.captureWarnings(True)
|
logging.captureWarnings(True)
|
||||||
logging.config.dictConfig(config.logging)
|
logging.config.dictConfig(config.logging)
|
||||||
|
|
||||||
if isinstance(config.get("languages"), dict) and isinstance(config["languages"].get("console"), str):
|
|
||||||
i18n_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+"/i18n"
|
|
||||||
Languages.init("message", i18n_path, config["languages"]["console"])
|
|
||||||
gettext = Languages().gettext
|
|
||||||
|
|
||||||
logging.addLevelName(20, gettext("INFO"))
|
|
||||||
logging.addLevelName(30, gettext("WARNING"))
|
|
||||||
logging.addLevelName(40, gettext("ERROR"))
|
|
||||||
|
|
||||||
if cli_logging:
|
if cli_logging:
|
||||||
logging.getLogger().handlers[0].setFormatter(logging.Formatter(
|
logging.getLogger().handlers[0].setFormatter(logging.Formatter(
|
||||||
"-- {levelname:>7} -- {message}",
|
"-- {levelname:>7} -- {message}",
|
||||||
@ -173,7 +159,10 @@ def init(
|
|||||||
|
|
||||||
if check_run and not options.run:
|
if check_run and not options.run:
|
||||||
raise SystemExit(
|
raise SystemExit(
|
||||||
gettext("To prevent accidental startup, you must specify the --run option to start.\n")+gettext("Try the --help option to find out what this service does.\n")+gettext("Make sure you understand exactly what you are doing!"))
|
"To prevent accidental startup, you must specify the --run option to start.\n"
|
||||||
|
"Try the --help option to find out what this service does.\n"
|
||||||
|
"Make sure you understand exactly what you are doing!"
|
||||||
|
)
|
||||||
|
|
||||||
return (parser, remaining, config)
|
return (parser, remaining, config)
|
||||||
|
|
||||||
@ -183,8 +172,8 @@ def _init_config(config_path: str, override_options: list[str], **load_flags: bo
|
|||||||
config_path = os.path.expanduser(config_path)
|
config_path = os.path.expanduser(config_path)
|
||||||
try:
|
try:
|
||||||
raw_config: dict = load_yaml_file(config_path)
|
raw_config: dict = load_yaml_file(config_path)
|
||||||
except Exception as err:
|
except Exception as ex:
|
||||||
raise SystemExit(f"ConfigError: Can't read config file {config_path!r}:\n{tools.efmt(err)}")
|
raise SystemExit(f"ConfigError: Can't read config file {config_path!r}:\n{tools.efmt(ex)}")
|
||||||
if not isinstance(raw_config, dict):
|
if not isinstance(raw_config, dict):
|
||||||
raise SystemExit(f"ConfigError: Top-level of the file {config_path!r} must be a dictionary")
|
raise SystemExit(f"ConfigError: Top-level of the file {config_path!r} must be a dictionary")
|
||||||
|
|
||||||
@ -199,11 +188,19 @@ def _init_config(config_path: str, override_options: list[str], **load_flags: bo
|
|||||||
config = make_config(raw_config, scheme)
|
config = make_config(raw_config, scheme)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
except (ConfigError, UnknownPluginError) as err:
|
except (ConfigError, UnknownPluginError) as ex:
|
||||||
raise SystemExit(f"ConfigError: {err}")
|
raise SystemExit(f"ConfigError: {ex}")
|
||||||
|
|
||||||
|
|
||||||
def _patch_raw(raw_config: dict) -> None: # pylint: disable=too-many-branches
|
def _patch_raw(raw_config: dict) -> None: # pylint: disable=too-many-branches
|
||||||
|
for (sub, cmd) in [("iface", "ip_cmd"), ("firewall", "iptables_cmd")]:
|
||||||
|
if isinstance(raw_config.get("otgnet"), dict):
|
||||||
|
if isinstance(raw_config["otgnet"].get(sub), dict):
|
||||||
|
if raw_config["otgnet"][sub].get(cmd):
|
||||||
|
raw_config["otgnet"].setdefault("commands", {})
|
||||||
|
raw_config["otgnet"]["commands"][cmd] = raw_config["otgnet"][sub][cmd]
|
||||||
|
del raw_config["otgnet"][sub][cmd]
|
||||||
|
|
||||||
if isinstance(raw_config.get("otg"), dict):
|
if isinstance(raw_config.get("otg"), dict):
|
||||||
for (old, new) in [
|
for (old, new) in [
|
||||||
("msd", "msd"),
|
("msd", "msd"),
|
||||||
@ -371,6 +368,12 @@ def _get_config_scheme() -> dict:
|
|||||||
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"enabled": Option(True, type=valid_bool),
|
"enabled": Option(True, type=valid_bool),
|
||||||
|
"expire": Option(0, type=valid_expire),
|
||||||
|
|
||||||
|
"usc": {
|
||||||
|
"users": Option([], type=valid_users_list), # PiKVM username has a same regex as a UNIX username
|
||||||
|
"groups": Option(["kvmd-selfauth"], type=valid_users_list), # groupname has a same regex as a username
|
||||||
|
},
|
||||||
|
|
||||||
"internal": {
|
"internal": {
|
||||||
"type": Option("htpasswd"),
|
"type": Option("htpasswd"),
|
||||||
@ -419,19 +422,7 @@ def _get_config_scheme() -> dict:
|
|||||||
|
|
||||||
"hid": {
|
"hid": {
|
||||||
"type": Option("", type=valid_stripped_string_not_empty),
|
"type": Option("", type=valid_stripped_string_not_empty),
|
||||||
|
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
|
||||||
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
|
|
||||||
"ignore_keys": Option([], type=functools.partial(valid_string_list, subval=valid_hid_key)),
|
|
||||||
|
|
||||||
"mouse_x_range": {
|
|
||||||
"min": Option(MouseRange.MIN, type=valid_hid_mouse_move),
|
|
||||||
"max": Option(MouseRange.MAX, type=valid_hid_mouse_move),
|
|
||||||
},
|
|
||||||
"mouse_y_range": {
|
|
||||||
"min": Option(MouseRange.MIN, type=valid_hid_mouse_move),
|
|
||||||
"max": Option(MouseRange.MAX, type=valid_hid_mouse_move),
|
|
||||||
},
|
|
||||||
|
|
||||||
# Dynamic content
|
# Dynamic content
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -483,7 +474,7 @@ def _get_config_scheme() -> dict:
|
|||||||
|
|
||||||
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
|
"unix": Option("/run/kvmd/ustreamer.sock", type=valid_abs_path, unpack_as="unix_path"),
|
||||||
"timeout": Option(2.0, type=valid_float_f01),
|
"timeout": Option(2.0, type=valid_float_f01),
|
||||||
"snapshot_timeout": Option(1.0, type=valid_float_f01), # error_delay * 3 + 1
|
"snapshot_timeout": Option(5.0, type=valid_float_f01), # error_delay * 3 + 1
|
||||||
|
|
||||||
"process_name_prefix": Option("kvmd/streamer"),
|
"process_name_prefix": Option("kvmd/streamer"),
|
||||||
|
|
||||||
@ -528,6 +519,38 @@ def _get_config_scheme() -> dict:
|
|||||||
"table": Option([], type=valid_ugpio_view_table),
|
"table": Option([], type=valid_ugpio_view_table),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"switch": {
|
||||||
|
"device": Option("/dev/kvmd-switch", type=valid_abs_path, unpack_as="device_path"),
|
||||||
|
"default_edid": Option("/etc/kvmd/switch-edid.hex", type=valid_abs_path, unpack_as="default_edid_path"),
|
||||||
|
"ignore_hpd_on_top": Option(False, type=valid_bool),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"media": {
|
||||||
|
"server": {
|
||||||
|
"unix": Option("/run/kvmd/media.sock", type=valid_abs_path, unpack_as="unix_path"),
|
||||||
|
"unix_rm": Option(True, type=valid_bool),
|
||||||
|
"unix_mode": Option(0o660, type=valid_unix_mode),
|
||||||
|
"heartbeat": Option(15.0, type=valid_float_f01),
|
||||||
|
"access_log_format": Option("[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---"
|
||||||
|
" referer='%{Referer}i'; user_agent='%{User-Agent}i'"),
|
||||||
|
},
|
||||||
|
|
||||||
|
"memsink": {
|
||||||
|
"jpeg": {
|
||||||
|
"sink": Option("", unpack_as="obj"),
|
||||||
|
"lock_timeout": Option(1.0, type=valid_float_f01),
|
||||||
|
"wait_timeout": Option(1.0, type=valid_float_f01),
|
||||||
|
"drop_same_frames": Option(0.0, type=valid_float_f0),
|
||||||
|
},
|
||||||
|
"h264": {
|
||||||
|
"sink": Option("", unpack_as="obj"),
|
||||||
|
"lock_timeout": Option(1.0, type=valid_float_f01),
|
||||||
|
"wait_timeout": Option(1.0, type=valid_float_f01),
|
||||||
|
"drop_same_frames": Option(0.0, type=valid_float_f0),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"pst": {
|
"pst": {
|
||||||
@ -553,16 +576,17 @@ def _get_config_scheme() -> dict:
|
|||||||
"vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation
|
"vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation
|
||||||
"product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget
|
"product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget
|
||||||
"manufacturer": Option("PiKVM", type=valid_stripped_string),
|
"manufacturer": Option("PiKVM", type=valid_stripped_string),
|
||||||
"product": Option("Composite KVM Device", type=valid_stripped_string),
|
"product": Option("PiKVM Composite Device", type=valid_stripped_string),
|
||||||
"serial": Option("CAFEBABE", type=valid_stripped_string, if_none=None),
|
"serial": Option("CAFEBABE", type=valid_stripped_string, if_none=None),
|
||||||
|
"config": Option("", type=valid_stripped_string),
|
||||||
"device_version": Option(-1, type=functools.partial(valid_number, min=-1, max=0xFFFF)),
|
"device_version": Option(-1, type=functools.partial(valid_number, min=-1, max=0xFFFF)),
|
||||||
"usb_version": Option(0x0200, type=valid_otg_id),
|
"usb_version": Option(0x0200, type=valid_otg_id),
|
||||||
"max_power": Option(250, type=functools.partial(valid_number, min=50, max=500)),
|
"max_power": Option(250, type=functools.partial(valid_number, min=50, max=500)),
|
||||||
"remote_wakeup": Option(False, type=valid_bool),
|
"remote_wakeup": Option(True, type=valid_bool),
|
||||||
|
|
||||||
"gadget": Option("kvmd", type=valid_otg_gadget),
|
"gadget": Option("kvmd", type=valid_otg_gadget),
|
||||||
"config": Option("PiKVM device", type=valid_stripped_string_not_empty),
|
|
||||||
"udc": Option("", type=valid_stripped_string),
|
"udc": Option("", type=valid_stripped_string),
|
||||||
|
"endpoints": Option(9, type=valid_int_f0),
|
||||||
"init_delay": Option(3.0, type=valid_float_f01),
|
"init_delay": Option(3.0, type=valid_float_f01),
|
||||||
|
|
||||||
"user": Option("kvmd", type=valid_user),
|
"user": Option("kvmd", type=valid_user),
|
||||||
@ -576,6 +600,9 @@ def _get_config_scheme() -> dict:
|
|||||||
"mouse": {
|
"mouse": {
|
||||||
"start": Option(True, type=valid_bool),
|
"start": Option(True, type=valid_bool),
|
||||||
},
|
},
|
||||||
|
"mouse_alt": {
|
||||||
|
"start": Option(True, type=valid_bool),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"msd": {
|
"msd": {
|
||||||
@ -586,6 +613,18 @@ def _get_config_scheme() -> dict:
|
|||||||
"rw": Option(False, type=valid_bool),
|
"rw": Option(False, type=valid_bool),
|
||||||
"removable": Option(True, type=valid_bool),
|
"removable": Option(True, type=valid_bool),
|
||||||
"fua": Option(True, type=valid_bool),
|
"fua": Option(True, type=valid_bool),
|
||||||
|
"inquiry_string": {
|
||||||
|
"cdrom": {
|
||||||
|
"vendor": Option("PiKVM", type=valid_stripped_string),
|
||||||
|
"product": Option("Optical Drive", type=valid_stripped_string),
|
||||||
|
"revision": Option("1.00", type=valid_stripped_string),
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
"vendor": Option("PiKVM", type=valid_stripped_string),
|
||||||
|
"product": Option("Flash Drive", type=valid_stripped_string),
|
||||||
|
"revision": Option("1.00", type=valid_stripped_string),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -602,6 +641,11 @@ def _get_config_scheme() -> dict:
|
|||||||
"kvm_mac": Option("", type=valid_mac, if_empty=""),
|
"kvm_mac": Option("", type=valid_mac, if_empty=""),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"audio": {
|
||||||
|
"enabled": Option(False, type=valid_bool),
|
||||||
|
"start": Option(True, type=valid_bool),
|
||||||
|
},
|
||||||
|
|
||||||
"drives": {
|
"drives": {
|
||||||
"enabled": Option(False, type=valid_bool),
|
"enabled": Option(False, type=valid_bool),
|
||||||
"start": Option(True, type=valid_bool),
|
"start": Option(True, type=valid_bool),
|
||||||
@ -612,6 +656,18 @@ def _get_config_scheme() -> dict:
|
|||||||
"rw": Option(True, type=valid_bool),
|
"rw": Option(True, type=valid_bool),
|
||||||
"removable": Option(True, type=valid_bool),
|
"removable": Option(True, type=valid_bool),
|
||||||
"fua": Option(True, type=valid_bool),
|
"fua": Option(True, type=valid_bool),
|
||||||
|
"inquiry_string": {
|
||||||
|
"cdrom": {
|
||||||
|
"vendor": Option("PiKVM", type=valid_stripped_string),
|
||||||
|
"product": Option("Optical Drive", type=valid_stripped_string),
|
||||||
|
"revision": Option("1.00", type=valid_stripped_string),
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
"vendor": Option("PiKVM", type=valid_stripped_string),
|
||||||
|
"product": Option("Flash Drive", type=valid_stripped_string),
|
||||||
|
"revision": Option("1.00", type=valid_stripped_string),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -619,8 +675,7 @@ def _get_config_scheme() -> dict:
|
|||||||
|
|
||||||
"otgnet": {
|
"otgnet": {
|
||||||
"iface": {
|
"iface": {
|
||||||
"net": Option("172.30.30.0/24", type=functools.partial(valid_net, v6=False)),
|
"net": Option("172.30.30.0/24", type=functools.partial(valid_net, v6=False)),
|
||||||
"ip_cmd": Option(["/usr/bin/ip"], type=valid_command),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"firewall": {
|
"firewall": {
|
||||||
@ -628,10 +683,13 @@ def _get_config_scheme() -> dict:
|
|||||||
"allow_tcp": Option([], type=valid_ports_list),
|
"allow_tcp": Option([], type=valid_ports_list),
|
||||||
"allow_udp": Option([67], type=valid_ports_list),
|
"allow_udp": Option([67], type=valid_ports_list),
|
||||||
"forward_iface": Option("", type=valid_stripped_string),
|
"forward_iface": Option("", type=valid_stripped_string),
|
||||||
"iptables_cmd": Option(["/usr/sbin/iptables", "--wait=5"], type=valid_command),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"commands": {
|
"commands": {
|
||||||
|
"ip_cmd": Option(["/usr/bin/ip"], type=valid_command),
|
||||||
|
"iptables_cmd": Option(["/usr/sbin/iptables", "--wait=5"], type=valid_command),
|
||||||
|
"sysctl_cmd": Option(["/usr/sbin/sysctl"], type=valid_command),
|
||||||
|
|
||||||
"pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command),
|
"pre_start_cmd": Option(["/bin/true", "pre-start"], type=valid_command),
|
||||||
"pre_start_cmd_remove": Option([], type=valid_options),
|
"pre_start_cmd_remove": Option([], type=valid_options),
|
||||||
"pre_start_cmd_append": Option([], type=valid_options),
|
"pre_start_cmd_append": Option([], type=valid_options),
|
||||||
@ -693,9 +751,10 @@ def _get_config_scheme() -> dict:
|
|||||||
},
|
},
|
||||||
|
|
||||||
"vnc": {
|
"vnc": {
|
||||||
"desired_fps": Option(30, type=valid_stream_fps),
|
"desired_fps": Option(30, type=valid_stream_fps),
|
||||||
"mouse_output": Option("usb", type=valid_hid_mouse_output),
|
"mouse_output": Option("usb", type=valid_hid_mouse_output),
|
||||||
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
|
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_file),
|
||||||
|
"scroll_rate": Option(4, type=functools.partial(valid_number, min=1, max=30)),
|
||||||
|
|
||||||
"server": {
|
"server": {
|
||||||
"host": Option("", type=valid_ip_or_host, if_empty=""),
|
"host": Option("", type=valid_ip_or_host, if_empty=""),
|
||||||
@ -747,8 +806,8 @@ def _get_config_scheme() -> dict:
|
|||||||
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"vncauth": {
|
"vncauth": {
|
||||||
"enabled": Option(False, type=valid_bool),
|
"enabled": Option(False, type=valid_bool, unpack_as="vncpass_enabled"),
|
||||||
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="path"),
|
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="vncpass_path"),
|
||||||
},
|
},
|
||||||
"vencrypt": {
|
"vencrypt": {
|
||||||
"enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"),
|
"enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"),
|
||||||
@ -756,13 +815,24 @@ def _get_config_scheme() -> dict:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"localhid": {
|
||||||
|
"kvmd": {
|
||||||
|
"unix": Option("/run/kvmd/kvmd.sock", type=valid_abs_path, unpack_as="unix_path"),
|
||||||
|
"timeout": Option(5.0, type=valid_float_f01),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"http": {
|
"http": {
|
||||||
"port": Option(80, type=valid_port),
|
"ipv4": Option("0.0.0.0", type=functools.partial(valid_ip, v6=False)),
|
||||||
|
"ipv6": Option("::", type=functools.partial(valid_ip, v4=False)),
|
||||||
|
"port": Option(80, type=valid_port),
|
||||||
},
|
},
|
||||||
"https": {
|
"https": {
|
||||||
"enabled": Option(True, type=valid_bool),
|
"enabled": Option(True, type=valid_bool),
|
||||||
"port": Option(443, type=valid_port),
|
"ipv4": Option("0.0.0.0", type=functools.partial(valid_ip, v6=False)),
|
||||||
|
"ipv6": Option("::", type=functools.partial(valid_ip, v4=False)),
|
||||||
|
"port": Option(443, type=valid_port),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -791,6 +861,7 @@ def _get_config_scheme() -> dict:
|
|||||||
], type=valid_command),
|
], type=valid_command),
|
||||||
"cmd_remove": Option([], type=valid_options),
|
"cmd_remove": Option([], type=valid_options),
|
||||||
"cmd_append": Option([], type=valid_options),
|
"cmd_append": Option([], type=valid_options),
|
||||||
|
"local_ice_servers": Option([], type=valid_ice_servers, unpack_as="ice_servers"),
|
||||||
},
|
},
|
||||||
|
|
||||||
"watchdog": {
|
"watchdog": {
|
||||||
@ -798,9 +869,4 @@ def _get_config_scheme() -> dict:
|
|||||||
"timeout": Option(300, type=valid_int_f1),
|
"timeout": Option(300, type=valid_int_f1),
|
||||||
"interval": Option(30, type=valid_int_f1),
|
"interval": Option(30, type=valid_int_f1),
|
||||||
},
|
},
|
||||||
|
|
||||||
"languages": {
|
|
||||||
"console": Option("default", type=valid_languages),
|
|
||||||
"web": Option("default", type=valid_languages),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,259 +22,22 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import dataclasses
|
|
||||||
import contextlib
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import IO
|
|
||||||
from typing import Generator
|
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from ...validators.basic import valid_bool
|
from ...validators.basic import valid_bool
|
||||||
from ...validators.basic import valid_int_f0
|
from ...validators.basic import valid_int_f0
|
||||||
|
|
||||||
|
from ...edid import EdidNoBlockError
|
||||||
|
from ...edid import Edid
|
||||||
|
|
||||||
# from .. import init
|
# from .. import init
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
class NoBlockError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _smart_open(path: str, mode: str) -> Generator[IO, None, None]:
|
|
||||||
fd = (0 if "r" in mode else 1)
|
|
||||||
with (os.fdopen(fd, mode, closefd=False) if path == "-" else open(path, mode)) as file:
|
|
||||||
yield file
|
|
||||||
if "w" in mode:
|
|
||||||
file.flush()
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
|
||||||
class _CeaBlock:
|
|
||||||
tag: int
|
|
||||||
data: bytes
|
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
assert 0 < self.tag <= 0b111
|
|
||||||
assert 0 < len(self.data) <= 0b11111
|
|
||||||
|
|
||||||
@property
|
|
||||||
def size(self) -> int:
|
|
||||||
return len(self.data) + 1
|
|
||||||
|
|
||||||
def pack(self) -> bytes:
|
|
||||||
header = (self.tag << 5) | len(self.data)
|
|
||||||
return header.to_bytes() + self.data
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def first_from_raw(cls, raw: (bytes | list[int])) -> "_CeaBlock":
|
|
||||||
assert 0 < raw[0] <= 0xFF
|
|
||||||
tag = (raw[0] & 0b11100000) >> 5
|
|
||||||
data_size = (raw[0] & 0b00011111)
|
|
||||||
data = bytes(raw[1:data_size + 1])
|
|
||||||
return _CeaBlock(tag, data)
|
|
||||||
|
|
||||||
|
|
||||||
_CEA = 128
|
|
||||||
_CEA_AUDIO = 1
|
|
||||||
_CEA_SPEAKERS = 4
|
|
||||||
|
|
||||||
|
|
||||||
class _Edid:
|
|
||||||
# https://en.wikipedia.org/wiki/Extended_Display_Identification_Data
|
|
||||||
|
|
||||||
def __init__(self, path: str) -> None:
|
|
||||||
with _smart_open(path, "rb") as file:
|
|
||||||
data = file.read()
|
|
||||||
if data.startswith(b"\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00"):
|
|
||||||
self.__data = list(data)
|
|
||||||
else:
|
|
||||||
text = re.sub(r"\s", "", data.decode())
|
|
||||||
self.__data = [
|
|
||||||
int(text[index:index + 2], 16)
|
|
||||||
for index in range(0, len(text), 2)
|
|
||||||
]
|
|
||||||
assert len(self.__data) == 256, f"Invalid EDID length: {len(self.__data)}, should be 256 bytes"
|
|
||||||
assert self.__data[126] == 1, "Zero extensions number"
|
|
||||||
assert (self.__data[_CEA + 0], self.__data[_CEA + 1]) == (0x02, 0x03), "Can't find CEA extension"
|
|
||||||
|
|
||||||
def write_hex(self, path: str) -> None:
|
|
||||||
self.__update_checksums()
|
|
||||||
text = "\n".join(
|
|
||||||
"".join(
|
|
||||||
f"{item:0{2}X}"
|
|
||||||
for item in self.__data[index:index + 16]
|
|
||||||
)
|
|
||||||
for index in range(0, len(self.__data), 16)
|
|
||||||
) + "\n"
|
|
||||||
with _smart_open(path, "w") as file:
|
|
||||||
file.write(text)
|
|
||||||
|
|
||||||
def write_bin(self, path: str) -> None:
|
|
||||||
self.__update_checksums()
|
|
||||||
with _smart_open(path, "wb") as file:
|
|
||||||
file.write(bytes(self.__data))
|
|
||||||
|
|
||||||
def __update_checksums(self) -> None:
|
|
||||||
self.__data[127] = 256 - (sum(self.__data[:127]) % 256)
|
|
||||||
self.__data[255] = 256 - (sum(self.__data[128:255]) % 256)
|
|
||||||
|
|
||||||
# =====
|
|
||||||
|
|
||||||
def get_mfc_id(self) -> str:
|
|
||||||
raw = self.__data[8] << 8 | self.__data[9]
|
|
||||||
return bytes([
|
|
||||||
((raw >> 10) & 0b11111) + 0x40,
|
|
||||||
((raw >> 5) & 0b11111) + 0x40,
|
|
||||||
(raw & 0b11111) + 0x40,
|
|
||||||
]).decode("ascii")
|
|
||||||
|
|
||||||
def set_mfc_id(self, mfc_id: str) -> None:
|
|
||||||
assert len(mfc_id) == 3, "Mfc ID must be 3 characters long"
|
|
||||||
data = mfc_id.upper().encode("ascii")
|
|
||||||
for ch in data:
|
|
||||||
assert 0x41 <= ch <= 0x5A, "Mfc ID must contain only A-Z characters"
|
|
||||||
raw = (
|
|
||||||
(data[2] - 0x40)
|
|
||||||
| ((data[1] - 0x40) << 5)
|
|
||||||
| ((data[0] - 0x40) << 10)
|
|
||||||
)
|
|
||||||
self.__data[8] = (raw >> 8) & 0xFF
|
|
||||||
self.__data[9] = raw & 0xFF
|
|
||||||
|
|
||||||
# =====
|
|
||||||
|
|
||||||
def get_product_id(self) -> int:
|
|
||||||
return (self.__data[10] | self.__data[11] << 8)
|
|
||||||
|
|
||||||
def set_product_id(self, product_id: int) -> None:
|
|
||||||
assert 0 <= product_id <= 0xFFFF, f"Product ID should be from 0 to {0xFFFF}"
|
|
||||||
self.__data[10] = product_id & 0xFF
|
|
||||||
self.__data[11] = (product_id >> 8) & 0xFF
|
|
||||||
|
|
||||||
# =====
|
|
||||||
|
|
||||||
def get_serial(self) -> int:
|
|
||||||
return (
|
|
||||||
self.__data[12]
|
|
||||||
| self.__data[13] << 8
|
|
||||||
| self.__data[14] << 16
|
|
||||||
| self.__data[15] << 24
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_serial(self, serial: int) -> None:
|
|
||||||
assert 0 <= serial <= 0xFFFFFFFF, f"Serial should be from 0 to {0xFFFFFFFF}"
|
|
||||||
self.__data[12] = serial & 0xFF
|
|
||||||
self.__data[13] = (serial >> 8) & 0xFF
|
|
||||||
self.__data[14] = (serial >> 16) & 0xFF
|
|
||||||
self.__data[15] = (serial >> 24) & 0xFF
|
|
||||||
|
|
||||||
# =====
|
|
||||||
|
|
||||||
def get_monitor_name(self) -> str:
|
|
||||||
return self.__get_dtd_text(0xFC, "Monitor Name")
|
|
||||||
|
|
||||||
def set_monitor_name(self, text: str) -> None:
|
|
||||||
self.__set_dtd_text(0xFC, "Monitor Name", text)
|
|
||||||
|
|
||||||
def get_monitor_serial(self) -> str:
|
|
||||||
return self.__get_dtd_text(0xFF, "Monitor Serial")
|
|
||||||
|
|
||||||
def set_monitor_serial(self, text: str) -> None:
|
|
||||||
self.__set_dtd_text(0xFF, "Monitor Serial", text)
|
|
||||||
|
|
||||||
def __get_dtd_text(self, d_type: int, name: str) -> str:
|
|
||||||
index = self.__find_dtd_text(d_type, name)
|
|
||||||
return bytes(self.__data[index:index + 13]).decode("cp437").strip()
|
|
||||||
|
|
||||||
def __set_dtd_text(self, d_type: int, name: str, text: str) -> None:
|
|
||||||
index = self.__find_dtd_text(d_type, name)
|
|
||||||
encoded = (text[:13] + "\n" + " " * 12)[:13].encode("cp437")
|
|
||||||
for (offset, ch) in enumerate(encoded):
|
|
||||||
self.__data[index + offset] = ch
|
|
||||||
|
|
||||||
def __find_dtd_text(self, d_type: int, name: str) -> int:
|
|
||||||
for index in [54, 72, 90, 108]:
|
|
||||||
if self.__data[index + 3] == d_type:
|
|
||||||
return index + 5
|
|
||||||
raise NoBlockError(f"Can't find DTD {name}")
|
|
||||||
|
|
||||||
# ===== CEA =====
|
|
||||||
|
|
||||||
def get_audio(self) -> bool:
|
|
||||||
(cbs, _) = self.__parse_cea()
|
|
||||||
audio = False
|
|
||||||
speakers = False
|
|
||||||
for cb in cbs:
|
|
||||||
if cb.tag == _CEA_AUDIO:
|
|
||||||
audio = True
|
|
||||||
elif cb.tag == _CEA_SPEAKERS:
|
|
||||||
speakers = True
|
|
||||||
return (audio and speakers and self.__get_basic_audio())
|
|
||||||
|
|
||||||
def set_audio(self, enabled: bool) -> None:
|
|
||||||
(cbs, dtds) = self.__parse_cea()
|
|
||||||
cbs = [cb for cb in cbs if cb.tag not in [_CEA_AUDIO, _CEA_SPEAKERS]]
|
|
||||||
if enabled:
|
|
||||||
cbs.append(_CeaBlock(_CEA_AUDIO, b"\x09\x7f\x07"))
|
|
||||||
cbs.append(_CeaBlock(_CEA_SPEAKERS, b"\x01\x00\x00"))
|
|
||||||
self.__replace_cea(cbs, dtds)
|
|
||||||
self.__set_basic_audio(enabled)
|
|
||||||
|
|
||||||
def __get_basic_audio(self) -> bool:
|
|
||||||
return bool(self.__data[_CEA + 3] & 0b01000000)
|
|
||||||
|
|
||||||
def __set_basic_audio(self, enabled: bool) -> None:
|
|
||||||
if enabled:
|
|
||||||
self.__data[_CEA + 3] |= 0b01000000
|
|
||||||
else:
|
|
||||||
self.__data[_CEA + 3] &= (0xFF - 0b01000000) # ~X
|
|
||||||
|
|
||||||
def __parse_cea(self) -> tuple[list[_CeaBlock], bytes]:
|
|
||||||
cea = self.__data[_CEA:]
|
|
||||||
dtd_begin = cea[2]
|
|
||||||
if dtd_begin == 0:
|
|
||||||
return ([], b"")
|
|
||||||
|
|
||||||
cbs: list[_CeaBlock] = []
|
|
||||||
if dtd_begin > 4:
|
|
||||||
raw = cea[4:dtd_begin]
|
|
||||||
while len(raw) != 0:
|
|
||||||
cb = _CeaBlock.first_from_raw(raw)
|
|
||||||
cbs.append(cb)
|
|
||||||
raw = raw[cb.size:]
|
|
||||||
|
|
||||||
dtds = b""
|
|
||||||
assert dtd_begin >= 4
|
|
||||||
raw = cea[dtd_begin:]
|
|
||||||
while len(raw) > (18 + 1) and raw[0] != 0:
|
|
||||||
dtds += bytes(raw[:18])
|
|
||||||
raw = raw[18:]
|
|
||||||
|
|
||||||
return (cbs, dtds)
|
|
||||||
|
|
||||||
def __replace_cea(self, cbs: list[_CeaBlock], dtds: bytes) -> None:
|
|
||||||
cbs_packed = b""
|
|
||||||
for cb in cbs:
|
|
||||||
cbs_packed += cb.pack()
|
|
||||||
|
|
||||||
raw = cbs_packed + dtds
|
|
||||||
assert len(raw) <= (128 - 4 - 1), "Too many CEA blocks or DTDs"
|
|
||||||
|
|
||||||
self.__data[_CEA + 2] = (0 if len(raw) == 0 else (len(cbs_packed) + 4))
|
|
||||||
|
|
||||||
for index in range(4, 127):
|
|
||||||
try:
|
|
||||||
ch = raw[index - 4]
|
|
||||||
except IndexError:
|
|
||||||
ch = 0
|
|
||||||
self.__data[_CEA + index] = ch
|
|
||||||
|
|
||||||
|
|
||||||
def _format_bool(value: bool) -> str:
|
def _format_bool(value: bool) -> str:
|
||||||
return ("yes" if value else "no")
|
return ("yes" if value else "no")
|
||||||
|
|
||||||
@ -283,7 +46,7 @@ def _make_format_hex(size: int) -> Callable[[int], str]:
|
|||||||
return (lambda value: ("0x{:0%dX} ({})" % (size * 2)).format(value, value))
|
return (lambda value: ("0x{:0%dX} ({})" % (size * 2)).format(value, value))
|
||||||
|
|
||||||
|
|
||||||
def _print_edid(edid: _Edid) -> None:
|
def _print_edid(edid: Edid) -> None:
|
||||||
for (key, get, fmt) in [
|
for (key, get, fmt) in [
|
||||||
("Manufacturer ID:", edid.get_mfc_id, str),
|
("Manufacturer ID:", edid.get_mfc_id, str),
|
||||||
("Product ID: ", edid.get_product_id, _make_format_hex(2)),
|
("Product ID: ", edid.get_product_id, _make_format_hex(2)),
|
||||||
@ -294,10 +57,37 @@ def _print_edid(edid: _Edid) -> None:
|
|||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
print(key, fmt(get()), file=sys.stderr) # type: ignore
|
print(key, fmt(get()), file=sys.stderr) # type: ignore
|
||||||
except NoBlockError:
|
except EdidNoBlockError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _find_out2_edid_path() -> str:
|
||||||
|
card = os.path.basename(os.readlink("/dev/dri/by-path/platform-gpu-card"))
|
||||||
|
path = f"/sys/devices/platform/gpu/drm/{card}/{card}-HDMI-A-2"
|
||||||
|
with open(os.path.join(path, "status")) as file:
|
||||||
|
if file.read().startswith("d"):
|
||||||
|
raise SystemExit("No display found")
|
||||||
|
return os.path.join(path, "edid")
|
||||||
|
|
||||||
|
|
||||||
|
def _adopt_out2_ids(dest: Edid) -> None:
|
||||||
|
src = Edid.from_file(_find_out2_edid_path())
|
||||||
|
dest.set_monitor_name(src.get_monitor_name())
|
||||||
|
try:
|
||||||
|
dest.get_monitor_serial()
|
||||||
|
except EdidNoBlockError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ser = src.get_monitor_serial()
|
||||||
|
except EdidNoBlockError:
|
||||||
|
ser = "{:08X}".format(src.get_serial())
|
||||||
|
dest.set_monitor_serial(ser)
|
||||||
|
dest.set_mfc_id(src.get_mfc_id())
|
||||||
|
dest.set_product_id(src.get_product_id())
|
||||||
|
dest.set_serial(src.get_serial())
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-branches,too-many-statements
|
def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-branches,too-many-statements
|
||||||
# (parent_parser, argv, _) = init(
|
# (parent_parser, argv, _) = init(
|
||||||
@ -326,6 +116,10 @@ def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-bra
|
|||||||
help="Import the specified bin/hex EDID to the [--edid] file as a hex text", metavar="<file>")
|
help="Import the specified bin/hex EDID to the [--edid] file as a hex text", metavar="<file>")
|
||||||
parser.add_argument("--import-preset", choices=presets,
|
parser.add_argument("--import-preset", choices=presets,
|
||||||
help="Restore default EDID or choose the preset", metavar=f"{{ {' | '.join(presets)} }}",)
|
help="Restore default EDID or choose the preset", metavar=f"{{ {' | '.join(presets)} }}",)
|
||||||
|
parser.add_argument("--import-display-ids", action="store_true",
|
||||||
|
help="On PiKVM V4, import and adopt IDs from a physical display connected to the OUT2 port")
|
||||||
|
parser.add_argument("--import-display", action="store_true",
|
||||||
|
help="On PiKVM V4, import full EDID from a physical display connected to the OUT2 port")
|
||||||
parser.add_argument("--set-audio", type=valid_bool,
|
parser.add_argument("--set-audio", type=valid_bool,
|
||||||
help="Enable or disable audio", metavar="<yes|no>")
|
help="Enable or disable audio", metavar="<yes|no>")
|
||||||
parser.add_argument("--set-mfc-id",
|
parser.add_argument("--set-mfc-id",
|
||||||
@ -348,30 +142,37 @@ def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-bra
|
|||||||
help="Presets directory", metavar="<dir>")
|
help="Presets directory", metavar="<dir>")
|
||||||
options = parser.parse_args(argv[1:])
|
options = parser.parse_args(argv[1:])
|
||||||
|
|
||||||
base: (_Edid | None) = None
|
base: (Edid | None) = None
|
||||||
if options.import_preset:
|
if options.import_preset:
|
||||||
imp = options.import_preset
|
imp = options.import_preset
|
||||||
if "." in imp:
|
if "." in imp:
|
||||||
(base_name, imp) = imp.split(".", 1) # v3.1080p-by-default
|
(base_name, imp) = imp.split(".", 1) # v3.1080p-by-default
|
||||||
base = _Edid(os.path.join(options.presets_path, f"{base_name}.hex"))
|
base = Edid.from_file(os.path.join(options.presets_path, f"{base_name}.hex"))
|
||||||
imp = f"_{imp}"
|
imp = f"_{imp}"
|
||||||
options.imp = os.path.join(options.presets_path, f"{imp}.hex")
|
options.imp = os.path.join(options.presets_path, f"{imp}.hex")
|
||||||
|
|
||||||
|
if options.import_display:
|
||||||
|
options.imp = _find_out2_edid_path()
|
||||||
|
|
||||||
orig_edid_path = options.edid_path
|
orig_edid_path = options.edid_path
|
||||||
if options.imp:
|
if options.imp:
|
||||||
options.export_hex = options.edid_path
|
options.export_hex = options.edid_path
|
||||||
options.edid_path = options.imp
|
options.edid_path = options.imp
|
||||||
|
|
||||||
edid = _Edid(options.edid_path)
|
edid = Edid.from_file(options.edid_path)
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
for cmd in dir(_Edid):
|
if options.import_display_ids:
|
||||||
|
_adopt_out2_ids(edid)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
for cmd in dir(Edid):
|
||||||
if cmd.startswith("set_"):
|
if cmd.startswith("set_"):
|
||||||
value = getattr(options, cmd)
|
value = getattr(options, cmd)
|
||||||
if value is None and base is not None:
|
if value is None and base is not None:
|
||||||
try:
|
try:
|
||||||
value = getattr(base, cmd.replace("set_", "get_"))()
|
value = getattr(base, cmd.replace("set_", "get_"))()
|
||||||
except NoBlockError:
|
except EdidNoBlockError:
|
||||||
pass
|
pass
|
||||||
if value is not None:
|
if value is not None:
|
||||||
getattr(edid, cmd)(value)
|
getattr(edid, cmd)(value)
|
||||||
@ -400,8 +201,7 @@ def main(argv: (list[str] | None)=None) -> None: # pylint: disable=too-many-bra
|
|||||||
"/usr/bin/v4l2-ctl",
|
"/usr/bin/v4l2-ctl",
|
||||||
f"--device={options.device_path}",
|
f"--device={options.device_path}",
|
||||||
f"--set-edid=file={orig_edid_path}",
|
f"--set-edid=file={orig_edid_path}",
|
||||||
"--fix-edid-checksums",
|
|
||||||
"--info-edid",
|
"--info-edid",
|
||||||
], stdout=sys.stderr, check=True)
|
], stdout=sys.stderr, check=True)
|
||||||
except subprocess.CalledProcessError as err:
|
except subprocess.CalledProcessError as ex:
|
||||||
raise SystemExit(str(err))
|
raise SystemExit(str(ex))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user