This commit is contained in:
Devaev Maxim 2018-09-26 02:11:23 +03:00
parent 6e9a3222ce
commit 940989b6e9
24 changed files with 265 additions and 173 deletions

View File

@ -15,6 +15,6 @@ search = version="{current_version}"
replace = version="{new_version}"
[bumpversion:file:PKGBUILD]
search = pkgver="{current_version}"
replace = pkgver="{new_version}"
search = pkgver={current_version}
replace = pkgver={new_version}

View File

@ -20,7 +20,8 @@ all:
run:
docker build --rm --tag $(TESTENV_IMAGE) -f testenv/Dockerfile .
sudo modprobe loop
docker build $(TESTENV_OPTS) --rm --tag $(TESTENV_IMAGE) -f testenv/Dockerfile .
- docker run --rm \
--volume `pwd`/kvmd:/kvmd:ro \
--volume `pwd`/web:/usr/share/kvmd/web:ro \

View File

@ -2,26 +2,26 @@
# Author: Maxim Devaev <mdevaev@gmail.com>
pkgname="kvmd"
pkgver="0.66"
pkgrel="1"
pkgname=kvmd
pkgver=0.66
pkgrel=1
pkgdesc="The main Pi-KVM daemon"
arch=("any")
url="https://github.com/pi-kvm/pi-kvm"
license=("GPL")
license=(GPL)
arch=(any)
depends=(
"python"
"python-yaml"
"python-aiohttp"
"python-aiofiles"
"python-pyudev"
"python-raspberry-gpio"
"python-pyserial"
"python-setproctitle"
python
python-yaml
python-aiohttp
python-aiofiles
python-pyudev
python-raspberry-gpio
python-pyserial
python-setproctitle
)
makedepends=("python-setuptools")
makedepends=(python-setuptools)
source=("$url/archive/v$pkgver.tar.gz")
md5sums=("SKIP")
md5sums=(SKIP)
build() {
@ -34,9 +34,9 @@ build() {
package() {
cd $srcdir/$pkgname-build
python setup.py install --root=$pkgdir
install -Dm644 configs/kvmd.service "$pkgdir"/usr/lib/systemd/system/kvmd.service
mkdir -p "$pkgdir"/usr/share/kvmd
cp -r web "$pkgdir"/usr/share/kvmd
cp -r configs "$pkgdir"/usr/share/kvmd
python setup.py install --root="$pkgdir"
install -Dm644 configs/kvmd.service "$pkgdir/usr/lib/systemd/system/kvmd.service"
mkdir -p "$pkgdir/usr/share/kvmd"
cp -r web "$pkgdir/usr/share/kvmd"
cp -r configs "$pkgdir/usr/share/kvmd"
}

View File

@ -36,15 +36,21 @@ kvmd:
init_restart_after: 1.0
shutdown_delay: 10.0
resolutions:
- 800x600 - 720x576
quality: 80
cmd:
- "/usr/bin/mjpg_streamer"
- "-i"
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r {resolution}"
- "-o"
- "output_http.so -l localhost -p 8082"
- "/usr/bin/ustreamer"
- "--device=/dev/kvmd-streamer"
- "--tv-standard=pal"
- "--format=yuyv"
- "--encoder=omx"
- "--jpeg-quality={quality}"
- "--width=720"
- "--height=576"
- "--fake-width=800"
- "--fake-height=600"
- "--host=localhost"
- "--port=8082"
logging:
version: 1

72
kvmd/configs/kvmd/v2.yaml Normal file
View File

@ -0,0 +1,72 @@
kvmd:
server:
host: localhost
port: 8081
heartbeat: 3.0
hid:
device: /dev/ttyAMA0
speed: 115200
atx:
pinout:
power_led: 16
hdd_led: 12
power_switch: 26
reset_switch: 20
click_delay: 0.1
long_click_delay: 5.5
state_poll: 0.1
msd:
device: "/dev/kvmd-msd"
init_delay: 2.0
write_meta: true
chunk_size: 65536
streamer:
pinout:
cap: -1
conv: -1
sync_delay: 0.0
init_delay: 1.0
init_restart_after: 0.0
shutdown_delay: 10.0
quality: 80
cmd:
- "/usr/bin/ustreamer"
- "--device=/dev/kvmd-streamer"
- "--format=uyvy"
- "--encoder=omx"
- "--jpeg-quality={quality}"
- "--dv-timings"
- "--host=localhost"
- "--port=8082"
logging:
version: 1
disable_existing_loggers: false
formatters:
console:
(): logging.Formatter
style: "{"
datefmt: "%H:%M:%S"
format: "[{asctime}] {name:20.20} {levelname:>7} --- {message}"
handlers:
console:
level: DEBUG
class: logging.StreamHandler
stream: ext://sys.stdout
formatter: console
root:
level: INFO
handlers:
- console

View File

@ -31,7 +31,7 @@ http {
server localhost:8081 fail_timeout=0s max_fails=0;
}
upstream mjpg_streamer {
upstream ustreamer {
server localhost:8082 fail_timeout=0s max_fails=0;
}
@ -112,9 +112,9 @@ http {
include /etc/nginx/proxy-params.conf;
}
location ~ ^/streamer/(snapshot|stream)(?:/(.*))?$ {
rewrite /streamer/?(.*)(?:/(.*))?$ /?action=$1 break;
proxy_pass http://mjpg_streamer;
location /streamer {
rewrite /streamer/?(.*) /$1 break;
proxy_pass http://ustreamer;
include /etc/nginx/proxy-params.conf;
proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering;

View File

@ -49,7 +49,7 @@ def main() -> None:
sync_delay=float(config["streamer"]["sync_delay"]),
init_delay=float(config["streamer"]["init_delay"]),
init_restart_after=float(config["streamer"]["init_restart_after"]),
resolutions=config["streamer"]["resolutions"],
quality=int(config["streamer"]["quality"]),
cmd=list(map(str, config["streamer"]["cmd"])),
loop=loop,
)

View File

@ -128,7 +128,7 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__system_tasks: List[asyncio.Task] = []
self.__reset_streamer = False
self.__streamer_resolution = streamer.get_current_resolution()
self.__streamer_quality = streamer.get_current_quality()
def run(self, host: str, port: int) -> None:
self.__hid.start()
@ -166,7 +166,7 @@ class Server: # pylint: disable=too-many-instance-attributes
# ===== INFO
async def __info_handler(self, _: aiohttp.web.Request) -> aiohttp.web.WebSocketResponse:
async def __info_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
return _json(_get_system_info())
# ===== WEBSOCKET
@ -305,12 +305,15 @@ class Server: # pylint: disable=too-many-instance-attributes
@_wrap_exceptions_for_web("Can't set stream params")
async def __streamer_set_params_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
resolution = request.query.get("resolution")
if resolution:
if resolution in self.__streamer.get_available_resolutions():
self.__streamer_resolution = resolution
else:
raise BadRequest("Unknown resolution %r" % (resolution))
quality = request.query.get("quality")
if quality:
try:
quality_int = int(quality)
if not (1 <= quality_int <= 100):
raise ValueError()
except Exception:
raise BadRequest("Invalid quality %r" % (quality))
self.__streamer_quality = quality_int
return _json()
async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
@ -356,7 +359,7 @@ class Server: # pylint: disable=too-many-instance-attributes
cur = len(self.__sockets)
if prev == 0 and cur > 0:
if not self.__streamer.is_running():
await self.__streamer.start(self.__streamer_resolution)
await self.__streamer.start(self.__streamer_quality)
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
elif prev > 0 and cur == 0:
shutdown_at = time.time() + self.__streamer_shutdown_delay
@ -365,10 +368,10 @@ class Server: # pylint: disable=too-many-instance-attributes
await self.__streamer.stop()
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
if self.__reset_streamer or self.__streamer_resolution != self.__streamer.get_current_resolution():
if self.__reset_streamer or self.__streamer_quality != self.__streamer.get_current_quality():
if self.__streamer.is_running():
await self.__streamer.stop()
await self.__streamer.start(self.__streamer_resolution, no_init_restart=True)
await self.__streamer.start(self.__streamer_quality, no_init_restart=True)
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
self.__reset_streamer = False

View File

@ -1,8 +1,6 @@
import asyncio
import asyncio.subprocess
from collections import OrderedDict as odict
from typing import List
from typing import Dict
from typing import Optional
@ -21,10 +19,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
sync_delay: float,
init_delay: float,
init_restart_after: float,
resolutions: List[str],
quality: int,
cmd: List[str],
loop: asyncio.AbstractEventLoop,
) -> None:
@ -33,26 +29,18 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__sync_delay = sync_delay
self.__init_delay = init_delay
self.__init_restart_after = init_restart_after
self.__resolutions = odict([
(display, (real or display))
for (display, real) in [
(tuple(map(str.lower, map(str.strip, resolution.split("-", maxsplit=1)))) + ("",))[:2]
for resolution in resolutions
]
])
self.__resolution = list(self.__resolutions)[0]
self.__quality = quality
self.__cmd = cmd
self.__loop = loop
self.__proc_task: Optional[asyncio.Task] = None
async def start(self, resolution: str, no_init_restart: bool=False) -> None:
async def start(self, quality: int, no_init_restart: bool=False) -> None:
logger = get_logger()
logger.info("Starting streamer ...")
assert resolution in self.__resolutions, (resolution, self.__resolutions)
self.__resolution = resolution
assert 1 <= quality <= 100
self.__quality = quality
await self.__inner_start()
if self.__init_restart_after > 0.0 and not no_init_restart:
logger.info("Stopping streamer to restart ...")
@ -67,22 +55,13 @@ class Streamer: # pylint: disable=too-many-instance-attributes
def is_running(self) -> bool:
return bool(self.__proc_task)
def get_current_resolution(self) -> str:
return self.__resolution
def get_available_resolutions(self) -> List[str]:
return list(self.__resolutions)
def get_current_quality(self) -> int:
return self.__quality
def get_state(self) -> Dict:
(width, height) = tuple(map(int, self.__resolution.split("x")))
return {
"is_running": self.is_running(),
"size": {
"width": width,
"height": height,
},
"resolution": self.__resolution,
"resolutions": list(self.__resolutions),
"quality": self.__quality,
}
async def cleanup(self) -> None:
@ -118,7 +97,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
while True: # pylint: disable=too-many-nested-blocks
proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member
try:
cmd = [part.format(resolution=self.__resolutions[self.__resolution]) for part in self.__cmd]
cmd = [part.format(quality=self.__quality) for part in self.__cmd]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,

View File

@ -33,7 +33,7 @@ RUN pacman -Syy \
python-pip \
nginx-mainline \
nginx-mainline-mod-lua \
mjpg-streamer \
ustreamer \
socat \
&& pacman -Sc --noconfirm

View File

@ -36,17 +36,16 @@ kvmd:
init_restart_after: 1.0
shutdown_delay: 10.0
resolutions:
- 640x480
- 800x600
- 1024x768
quality: 80
cmd:
- "/usr/bin/mjpg_streamer"
- "-i"
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {resolution}"
- "-o"
- "output_http.so -l 0.0.0.0 -p 8082"
- "/usr/bin/ustreamer"
- "--device=/dev/kvmd-streamer"
- "--jpeg-quality={quality}"
- "--width=800"
- "--height=600"
- "--host=0.0.0.0"
- "--port=8082"
logging:
version: 1

View File

@ -35,7 +35,7 @@ div.stream-box-mouse-enabled {
cursor: url("../svg/stream-mouse-cursor.svg"), pointer;
}
select#stream-resolution-select {
select#stream-quality-select {
margin: 8px 0 8px 0;
}

View File

@ -75,9 +75,9 @@
</div>
<hr>
<div data-dont-hide-menu class="ctl-dropdown-content-text">
Resolution:
<select disabled id="stream-resolution-select">
<option>640x480</option>
Quality:
<select disabled id="stream-quality-select">
<option>80%</option>
</select>
</div>
<hr>

View File

@ -5,8 +5,7 @@ function Stream() {
var __prev_state = false;
var __resolution = "640x480";
var __resolutions = ["640x480"];
var __quality = 80;
var __normal_size = {width: 640, height: 480};
var __size_factor = 1;
@ -14,8 +13,13 @@ function Stream() {
var __init__ = function() {
$("stream-led").title = "Stream inactive";
var quality = 10;
for (; quality <= 100; quality += 10) {
$("stream-quality-select").innerHTML += "<option value=\"" + quality + "\">" + quality + "%</option>";
}
tools.setOnClick($("stream-reset-button"), __clickResetButton);
$("stream-resolution-select").onchange = __changeResolution;
$("stream-quality-select").onchange = __changeQuality;
$("stream-size-slider").oninput = __resize;
$("stream-size-slider").onchange = __resize;
@ -27,12 +31,10 @@ function Stream() {
// XXX: In current implementation we don't need this event because Stream() has own state poller
var __startPoller = function() {
var http = tools.makeRequest("GET", "/streamer/snapshot", function() {
if (http.readyState === 2 || http.readyState === 4) {
var status = http.status;
http.onreadystatechange = null;
http.abort();
if (status !== 200) {
var http = tools.makeRequest("GET", "/streamer/ping", function() {
if (http.readyState === 4) {
var response = (http.status === 200 ? JSON.parse(http.responseText) : null);
if (http.status !== 200 || !response.stream.online) {
tools.info("Refreshing stream ...");
__prev_state = false;
$("stream-image").className = "stream-image-inactive";
@ -40,8 +42,9 @@ function Stream() {
$("stream-led").className = "led-off";
$("stream-led").title = "Stream inactive";
$("stream-reset-button").disabled = true;
$("stream-resolution-select").disabled = true;
} else if (!__prev_state) {
$("stream-quality-select").disabled = true;
} else if (http.status === 200 && !__prev_state) {
__normal_size = response.stream.resolution;
__refreshImage();
__prev_state = true;
$("stream-image").className = "stream-image-active";
@ -49,6 +52,7 @@ function Stream() {
$("stream-led").className = "led-on";
$("stream-led").title = "Stream is active";
$("stream-reset-button").disabled = false;
$("stream-quality-select").disabled = false;
}
}
});
@ -66,11 +70,11 @@ function Stream() {
});
};
var __changeResolution = function() {
var resolution = $("stream-resolution-select").value;
if (__resolution != resolution) {
$("stream-resolution-select").disabled = true;
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?resolution=" + resolution, function() {
var __changeQuality = function() {
var quality = parseInt($("stream-quality-select").value);
if (__quality != quality) {
$("stream-quality-select").disabled = true;
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?quality=" + quality, function() {
if (http.readyState === 4) {
if (http.status !== 200) {
ui.error("Can't configure stream:<br>", http.responseText);
@ -99,25 +103,14 @@ function Stream() {
if (http.readyState === 4 && http.status === 200) {
var result = JSON.parse(http.responseText).result;
if (__resolutions != result.resolutions) {
tools.info("Resolutions list changed:", result.resolutions);
$("stream-resolution-select").innerHTML = "";
result.resolutions.forEach(function(resolution) {
$("stream-resolution-select").innerHTML += "<option value=\"" + resolution + "\">" + resolution + "</option>";
});
$("stream-resolution-select").disabled = (result.resolutions.length == 1);
__resolutions = result.resolutions;
if (__quality != result.quality) {
tools.info("Quality changed:", result.quality);
document.querySelector("#stream-quality-select [value=\"" + result.quality + "\"]").selected = true;
__quality = result.quality;
}
if (__resolution != result.resolution) {
tools.info("Resolution changed:", result.resolution);
document.querySelector("#stream-resolution-select [value=\"" + result.resolution + "\"]").selected = true;
__resolution = result.resolution;
}
__normal_size = result.size;
__applySizeFactor();
$("stream-image").src = "/streamer/stream/" + new Date().getTime();
$("stream-image").src = "/streamer/stream?t=" + new Date().getTime();
}
});
};

View File

@ -9,7 +9,12 @@ WEBUI_ADMIN_PASSWD ?= admin
# =====
_BUILD_DIR = ./.build
_KVMD_VERSION = $(shell bash -c 'source ../kvmd/PKGBUILD; echo $$pkgver')
define fetch_version
curl --silent "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=$(1)" \
| grep "^pkgver=" \
| grep -Po "\d+\.\d+[^\"']*"
endef
# =====
@ -21,24 +26,30 @@ v1:
make _pikvm PIKVM_PLATFORM=v1 PI_BOARD=rpi-2
v2:
make _pikvm PIKVM_PLATFORM=v2 PI_BOARD=rpi-2
shell:
cd $(_BUILD_DIR) && make shell
_pikvm: $(_BUILD_DIR)
rm -rf $(_BUILD_DIR)/stages/pikvm
rm -rf $(_BUILD_DIR)/stages/pikvm-*
rm -rf $(_BUILD_DIR)/builder/scripts/pikvm
cp -a platforms/$(PIKVM_PLATFORM) $(_BUILD_DIR)/stages/pikvm
cp -a platforms/common $(_BUILD_DIR)/stages/pikvm-common
cp -a platforms/$(PIKVM_PLATFORM) $(_BUILD_DIR)/stages/pikvm-$(PIKVM_PLATFORM)
cd $(_BUILD_DIR) && make binfmt && make _rpi \
BUILD_OPTS=" $(BUILD_OPTS) \
--build-arg KVMD_VERSION=$(_KVMD_VERSION) \
--build-arg USTREAMER_VERSION=$(call fetch_version, ustreamer) \
--build-arg KVMD_VERSION=$(call fetch_version, kvmd) \
--build-arg NEW_SSH_KEYGEN=$(shell uuidgen) \
--build-arg WEBUI_ADMIN_PASSWD='$(WEBUI_ADMIN_PASSWD)' \
--build-arg NEW_HTTPS_CERT=$(shell uuidgen) \
" \
PROJECT=pikvm \
BOARD=$(PI_BOARD) \
STAGES="__init__ os watchdog ro pikvm rootssh __cleanup__" \
STAGES="__init__ os watchdog ro pikvm-common pikvm-$(PIKVM_PLATFORM) rootssh __cleanup__" \
LOCALE=$(LOCALE) \
TIMEZONE=$(TIMEZONE)

View File

@ -0,0 +1,51 @@
RUN pkg-install \
nginx-mainline \
apache-tools \
raspberrypi-firmware \
v4l-utils \
python \
python-raspberry-gpio \
customizepkg
RUN systemctl enable nginx
COPY stages/pikvm-common/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-ndk
COPY stages/pikvm-common/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-lua
RUN env MAKEPKGOPTS="--skipchecksums --skippgpcheck" pkg-install nginx-mainline-mod-lua
ARG USTREAMER_VERSION
ENV USTREAMER_VERSION $USTREAMER_VERSION
RUN echo $USTREAMER_VERSION
RUN pkg-install ustreamer
ARG KVMD_VERSION
ENV KVMD_VERSION $KVMD_VERSION
RUN echo $KVMD_VERSION
RUN pkg-install kvmd
RUN systemctl enable kvmd
COPY stages/pikvm-common/sysctl.conf /etc/sysctl.d/99-pikvm.conf
COPY stages/pikvm-common/motd /etc/
RUN sed -i -e "s/console=ttyAMA0\,115200//g" /boot/cmdline.txt \
&& sed -i -e "s/kgdboc=ttyAMA0\,115200//g" /boot/cmdline.txt
RUN systemctl mask serial-getty@ttyAMA0.service
RUN rm -rf /etc/nginx/* \
&& cp /usr/share/kvmd/configs/nginx/* /etc/nginx/ \
&& sed -i -e "s/^#PROD//g" /etc/nginx/nginx.conf
ARG WEBUI_ADMIN_PASSWD
ENV WEBUI_ADMIN_PASSWD $WEBUI_ADMIN_PASSWD
RUN echo "$WEBUI_ADMIN_PASSWD" | htpasswd -ci /etc/nginx/htpasswd admin
ARG NEW_HTTPS_CERT
ENV NEW_HTTPS_CERT $NEW_HTTPS_CERT
RUN echo $NEW_HTTPS_CERT
RUN mkdir /etc/nginx/ssl \
&& cd /etc/nginx/ssl \
&& openssl req -new -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 3650 \
-subj "/C=RU/ST=Moscow/L=Moscow/O=Pi-KVM/OU=Pi-KVM/CN=localhost" \
&& chmod 400 server.key \
&& chmod 444 server.crt \
&& chmod 750 /etc/nginx/ssl \
&& chown -R root:http /etc/nginx/ssl

View File

@ -1,48 +1,4 @@
RUN pkg-install \
nginx-mainline \
apache-tools \
mjpg-streamer \
python \
python-raspberry-gpio \
customizepkg
RUN systemctl enable nginx
COPY stages/pikvm-v1/config.txt /boot/
COPY stages/pikvm-v1/udev.rules /etc/udev/rules.d/pikvm.rules
COPY stages/pikvm/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-ndk
COPY stages/pikvm/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-lua
RUN env MAKEPKGOPTS="--skipchecksums --skippgpcheck" pkg-install nginx-mainline-mod-lua
ARG KVMD_VERSION
ENV KVMD_VERSION $KVMD_VERSION
RUN echo $KVMD_VERSION
RUN pkg-install kvmd
RUN systemctl enable kvmd
COPY stages/pikvm/config.txt /boot/
COPY stages/pikvm/sysctl.conf /etc/sysctl.d/99-pikvm.conf
COPY stages/pikvm/udev.rules /etc/udev/rules.d/pikvm.rules
COPY stages/pikvm/motd /etc/
RUN sed -i -e "s/console=ttyAMA0\,115200//g" /boot/cmdline.txt \
&& sed -i -e "s/kgdboc=ttyAMA0\,115200//g" /boot/cmdline.txt
RUN systemctl mask serial-getty@ttyAMA0.service
RUN rm -rf /etc/nginx/* \
&& cp /usr/share/kvmd/configs/nginx/* /etc/nginx/ \
&& sed -i -e "s/^#PROD//g" /etc/nginx/nginx.conf
RUN cp /usr/share/kvmd/configs/kvmd/v1.yaml /etc/kvmd.yaml
ARG WEBUI_ADMIN_PASSWD
ENV WEBUI_ADMIN_PASSWD $WEBUI_ADMIN_PASSWD
RUN echo "$WEBUI_ADMIN_PASSWD" | htpasswd -ci /etc/nginx/htpasswd admin
ARG NEW_HTTPS_CERT
ENV NEW_HTTPS_CERT $NEW_HTTPS_CERT
RUN echo $NEW_HTTPS_CERT
RUN mkdir /etc/nginx/ssl \
&& cd /etc/nginx/ssl \
&& openssl req -new -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 3650 \
-subj "/C=RU/ST=Moscow/L=Moscow/O=Pi-KVM/OU=Pi-KVM/CN=localhost" \
&& chmod 400 server.key \
&& chmod 444 server.crt \
&& chmod 750 /etc/nginx/ssl \
&& chown -R root:http /etc/nginx/ssl

View File

@ -0,0 +1,11 @@
RUN pkg-install \
dkms \
tc358743-dkms
RUN sed -i -e "s/rootwait/cma=128M rootwait/g" /boot/cmdline.txt
COPY stages/pikvm-v2/config.txt /boot/
COPY stages/pikvm-v2/udev.rules /etc/udev/rules.d/pikvm.rules
COPY stages/pikvm-v2/modules.load /etc/modules-load.d/pikvm.conf
RUN cp /usr/share/kvmd/configs/kvmd/v2.yaml /etc/kvmd.yaml

View File

@ -0,0 +1,5 @@
gpu_mem=16
start_x=1
enable_uart=1
dtoverlay=tc358743,i2c_pins_28_29=1
dtparam=act_led_gpio=27

View File

@ -0,0 +1 @@
tc358743

View File

@ -0,0 +1,4 @@
# 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", KERNELS=="soc", SYMLINK+="kvmd-streamer"
KERNEL=="sd[a-z]", SUBSYSTEM=="block", KERNELS=="1-1.4:1.0", SYMLINK+="kvmd-msd"