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}" replace = version="{new_version}"
[bumpversion:file:PKGBUILD] [bumpversion:file:PKGBUILD]
search = pkgver="{current_version}" search = pkgver={current_version}
replace = pkgver="{new_version}" replace = pkgver={new_version}

View File

@ -20,7 +20,8 @@ all:
run: 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 \ - docker run --rm \
--volume `pwd`/kvmd:/kvmd:ro \ --volume `pwd`/kvmd:/kvmd:ro \
--volume `pwd`/web:/usr/share/kvmd/web:ro \ --volume `pwd`/web:/usr/share/kvmd/web:ro \

View File

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

View File

@ -36,15 +36,21 @@ kvmd:
init_restart_after: 1.0 init_restart_after: 1.0
shutdown_delay: 10.0 shutdown_delay: 10.0
resolutions: quality: 80
- 800x600 - 720x576
cmd: cmd:
- "/usr/bin/mjpg_streamer" - "/usr/bin/ustreamer"
- "-i" - "--device=/dev/kvmd-streamer"
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r {resolution}" - "--tv-standard=pal"
- "-o" - "--format=yuyv"
- "output_http.so -l localhost -p 8082" - "--encoder=omx"
- "--jpeg-quality={quality}"
- "--width=720"
- "--height=576"
- "--fake-width=800"
- "--fake-height=600"
- "--host=localhost"
- "--port=8082"
logging: logging:
version: 1 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; server localhost:8081 fail_timeout=0s max_fails=0;
} }
upstream mjpg_streamer { upstream ustreamer {
server localhost:8082 fail_timeout=0s max_fails=0; server localhost:8082 fail_timeout=0s max_fails=0;
} }
@ -112,9 +112,9 @@ http {
include /etc/nginx/proxy-params.conf; include /etc/nginx/proxy-params.conf;
} }
location ~ ^/streamer/(snapshot|stream)(?:/(.*))?$ { location /streamer {
rewrite /streamer/?(.*)(?:/(.*))?$ /?action=$1 break; rewrite /streamer/?(.*) /$1 break;
proxy_pass http://mjpg_streamer; proxy_pass http://ustreamer;
include /etc/nginx/proxy-params.conf; include /etc/nginx/proxy-params.conf;
proxy_buffering off; proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering; proxy_ignore_headers X-Accel-Buffering;

View File

@ -49,7 +49,7 @@ def main() -> None:
sync_delay=float(config["streamer"]["sync_delay"]), sync_delay=float(config["streamer"]["sync_delay"]),
init_delay=float(config["streamer"]["init_delay"]), init_delay=float(config["streamer"]["init_delay"]),
init_restart_after=float(config["streamer"]["init_restart_after"]), 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"])), cmd=list(map(str, config["streamer"]["cmd"])),
loop=loop, loop=loop,
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,8 +5,7 @@ function Stream() {
var __prev_state = false; var __prev_state = false;
var __resolution = "640x480"; var __quality = 80;
var __resolutions = ["640x480"];
var __normal_size = {width: 640, height: 480}; var __normal_size = {width: 640, height: 480};
var __size_factor = 1; var __size_factor = 1;
@ -14,8 +13,13 @@ function Stream() {
var __init__ = function() { var __init__ = function() {
$("stream-led").title = "Stream inactive"; $("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); tools.setOnClick($("stream-reset-button"), __clickResetButton);
$("stream-resolution-select").onchange = __changeResolution; $("stream-quality-select").onchange = __changeQuality;
$("stream-size-slider").oninput = __resize; $("stream-size-slider").oninput = __resize;
$("stream-size-slider").onchange = __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 // XXX: In current implementation we don't need this event because Stream() has own state poller
var __startPoller = function() { var __startPoller = function() {
var http = tools.makeRequest("GET", "/streamer/snapshot", function() { var http = tools.makeRequest("GET", "/streamer/ping", function() {
if (http.readyState === 2 || http.readyState === 4) { if (http.readyState === 4) {
var status = http.status; var response = (http.status === 200 ? JSON.parse(http.responseText) : null);
http.onreadystatechange = null; if (http.status !== 200 || !response.stream.online) {
http.abort();
if (status !== 200) {
tools.info("Refreshing stream ..."); tools.info("Refreshing stream ...");
__prev_state = false; __prev_state = false;
$("stream-image").className = "stream-image-inactive"; $("stream-image").className = "stream-image-inactive";
@ -40,8 +42,9 @@ function Stream() {
$("stream-led").className = "led-off"; $("stream-led").className = "led-off";
$("stream-led").title = "Stream inactive"; $("stream-led").title = "Stream inactive";
$("stream-reset-button").disabled = true; $("stream-reset-button").disabled = true;
$("stream-resolution-select").disabled = true; $("stream-quality-select").disabled = true;
} else if (!__prev_state) { } else if (http.status === 200 && !__prev_state) {
__normal_size = response.stream.resolution;
__refreshImage(); __refreshImage();
__prev_state = true; __prev_state = true;
$("stream-image").className = "stream-image-active"; $("stream-image").className = "stream-image-active";
@ -49,6 +52,7 @@ function Stream() {
$("stream-led").className = "led-on"; $("stream-led").className = "led-on";
$("stream-led").title = "Stream is active"; $("stream-led").title = "Stream is active";
$("stream-reset-button").disabled = false; $("stream-reset-button").disabled = false;
$("stream-quality-select").disabled = false;
} }
} }
}); });
@ -66,11 +70,11 @@ function Stream() {
}); });
}; };
var __changeResolution = function() { var __changeQuality = function() {
var resolution = $("stream-resolution-select").value; var quality = parseInt($("stream-quality-select").value);
if (__resolution != resolution) { if (__quality != quality) {
$("stream-resolution-select").disabled = true; $("stream-quality-select").disabled = true;
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?resolution=" + resolution, function() { var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?quality=" + quality, function() {
if (http.readyState === 4) { if (http.readyState === 4) {
if (http.status !== 200) { if (http.status !== 200) {
ui.error("Can't configure stream:<br>", http.responseText); ui.error("Can't configure stream:<br>", http.responseText);
@ -99,25 +103,14 @@ function Stream() {
if (http.readyState === 4 && http.status === 200) { if (http.readyState === 4 && http.status === 200) {
var result = JSON.parse(http.responseText).result; var result = JSON.parse(http.responseText).result;
if (__resolutions != result.resolutions) { if (__quality != result.quality) {
tools.info("Resolutions list changed:", result.resolutions); tools.info("Quality changed:", result.quality);
$("stream-resolution-select").innerHTML = ""; document.querySelector("#stream-quality-select [value=\"" + result.quality + "\"]").selected = true;
result.resolutions.forEach(function(resolution) { __quality = result.quality;
$("stream-resolution-select").innerHTML += "<option value=\"" + resolution + "\">" + resolution + "</option>";
});
$("stream-resolution-select").disabled = (result.resolutions.length == 1);
__resolutions = result.resolutions;
} }
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(); __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 _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 make _pikvm PIKVM_PLATFORM=v1 PI_BOARD=rpi-2
v2:
make _pikvm PIKVM_PLATFORM=v2 PI_BOARD=rpi-2
shell: shell:
cd $(_BUILD_DIR) && make shell cd $(_BUILD_DIR) && make shell
_pikvm: $(_BUILD_DIR) _pikvm: $(_BUILD_DIR)
rm -rf $(_BUILD_DIR)/stages/pikvm rm -rf $(_BUILD_DIR)/stages/pikvm-*
rm -rf $(_BUILD_DIR)/builder/scripts/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 \ cd $(_BUILD_DIR) && make binfmt && make _rpi \
BUILD_OPTS=" $(BUILD_OPTS) \ 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 NEW_SSH_KEYGEN=$(shell uuidgen) \
--build-arg WEBUI_ADMIN_PASSWD='$(WEBUI_ADMIN_PASSWD)' \ --build-arg WEBUI_ADMIN_PASSWD='$(WEBUI_ADMIN_PASSWD)' \
--build-arg NEW_HTTPS_CERT=$(shell uuidgen) \ --build-arg NEW_HTTPS_CERT=$(shell uuidgen) \
" \ " \
PROJECT=pikvm \ PROJECT=pikvm \
BOARD=$(PI_BOARD) \ 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) \ LOCALE=$(LOCALE) \
TIMEZONE=$(TIMEZONE) 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 \ COPY stages/pikvm-v1/config.txt /boot/
nginx-mainline \ COPY stages/pikvm-v1/udev.rules /etc/udev/rules.d/pikvm.rules
apache-tools \
mjpg-streamer \
python \
python-raspberry-gpio \
customizepkg
RUN systemctl enable nginx
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 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"