mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Compare commits
57 Commits
v2.7.2
...
split-cont
Author | SHA1 | Date | |
---|---|---|---|
e0245b86f3 | |||
3c4d7b9d60 | |||
7a9b33706a | |||
052a961fd9 | |||
8ef9c1aff5 | |||
6ed3493aa0 | |||
5959d056f3 | |||
92ad202bfe | |||
cd4acb5eec | |||
a32be0b44a | |||
b2080649ea | |||
851c38b8fd | |||
e2336be568 | |||
e417ec5dbe | |||
ad7e1f2b7b | |||
91e1a8b502 | |||
9bdf9c8851 | |||
c1360d3abc | |||
950095d6d8 | |||
009bc20969 | |||
395db0fd54 | |||
13fa86d543 | |||
8308c13382 | |||
ec175909a3 | |||
d2f51fa10f | |||
70325e0277 | |||
bdff0841ec | |||
3c17dbe282 | |||
887413d536 | |||
f9228e653d | |||
df634be1c5 | |||
2130daa02d | |||
aee7650d47 | |||
98ba32c574 | |||
d08d3eccef | |||
0dd9597519 | |||
334fcef407 | |||
e868ad4061 | |||
b41d0bf956 | |||
b62fa6ab8b | |||
8dba9cff44 | |||
217cc451ea | |||
1f81bd3efc | |||
50e5483661 | |||
d2765c30fd | |||
76fc892823 | |||
ea99ce7753 | |||
646eaced29 | |||
9104953ad9 | |||
c40243635f | |||
8059002475 | |||
9daf83cc52 | |||
0cebe465a2 | |||
d9403d9c14 | |||
8604a30744 | |||
957e893cf6 | |||
55005a6f8d |
@ -27,7 +27,7 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
@ -79,7 +79,9 @@ RUN set -eux; \
|
||||
# Japanese fonts
|
||||
fonts-takao-mincho \
|
||||
# Chinese fonts
|
||||
fonts-wqy-zenhei; \
|
||||
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||
# Korean fonts
|
||||
fonts-wqy-microhei; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
@ -88,19 +90,18 @@ RUN set -eux; \
|
||||
adduser $USERNAME video; \
|
||||
adduser $USERNAME pulse; \
|
||||
#
|
||||
# setup pulseaudio
|
||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
||||
#
|
||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||
mkdir /tmp/.X11-unix; \
|
||||
chmod 1777 /tmp/.X11-unix; \
|
||||
chown $USERNAME /tmp/.X11-unix/; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
||||
mkdir -p /etc/neko /var/www /var/log/neko \
|
||||
/tmp/runtime-$USERNAME \
|
||||
/home/$USERNAME/.config/pulse \
|
||||
/home/$USERNAME/.local/share/xorg; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown $USERNAME /var/log/neko/; \
|
||||
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
@ -118,6 +119,8 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
ENV DISPLAY=:99.0
|
||||
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
|
@ -27,29 +27,32 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:18-bullseye-slim as client
|
||||
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends python2 build-essential
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
COPY client/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Because client builds fail in Github Actions, therefor we build it outside of Docker.
|
||||
#
|
||||
# FROM node:18-bullseye-slim as client
|
||||
#
|
||||
# # install dependencies
|
||||
# RUN set -eux; apt-get update; \
|
||||
# apt-get install -y --no-install-recommends python2 build-essential
|
||||
#
|
||||
# WORKDIR /src
|
||||
#
|
||||
# #
|
||||
# # install dependencies
|
||||
# COPY client/package*.json ./
|
||||
# RUN npm install
|
||||
#
|
||||
# #
|
||||
# # build client
|
||||
# COPY client/ .
|
||||
# RUN npm run build
|
||||
#
|
||||
# build client
|
||||
COPY client/ .
|
||||
RUN npm run build
|
||||
|
||||
#
|
||||
# STAGE 3: RUNTIME
|
||||
#
|
||||
@ -85,7 +88,9 @@ RUN set -eux; \
|
||||
# Japanese fonts
|
||||
fonts-takao-mincho \
|
||||
# Chinese fonts
|
||||
fonts-wqy-zenhei; \
|
||||
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||
# Korean fonts
|
||||
fonts-wqy-microhei; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
@ -94,19 +99,18 @@ RUN set -eux; \
|
||||
adduser $USERNAME video; \
|
||||
adduser $USERNAME pulse; \
|
||||
#
|
||||
# setup pulseaudio
|
||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
||||
#
|
||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||
mkdir /tmp/.X11-unix; \
|
||||
chmod 1777 /tmp/.X11-unix; \
|
||||
chown $USERNAME /tmp/.X11-unix/; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
||||
mkdir -p /etc/neko /var/www /var/log/neko \
|
||||
/tmp/runtime-$USERNAME \
|
||||
/home/$USERNAME/.config/pulse \
|
||||
/home/$USERNAME/.local/share/xorg; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown $USERNAME /var/log/neko/; \
|
||||
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
@ -124,6 +128,8 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
ENV DISPLAY=:99.0
|
||||
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
@ -131,7 +137,8 @@ ENV NEKO_BIND=:8080
|
||||
#
|
||||
# copy static files from previous stages
|
||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||
COPY --from=client /src/dist/ /var/www
|
||||
# COPY --from=client /src/dist/ /var/www
|
||||
COPY client/dist/ /var/www
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
|
||||
|
@ -27,7 +27,7 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
@ -88,7 +88,9 @@ RUN set -eux; \
|
||||
# Japanese fonts
|
||||
fonts-takao-mincho \
|
||||
# Chinese fonts
|
||||
fonts-wqy-zenhei; \
|
||||
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||
# Korean fonts
|
||||
fonts-wqy-microhei; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
@ -97,19 +99,18 @@ RUN set -eux; \
|
||||
adduser $USERNAME video; \
|
||||
adduser $USERNAME pulse; \
|
||||
#
|
||||
# setup pulseaudio
|
||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
||||
#
|
||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||
mkdir /tmp/.X11-unix; \
|
||||
chmod 1777 /tmp/.X11-unix; \
|
||||
chown $USERNAME /tmp/.X11-unix/; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
||||
mkdir -p /etc/neko /var/www /var/log/neko \
|
||||
/tmp/runtime-$USERNAME \
|
||||
/home/$USERNAME/.config/pulse \
|
||||
/home/$USERNAME/.local/share/xorg; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown $USERNAME /var/log/neko/; \
|
||||
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
@ -128,9 +129,12 @@ COPY .docker/base/intel/add-render-group.sh /usr/bin/add-render-group.sh
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
ENV DISPLAY=:99.0
|
||||
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
ENV NEKO_HWENC=VAAPI
|
||||
ENV RENDER_GID=
|
||||
|
||||
#
|
||||
|
294
.docker/base/Dockerfile.nvidia
Normal file
294
.docker/base/Dockerfile.nvidia
Normal file
@ -0,0 +1,294 @@
|
||||
ARG UBUNTU_RELEASE=20.04
|
||||
ARG CUDA_VERSION=11.4.3
|
||||
ARG VIRTUALGL_VERSION=3.1
|
||||
ARG GSTREAMER_VERSION=1.20
|
||||
|
||||
#
|
||||
# STAGE 0: Build gstreamer with nvidia plugins.
|
||||
#
|
||||
FROM ubuntu:${UBUNTU_RELEASE} AS gstreamer
|
||||
ARG GSTREAMER_VERSION
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
# Install essentials
|
||||
curl build-essential ca-certificates git \
|
||||
# Install pip and ninja
|
||||
python3-pip python-gi-dev ninja-build \
|
||||
# Install build deps
|
||||
autopoint autoconf automake autotools-dev libtool gettext bison flex gtk-doc-tools \
|
||||
# Install libraries
|
||||
librtmp-dev \
|
||||
libvo-aacenc-dev \
|
||||
libtool-bin \
|
||||
libgtk2.0-dev \
|
||||
libgl1-mesa-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
libssl-dev \
|
||||
libx264-dev \
|
||||
libvpx-dev; \
|
||||
# Install meson
|
||||
pip3 install meson; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# build gstreamer
|
||||
RUN set -eux; \
|
||||
git clone --depth 1 --branch $GSTREAMER_VERSION https://gitlab.freedesktop.org/gstreamer/gstreamer.git /gstreamer/src; \
|
||||
cd /gstreamer/src; \
|
||||
mkdir -p /opt/gstreamer; \
|
||||
meson --prefix /opt/gstreamer \
|
||||
-Dgpl=enabled \
|
||||
-Dugly=enabled \
|
||||
-Dgst-plugins-ugly:x264=enabled \
|
||||
build; \
|
||||
ninja -C build; \
|
||||
meson install -C build;
|
||||
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends git cmake make libx11-dev libxrandr-dev libxtst-dev \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly; \
|
||||
#
|
||||
# install libclipboard
|
||||
set -eux; \
|
||||
cd /tmp; \
|
||||
git clone --depth=1 https://github.com/jtanx/libclipboard; \
|
||||
cd libclipboard; \
|
||||
cmake .; \
|
||||
make -j4; \
|
||||
make install; \
|
||||
rm -rf /tmp/libclipboard; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:18-bullseye-slim as client
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
COPY client/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
#
|
||||
# build client
|
||||
COPY client/ .
|
||||
RUN npm run build
|
||||
|
||||
#
|
||||
# STAGE 3: RUNTIME
|
||||
#
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_RELEASE} as runtime
|
||||
|
||||
ARG UBUNTU_RELEASE
|
||||
ARG VIRTUALGL_VERSION
|
||||
|
||||
# Make all NVIDIA GPUs visible by default
|
||||
ENV NVIDIA_VISIBLE_DEVICES all
|
||||
# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES all
|
||||
|
||||
#
|
||||
# set vgl-display to headless 3d gpu card/// correct values are egl[n] or /dev/dri/card0:if this is passed into container
|
||||
ENV VGL_DISPLAY egl
|
||||
|
||||
#
|
||||
# avoid warnings by switching to noninteractive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
#
|
||||
# set custom user
|
||||
ARG USERNAME=neko
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
RUN set -eux; \
|
||||
dpkg --add-architecture i386; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
# opengl base: https://gitlab.com/nvidia/container-images/opengl/-/blob/ubuntu20.04/base/Dockerfile
|
||||
libxau6 libxau6:i386 \
|
||||
libxdmcp6 libxdmcp6:i386 \
|
||||
libxcb1 libxcb1:i386 \
|
||||
libxext6 libxext6:i386 \
|
||||
libx11-6 libx11-6:i386 \
|
||||
# opengl runtime: https://gitlab.com/nvidia/container-images/opengl/-/blob/ubuntu20.04/glvnd/runtime/Dockerfile
|
||||
libglvnd0 libglvnd0:i386 \
|
||||
libgl1 libgl1:i386 \
|
||||
libglx0 libglx0:i386 \
|
||||
libegl1 libegl1:i386 \
|
||||
libgles2 libgles2:i386 \
|
||||
# hardware accleration utilities
|
||||
libglu1 libglu1:i386 \
|
||||
libvulkan-dev libvulkan-dev:i386 \
|
||||
mesa-utils mesa-utils-extra \
|
||||
mesa-va-drivers mesa-vulkan-drivers \
|
||||
vainfo vdpauinfo; \
|
||||
#
|
||||
# install vulkan-utils or vulkan-tools depending on ubuntu release
|
||||
if [ "${UBUNTU_RELEASE}" = "18.04" ]; then \
|
||||
apt-get install -y --no-install-recommends vulkan-utils; \
|
||||
else \
|
||||
apt-get install -y --no-install-recommends vulkan-tools; \
|
||||
fi; \
|
||||
#
|
||||
# create symlink for libnvrtc.so (needed for cudaconvert)
|
||||
find /usr/local/cuda/lib64/ -maxdepth 1 -type l -name "*libnvrtc.so.*" -exec sh -c 'ln -sf {} /usr/local/cuda/lib64/libnvrtc.so' \;; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# add cuda to ld path, for gstreamer cuda plugins
|
||||
ENV LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}:/usr/local/cuda/lib:/usr/local/cuda/lib64"
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
#
|
||||
# install dependencies
|
||||
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
|
||||
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
|
||||
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6 libx264-155 libvo-aacenc0 librtmp1; \
|
||||
apt-get install -y --no-install-recommends libgtk-3-bin software-properties-common cabextract aptitude vim curl; \
|
||||
#
|
||||
# install fonts
|
||||
apt-get install -y --no-install-recommends \
|
||||
# Google emojis
|
||||
fonts-noto-color-emoji \
|
||||
# Japanese fonts
|
||||
fonts-takao-mincho \
|
||||
# Chinese fonts
|
||||
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||
# Korean fonts
|
||||
fonts-wqy-microhei; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
|
||||
adduser $USERNAME audio; \
|
||||
adduser $USERNAME video; \
|
||||
adduser $USERNAME pulse; \
|
||||
#
|
||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||
mkdir /tmp/.X11-unix; \
|
||||
chmod 1777 /tmp/.X11-unix; \
|
||||
chown $USERNAME /tmp/.X11-unix/; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko \
|
||||
/tmp/runtime-$USERNAME \
|
||||
/home/$USERNAME/.config/pulse \
|
||||
/home/$USERNAME/.local/share/xorg; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# configure EGL and Vulkan manually
|
||||
RUN VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)') && \
|
||||
# Configure EGL manually
|
||||
mkdir -p /usr/share/glvnd/egl_vendor.d/ && \
|
||||
echo "{\n\
|
||||
\"file_format_version\" : \"1.0.0\",\n\
|
||||
\"ICD\": {\n\
|
||||
\"library_path\": \"libEGL_nvidia.so.0\"\n\
|
||||
}\n\
|
||||
}" > /usr/share/glvnd/egl_vendor.d/10_nvidia.json && \
|
||||
# Configure Vulkan manually
|
||||
mkdir -p /etc/vulkan/icd.d/ && \
|
||||
echo "{\n\
|
||||
\"file_format_version\" : \"1.0.0\",\n\
|
||||
\"ICD\": {\n\
|
||||
\"library_path\": \"libGLX_nvidia.so.0\",\n\
|
||||
\"api_version\" : \"${VULKAN_API_VERSION}\"\n\
|
||||
}\n\
|
||||
}" > /etc/vulkan/icd.d/nvidia_icd.json
|
||||
|
||||
#
|
||||
# install VirtualGL and make libraries available for preload
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \
|
||||
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \
|
||||
apt-get install -y --no-install-recommends ./virtualgl_${VIRTUALGL_VERSION}_amd64.deb ./virtualgl32_${VIRTUALGL_VERSION}_amd64.deb; \
|
||||
rm -f "virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \
|
||||
chmod u+s /usr/lib/libvglfaker.so; \
|
||||
chmod u+s /usr/lib/libdlfaker.so; \
|
||||
chmod u+s /usr/lib32/libvglfaker.so; \
|
||||
chmod u+s /usr/lib32/libdlfaker.so; \
|
||||
chmod u+s /usr/lib/i386-linux-gnu/libvglfaker.so; \
|
||||
chmod u+s /usr/lib/i386-linux-gnu/libdlfaker.so; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*;
|
||||
|
||||
#
|
||||
# copy config files
|
||||
COPY .docker/base/dbus /usr/bin/dbus
|
||||
COPY .docker/base/default.pa /etc/pulse/default.pa
|
||||
COPY .docker/base/supervisord.conf /etc/neko/supervisord.conf
|
||||
COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
||||
COPY .docker/base/nvidia/entrypoint.sh /bin/entrypoint.sh
|
||||
|
||||
#
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
ENV DISPLAY=:99.0
|
||||
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
|
||||
#
|
||||
# set gstreamer envs
|
||||
ENV PATH="/opt/gstreamer/bin:${PATH}"
|
||||
ENV LD_LIBRARY_PATH="/opt/gstreamer/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
|
||||
ENV PKG_CONFIG_PATH="/opt/gstreamer/lib/x86_64-linux-gnu/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
|
||||
|
||||
#
|
||||
# copy gstreamer from previous stage
|
||||
COPY --from=gstreamer /opt/gstreamer /opt/gstreamer
|
||||
|
||||
#
|
||||
# copy static files from previous stages
|
||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||
COPY --from=client /src/dist/ /var/www
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
|
||||
|
||||
#
|
||||
# run neko
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]
|
@ -1,5 +1,8 @@
|
||||
#!/usr/bin/pulseaudio -nF
|
||||
|
||||
### Create virtual output device sink
|
||||
load-module module-null-sink sink_name=audio_output sink_properties=device.description="Virtual\ Audio\ Output"
|
||||
|
||||
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
|
||||
load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1
|
||||
|
||||
|
12
.docker/base/nvidia/entrypoint.sh
Executable file
12
.docker/base/nvidia/entrypoint.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# Add VirtualGL directories to path
|
||||
export PATH="${PATH}:/opt/VirtualGL/bin"
|
||||
|
||||
# Use VirtualGL to run wine with OpenGL if the GPU is available, otherwise use barebone wine
|
||||
if [ -n "$(nvidia-smi --query-gpu=uuid --format=csv | sed -n 2p)" ]; then
|
||||
exec vglrun "$@"
|
||||
else
|
||||
echo "No GPU detected"
|
||||
exec "$@"
|
||||
fi
|
@ -54,3 +54,14 @@ stdout_logfile=/var/log/neko/neko.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[unix_http_server]
|
||||
file=/var/run/supervisor.sock
|
||||
chmod=0700
|
||||
chown=root:root
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///var/run/supervisor.sock
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
23
.docker/brave/Dockerfile.nvidia
Normal file
23
.docker/brave/Dockerfile.nvidia
Normal file
@ -0,0 +1,23 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends apt-transport-https curl openbox; \
|
||||
#
|
||||
# install brave browser
|
||||
curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg; \
|
||||
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" \
|
||||
| tee /etc/apt/sources.list.d/brave-browser-release.list; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends brave-browser; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/brave.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/brave/Default/Preferences
|
||||
COPY policies.json /etc/brave/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:brave]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/brave-browser --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/brave --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/brave-browser
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/brave
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/brave/supervisord.nvidia.conf
Normal file
36
.docker/brave/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:brave]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/brave-browser
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/brave
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/brave.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
@ -92,6 +92,21 @@ build_intel() {
|
||||
fi
|
||||
}
|
||||
|
||||
build_nvidia() {
|
||||
if [ "$1" = "base" ]
|
||||
then
|
||||
# build nvidia base
|
||||
docker build -t "${BUILD_IMAGE}:nvidia-base" -f base/Dockerfile.nvidia "${BASE}"
|
||||
elif [ -f "$1/Dockerfile.nvidia" ]
|
||||
then
|
||||
# build dedicated nvidia image
|
||||
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile.nvidia" "$1/"
|
||||
else
|
||||
# try to build nvidia image with common Dockerfile
|
||||
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile" "$1/"
|
||||
fi
|
||||
}
|
||||
|
||||
case $1 in
|
||||
client) build_client;;
|
||||
server) build_server;;
|
||||
@ -102,6 +117,9 @@ case $1 in
|
||||
# build intel- images
|
||||
intel-*) build_intel "${1#intel-}";;
|
||||
|
||||
# build nvidia- images
|
||||
nvidia-*) build_nvidia "${1#nvidia-}";;
|
||||
|
||||
# build images
|
||||
*) build "$1";;
|
||||
esac
|
||||
|
37
.docker/chromium/Dockerfile.nvidia
Normal file
37
.docker/chromium/Dockerfile.nvidia
Normal file
@ -0,0 +1,37 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
#
|
||||
# install neko chromium
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends software-properties-common; \
|
||||
# chromium-browser from default repo needs snap to be installed
|
||||
# and nvidia base is ubuntu not debian
|
||||
add-apt-repository ppa:system76/pop; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends unzip chromium openbox; \
|
||||
#
|
||||
# install widevine module
|
||||
CHROMIUM_DIR="/usr/lib/chromium"; \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
||||
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
|
||||
mkdir -p "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64"; \
|
||||
unzip -p /tmp/widevine.zip LICENSE.txt > "${CHROMIUM_DIR}/WidevineCdm/LICENSE"; \
|
||||
unzip -p /tmp/widevine.zip manifest.json > "${CHROMIUM_DIR}/WidevineCdm/manifest.json"; \
|
||||
unzip -p /tmp/widevine.zip libwidevinecdm.so > "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64/libwidevinecdm.so"; \
|
||||
find "${CHROMIUM_DIR}/WidevineCdm" -type d -exec chmod 0755 '{}' \;; \
|
||||
find "${CHROMIUM_DIR}/WidevineCdm" -type f -exec chmod 0644 '{}' \;; \
|
||||
rm /tmp/widevine.zip; \
|
||||
#
|
||||
# clean up
|
||||
apt-get --purge autoremove -y unzip; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/chromium.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/chromium/Default/Preferences
|
||||
COPY policies.json /etc/chromium/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:chromium]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/chromium --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/chromium --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/chromium
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/chromium
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/chromium/supervisord.nvidia.conf
Normal file
36
.docker/chromium/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:chromium]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/chromium
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/chromium
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/chromium.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
35
.docker/firefox/Dockerfile.nvidia
Normal file
35
.docker/firefox/Dockerfile.nvidia
Normal file
@ -0,0 +1,35 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
ARG SRC_URL="https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US"
|
||||
|
||||
#
|
||||
# install firefox
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends openbox \
|
||||
xz-utils bzip2 libgtk-3-0 libdbus-glib-1-2; \
|
||||
#
|
||||
# fetch latest release
|
||||
wget -O /tmp/firefox-setup.tar.bz2 "${SRC_URL}"; \
|
||||
mkdir /usr/lib/firefox; \
|
||||
tar -xjf /tmp/firefox-setup.tar.bz2 -C /usr/lib; \
|
||||
rm -f /tmp/firefox-setup.tar.bz2; \
|
||||
ln -s /usr/lib/firefox/firefox /usr/bin/firefox; \
|
||||
#
|
||||
# create a profile directory
|
||||
mkdir -p /home/neko/.mozilla/firefox/profile.default/extensions; \
|
||||
chown -R neko:neko /home/neko/.mozilla/firefox/profile.default; \
|
||||
#
|
||||
# clean up
|
||||
apt-get --purge autoremove -y xz-utils bzip2; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/firefox.conf
|
||||
COPY neko.js /usr/lib/firefox/mozilla.cfg
|
||||
COPY autoconfig.js /usr/lib/firefox/defaults/pref/autoconfig.js
|
||||
COPY policies.json /usr/lib/firefox/distribution/policies.json
|
||||
COPY --chown=neko profiles.ini /home/neko/.mozilla/firefox/profiles.ini
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
28
.docker/firefox/supervisord.nvidia.conf
Normal file
28
.docker/firefox/supervisord.nvidia.conf
Normal file
@ -0,0 +1,28 @@
|
||||
[program:firefox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/firefox
|
||||
--no-remote
|
||||
-P default
|
||||
--display=%(ENV_DISPLAY)s
|
||||
-setDefaultBrowser
|
||||
-width 1280
|
||||
-height 720
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/firefox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
23
.docker/google-chrome/Dockerfile.nvidia
Normal file
23
.docker/google-chrome/Dockerfile.nvidia
Normal file
@ -0,0 +1,23 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
# latest working version with EGL: 111.0.5563.146, revert when resolved
|
||||
# 112.0.5615.49 fails: https://github.com/VirtualGL/virtualgl/issues/229
|
||||
ARG SRC_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_111.0.5563.146-1_amd64.deb"
|
||||
|
||||
#
|
||||
# install google chrome
|
||||
RUN set -eux; apt-get update; \
|
||||
wget -O /tmp/google-chrome.deb "${SRC_URL}"; \
|
||||
apt-get install -y --no-install-recommends openbox /tmp/google-chrome.deb; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/google-chrome.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/google-chrome/Default/Preferences
|
||||
COPY policies.json /etc/opt/chrome/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:google-chrome]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/google-chrome --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/google-chrome --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/google-chrome
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/google-chrome
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/google-chrome/supervisord.nvidia.conf
Normal file
36
.docker/google-chrome/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:google-chrome]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/google-chrome
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/chromium
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/google-chrome.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
@ -15,6 +15,11 @@ RUN set -eux; apt-get update; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# disable autolock
|
||||
RUN kwriteconfig5 --file /home/neko/.config/kscreenlockerrc --group Daemon --key Autolock false; \
|
||||
chown neko:neko /home/neko/.config/kscreenlockerrc
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.conf /etc/neko/supervisord/kde.conf
|
||||
|
24
.docker/microsoft-edge/Dockerfile.nvidia
Normal file
24
.docker/microsoft-edge/Dockerfile.nvidia
Normal file
@ -0,0 +1,24 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
ARG API_URL="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/"
|
||||
|
||||
#
|
||||
# install microsoft edge
|
||||
RUN set -eux; apt-get update; \
|
||||
#
|
||||
# fetch latest release
|
||||
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | tail -1)"; \
|
||||
wget -O /tmp/microsoft-edge.deb "${SRC_URL}"; \
|
||||
apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/microsoft-edge.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/microsoft-edge/Default/Preferences
|
||||
COPY policies.json /etc/opt/edge/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:microsoft-edge]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/microsoft-edge --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/microsoft-edge --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/microsoft-edge
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/microsoft-edge
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/microsoft-edge/supervisord.nvidia.conf
Normal file
36
.docker/microsoft-edge/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:microsoft-edge]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/microsoft-edge
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/microsoft-edge
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/microsoft-edge.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
@ -17,5 +17,9 @@ fi
|
||||
|
||||
docker run --rm -it \
|
||||
-v "${PWD}/../server:/src" \
|
||||
--entrypoint="go" \
|
||||
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"
|
||||
-e GIT_COMMIT=`git rev-parse --short HEAD` \
|
||||
-e GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD` \
|
||||
-e GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"` \
|
||||
--entrypoint="bash" \
|
||||
--workdir="/src" \
|
||||
neko_dev_server ./build
|
||||
|
@ -16,7 +16,7 @@ if [ ! -d "${PWD}/../client/node_modules" ] || [ "$1" == "-i" ]; then
|
||||
-v "${PWD}/../client:/app" \
|
||||
--workdir="/app" \
|
||||
--entrypoint="npm" \
|
||||
node:14-buster-slim install
|
||||
node:18-bullseye-slim install
|
||||
fi
|
||||
|
||||
docker run --rm -it \
|
||||
@ -25,5 +25,5 @@ docker run --rm -it \
|
||||
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
|
||||
--workdir="/app" \
|
||||
--entrypoint="npm" \
|
||||
node:14-buster-slim run serve
|
||||
node:18-bullseye-slim run serve
|
||||
|
@ -17,8 +17,22 @@ if [ ! -f "${BINARY_PATH}" ] || [ "$1" == "-r" ]; then
|
||||
./rebuild-server
|
||||
fi
|
||||
|
||||
# if image starts with nvidia- add --gpus all
|
||||
if [[ "${SERVER_TAG}" == "nvidia-"* ]]; then
|
||||
GPU_FLAG="--gpus all"
|
||||
echo "Nvidia GPU acceleration enabled"
|
||||
fi
|
||||
|
||||
# if image starts with intel- add --device /dev/dri
|
||||
if [[ "${SERVER_TAG}" == "intel-"* ]]; then
|
||||
GPU_FLAG="--device /dev/dri"
|
||||
echo "Intel GPU acceleration enabled"
|
||||
fi
|
||||
|
||||
# use --gpus all to enable GPU acceleration
|
||||
docker run --rm -it \
|
||||
--name "neko_dev" \
|
||||
$GPU_FLAG \
|
||||
-p "${SERVER_PORT}:8080" \
|
||||
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
|
||||
-e "NEKO_SCREEN=1920x1080@60" \
|
||||
|
37
.github/workflows/ghcr-arm.yml
vendored
37
.github/workflows/ghcr-arm.yml
vendored
@ -13,7 +13,7 @@ env:
|
||||
PLATFORMS: linux/arm64,linux/arm/v7
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
build-client:
|
||||
runs-on: ubuntu-latest
|
||||
#
|
||||
# do not run on forks
|
||||
@ -23,6 +23,41 @@ jobs:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
-
|
||||
name: Build client
|
||||
run: |
|
||||
cd client
|
||||
npm install
|
||||
npm run build
|
||||
-
|
||||
name: Upload client dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: client-dist
|
||||
path: client/dist
|
||||
|
||||
build-base:
|
||||
runs-on: ubuntu-latest
|
||||
#
|
||||
# do not run on forks
|
||||
#
|
||||
if: github.repository_owner == 'm1k1o'
|
||||
needs: [ build-client ]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Download client dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: client-dist
|
||||
path: client/dist
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
124
.github/workflows/ghcr-nvidia.yml
vendored
Normal file
124
.github/workflows/ghcr-nvidia.yml
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
name: "nvidia gpu supported images"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: m1k1o/neko
|
||||
TAG_PREFIX: nvidia-
|
||||
BASE_DOCKERFILE: Dockerfile.nvidia
|
||||
PLATFORMS: linux/amd64
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
runs-on: ubuntu-latest
|
||||
#
|
||||
# do not run on forks
|
||||
#
|
||||
if: github.repository_owner == 'm1k1o'
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Extract metadata (tags, labels) for Docker
|
||||
uses: docker/metadata-action@v3
|
||||
id: meta
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,format=long
|
||||
-
|
||||
name: Log in to the Container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GHCR_ACCESS_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./
|
||||
file: .docker/base/${{ env.BASE_DOCKERFILE }}
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
#
|
||||
# do not run on forks
|
||||
#
|
||||
if: github.repository_owner == 'm1k1o'
|
||||
needs: [ build-base ]
|
||||
strategy:
|
||||
# Will build all images even if some fail.
|
||||
matrix:
|
||||
include:
|
||||
- tag: firefox
|
||||
dockerfile: Dockerfile.nvidia
|
||||
- tag: brave
|
||||
dockerfile: Dockerfile.nvidia
|
||||
- tag: chromium
|
||||
dockerfile: Dockerfile.nvidia
|
||||
- tag: google-chrome
|
||||
dockerfile: Dockerfile.nvidia
|
||||
- tag: microsoft-edge
|
||||
dockerfile: Dockerfile.nvidia
|
||||
env:
|
||||
TAG_NAME: ${{ matrix.tag }}
|
||||
DOCKERFILE: ${{ matrix.dockerfile }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Extract metadata (tags, labels) for Docker
|
||||
uses: docker/metadata-action@v3
|
||||
id: meta
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}${{ env.TAG_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,format=long
|
||||
-
|
||||
name: Log in to the Container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GHCR_ACCESS_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .docker/${{ env.TAG_NAME }}
|
||||
file: .docker/${{ env.TAG_NAME }}/${{ env.DOCKERFILE }}
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base:sha-${{ github.sha }}
|
4
LICENSE
4
LICENSE
@ -186,7 +186,9 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020 Nurdism <nurdism.io@gmail.com>, 2020-2021 m1k1o
|
||||
Copyright (C) 2020 Nurdism <nurdism.io@gmail.com>
|
||||
Copyright (C) 2020-2023 m1k1o
|
||||
All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -1,17 +1,22 @@
|
||||
<template>
|
||||
<div id="neko" :class="[side ? 'expanded' : '']">
|
||||
<div id="neko" :class="[!videoOnly && side ? 'expanded' : '']">
|
||||
<template v-if="!$client.supported">
|
||||
<neko-unsupported />
|
||||
</template>
|
||||
<template v-else>
|
||||
<main class="neko-main">
|
||||
<div v-if="!hideControls" class="header-container">
|
||||
<div v-if="!videoOnly" class="header-container">
|
||||
<neko-header />
|
||||
</div>
|
||||
<div class="video-container">
|
||||
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
|
||||
<neko-video
|
||||
ref="video"
|
||||
:hideControls="hideControls"
|
||||
:extraControls="isEmbedMode"
|
||||
@control-attempt="controlAttempt"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!hideControls" class="room-container">
|
||||
<div v-if="!videoOnly" class="room-container">
|
||||
<neko-members />
|
||||
<div class="room-menu">
|
||||
<div class="settings">
|
||||
@ -26,11 +31,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<neko-side v-if="!hideControls && side" />
|
||||
<neko-side v-if="!videoOnly && side" />
|
||||
<neko-connect v-if="!connected" />
|
||||
<neko-about v-if="about" />
|
||||
<notifications
|
||||
v-if="!hideControls"
|
||||
v-if="!videoOnly"
|
||||
group="neko"
|
||||
position="top left"
|
||||
style="top: 50px; pointer-events: none"
|
||||
@ -176,10 +181,32 @@
|
||||
|
||||
shakeKbd = false
|
||||
|
||||
get hideControls() {
|
||||
get volume() {
|
||||
const numberParam = parseFloat(new URL(location.href).searchParams.get('volume') || '1.0')
|
||||
return Math.max(0.0, Math.min(!isNaN(numberParam) ? numberParam * 100 : 100, 100))
|
||||
}
|
||||
|
||||
get isCastMode() {
|
||||
return !!new URL(location.href).searchParams.get('cast')
|
||||
}
|
||||
|
||||
get isEmbedMode() {
|
||||
return !!new URL(location.href).searchParams.get('embed')
|
||||
}
|
||||
|
||||
get hideControls() {
|
||||
return this.isCastMode
|
||||
}
|
||||
|
||||
get videoOnly() {
|
||||
return this.isCastMode || this.isEmbedMode
|
||||
}
|
||||
|
||||
@Watch('volume', { immediate: true })
|
||||
onVolume(volume: number) {
|
||||
this.$accessor.video.setVolume(volume)
|
||||
}
|
||||
|
||||
@Watch('hideControls', { immediate: true })
|
||||
onHideControls(enabled: boolean) {
|
||||
if (enabled) {
|
||||
|
@ -76,5 +76,12 @@
|
||||
about() {
|
||||
this.$accessor.client.toggleAbout()
|
||||
}
|
||||
|
||||
mounted() {
|
||||
const default_lang = new URL(location.href).searchParams.get('lang')
|
||||
if (default_lang && this.langs.includes(default_lang)) {
|
||||
this.$i18n.locale = default_lang
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -21,26 +21,29 @@
|
||||
@mouseup.stop.prevent="onMouseUp"
|
||||
@mouseenter.stop.prevent="onMouseEnter"
|
||||
@mouseleave.stop.prevent="onMouseLeave"
|
||||
@touchmove.stop.prevent="onTouchHandler"
|
||||
@touchstart.stop.prevent="onTouchHandler"
|
||||
@touchend.stop.prevent="onTouchHandler"
|
||||
/>
|
||||
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="toggle">
|
||||
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="playAndUnmute">
|
||||
<i class="fas fa-play-circle" />
|
||||
</div>
|
||||
<div v-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
|
||||
<div v-else-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
|
||||
<i class="fas fa-volume-up" />
|
||||
</div>
|
||||
<div ref="aspect" class="player-aspect" />
|
||||
</div>
|
||||
<ul v-if="!fullscreen" class="video-menu top">
|
||||
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
|
||||
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
|
||||
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
|
||||
<li :class="hideControls || 'request-control'">
|
||||
<li v-if="!implicitHosting" :class="extraControls || 'extra-control'">
|
||||
<i
|
||||
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
||||
@click.stop.prevent="toggleControl"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="!fullscreen" class="video-menu bottom">
|
||||
<ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
|
||||
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
|
||||
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
|
||||
</li>
|
||||
@ -106,12 +109,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.request-control {
|
||||
/* usually extra controls are only shown on mobile */
|
||||
&.extra-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.request-control {
|
||||
&.extra-control {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@ -223,13 +226,15 @@
|
||||
@Ref('resolution') readonly _resolution!: Resolution
|
||||
@Ref('clipboard') readonly _clipboard!: Clipboard
|
||||
|
||||
// all controls are hidden (e.g. for cast mode)
|
||||
@Prop(Boolean) readonly hideControls!: boolean
|
||||
// extra controls are shown (e.g. for embed mode)
|
||||
@Prop(Boolean) readonly extraControls!: boolean
|
||||
|
||||
private keyboard = GuacamoleKeyboard()
|
||||
private observer = new ResizeObserver(this.onResize.bind(this))
|
||||
private focused = false
|
||||
private fullscreen = false
|
||||
private startsMuted = true
|
||||
private mutedOverlay = true
|
||||
|
||||
get admin() {
|
||||
@ -361,7 +366,6 @@
|
||||
onMutedChanged(muted: boolean) {
|
||||
if (this._video && this._video.muted != muted) {
|
||||
this._video.muted = muted
|
||||
this.startsMuted = muted
|
||||
|
||||
if (!muted) {
|
||||
this.mutedOverlay = false
|
||||
@ -384,9 +388,29 @@
|
||||
}
|
||||
|
||||
@Watch('playing')
|
||||
onPlayingChanged(playing: boolean) {
|
||||
async onPlayingChanged(playing: boolean) {
|
||||
if (this._video && this._video.paused && playing) {
|
||||
this.play()
|
||||
// if autoplay is disabled, play() will throw an error
|
||||
// and we need to properly save the state otherwise we
|
||||
// would be thinking we're playing when we're not
|
||||
try {
|
||||
await this._video.play()
|
||||
} catch (err: any) {
|
||||
if (!this._video.muted) {
|
||||
// video.play() can fail if audio is set due restrictive
|
||||
// browsers autoplay policy -> retry with muted audio
|
||||
try {
|
||||
this.$accessor.video.setMuted(true)
|
||||
this._video.muted = true
|
||||
await this._video.play()
|
||||
} catch (err: any) {
|
||||
// if it still fails, we're not playing anything
|
||||
this.$accessor.video.pause()
|
||||
}
|
||||
} else {
|
||||
this.$accessor.video.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._video && !this._video.paused && !playing) {
|
||||
@ -424,11 +448,6 @@
|
||||
this._video.addEventListener('canplaythrough', () => {
|
||||
this.$accessor.video.setPlayable(true)
|
||||
if (this.autoplay) {
|
||||
// start as muted due to restrictive browsers autoplay policy
|
||||
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
|
||||
this.$accessor.video.setMuted(true)
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$accessor.video.play()
|
||||
})
|
||||
@ -560,6 +579,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
playAndUnmute() {
|
||||
this.$accessor.video.play()
|
||||
this.$accessor.video.setMuted(false)
|
||||
}
|
||||
|
||||
unmute() {
|
||||
this.$accessor.video.setMuted(false)
|
||||
}
|
||||
@ -667,6 +691,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
onTouchHandler(e: TouchEvent) {
|
||||
let first = e.changedTouches[0]
|
||||
let type = ''
|
||||
switch (e.type) {
|
||||
case 'touchstart':
|
||||
type = 'mousedown'
|
||||
break
|
||||
case 'touchmove':
|
||||
type = 'mousemove'
|
||||
break
|
||||
case 'touchend':
|
||||
type = 'mouseup'
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
const simulatedEvent = new MouseEvent(type, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window,
|
||||
screenX: first.screenX,
|
||||
screenY: first.screenY,
|
||||
clientX: first.clientX,
|
||||
clientY: first.clientY,
|
||||
})
|
||||
first.target.dispatchEvent(simulatedEvent)
|
||||
}
|
||||
|
||||
onMouseDown(e: MouseEvent) {
|
||||
if (!this.hosting) {
|
||||
this.$emit('control-attempt', e)
|
||||
|
@ -203,11 +203,12 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
return
|
||||
}
|
||||
|
||||
this._peer = new RTCPeerConnection()
|
||||
if (lite !== true) {
|
||||
this._peer = new RTCPeerConnection({
|
||||
iceServers: servers,
|
||||
})
|
||||
} else {
|
||||
this._peer = new RTCPeerConnection()
|
||||
}
|
||||
|
||||
this._peer.onconnectionstatechange = () => {
|
||||
@ -251,11 +252,28 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
|
||||
this._peer.ontrack = this.onTrack.bind(this)
|
||||
|
||||
this._peer.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||
if (!event.candidate) {
|
||||
this.emit('debug', `sent all local ICE candidates`)
|
||||
return
|
||||
}
|
||||
|
||||
const init = event.candidate.toJSON()
|
||||
this.emit('debug', `sending local ICE candidate`, init)
|
||||
|
||||
this._ws!.send(
|
||||
JSON.stringify({
|
||||
event: EVENT.SIGNAL.CANDIDATE,
|
||||
data: JSON.stringify(init),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
this._peer.onnegotiationneeded = async () => {
|
||||
this.emit('warn', `negotiation is needed`)
|
||||
|
||||
const d = await this._peer!.createOffer()
|
||||
this._peer!.setLocalDescription(d)
|
||||
await this._peer!.setLocalDescription(d)
|
||||
|
||||
this._ws!.send(
|
||||
JSON.stringify({
|
||||
@ -277,10 +295,10 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
return
|
||||
}
|
||||
|
||||
this._peer.setRemoteDescription({ type: 'offer', sdp })
|
||||
await this._peer.setRemoteDescription({ type: 'offer', sdp })
|
||||
|
||||
for (const candidate of this._candidates) {
|
||||
this._peer.addIceCandidate(candidate)
|
||||
await this._peer.addIceCandidate(candidate)
|
||||
}
|
||||
this._candidates = []
|
||||
|
||||
@ -310,7 +328,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
return
|
||||
}
|
||||
|
||||
this._peer.setRemoteDescription({ type: 'answer', sdp })
|
||||
await this._peer.setRemoteDescription({ type: 'answer', sdp })
|
||||
}
|
||||
|
||||
private async onMessage(e: MessageEvent) {
|
||||
|
@ -6,5 +6,6 @@ Vue.use(VueI18n)
|
||||
|
||||
export const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages,
|
||||
})
|
||||
|
@ -2,19 +2,41 @@
|
||||
|
||||
## master branch
|
||||
|
||||
### New Features
|
||||
- Added nvidia support for firefox.
|
||||
- Added `?lang=<lang>` parameter to the URL, which will set the language of the interface (by @mbattista).
|
||||
|
||||
### Misc
|
||||
- Git commit and tag are now included in the build when creating a docker image.
|
||||
|
||||
## [n.eko v2.8.0](https://github.com/m1k1o/neko/releases/tag/v2.8.0)
|
||||
|
||||
### New Features
|
||||
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
|
||||
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
|
||||
- New VirtualGL version 3.1 was released, adding support for Chromium browsers to use Nvidia GPU acceleration!
|
||||
- Added `?embed=1` parameter to the URL, which will hide the sidebar and the top bar, so that it can be embedded in other websites.
|
||||
- Added `?volume=<0-1>` parameter to the URL, which will set the inital volume of the player (by @urbanekpj).
|
||||
- Touch events are now supported on mobile devices (by @urbanekpj).
|
||||
- Added NVENC support, hardware h264 encoding for Nvidia GPUs!
|
||||
- Fixed an issue where `nvh264enc` did not send SPS and PPS NAL units (by @mbattista).
|
||||
|
||||
### Bugs
|
||||
- Fixed TCP mux occasional freeze by adding write buffer to it.
|
||||
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
|
||||
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
|
||||
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
|
||||
- Fixed client side webrtc ICE gathering, so that neko can be used without exposed ports, only with STUN and TURN servers.
|
||||
- Fixed play state synchronization, when autoplay is disabled.
|
||||
|
||||
### Misc
|
||||
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
|
||||
- Added adaptive framerate which now streams in the framerate you selected from the dropdown.
|
||||
- Improved chinese and korean characters support.
|
||||
- Disabled autolock for kde, so that it does not lock the screen when you are not using it.
|
||||
- Refactored autoplay, so that it will start playing audio, if it's allowed by the browser (by @urbanekpj).
|
||||
- Renamed pulseaudio sink from `auto_null` to `audio_output`, because it was ignored by KDE.
|
||||
- Pulseaudio is now configured using environment variables, so that users can mount `/home/neko` without losing audio configuration.
|
||||
|
||||
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)
|
||||
|
||||
|
@ -50,7 +50,7 @@ All images are also available on [GitHub Container Registry](https://github.com/
|
||||
- `ghcr.io/m1k1o/neko/xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/kde:latest`
|
||||
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently, not all images are available for ARM, because not all applications are available for ARM.
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently, not all images are available for ARM, because not all applications are available for ARM. Please note, that `m1k1o/neko:arm-*` images from dockerhub are currently not maintained and they can contain outdated software. Please use images below:
|
||||
|
||||
- `ghcr.io/m1k1o/neko/arm-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-chromium:latest`
|
||||
@ -74,6 +74,14 @@ For images with VAAPI GPU hardware acceleration using intel drivers use:
|
||||
- `ghcr.io/m1k1o/neko/intel-xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-kde:latest`
|
||||
|
||||
For images with Nvidia GPU hardware acceleration using EGL (see example below) use (please note, there is a known issue with EGL and Chromium-based browsers, see [here](https://github.com/m1k1o/neko/issues/279)):
|
||||
|
||||
- `ghcr.io/m1k1o/neko/nvidia-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-google-chrome:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-microsoft-edge:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-brave:latest`
|
||||
|
||||
GHCR images are built using GitHub actions for every tag.
|
||||
|
||||
### Networking:
|
||||
@ -124,6 +132,17 @@ services:
|
||||
- UDP is generally better for latency. But some networks block UDP so it is good to have TCP available as fallback.
|
||||
- Still, using `NEKO_ICELITE=true` is recommended.
|
||||
|
||||
### Using turn servers instead of port forwarding
|
||||
|
||||
- If you don't want to use port forwarding, you can use turn servers.
|
||||
- But you need to have your own turn server (e.g. [cotrun](https://github.com/coturn/coturn)) or have access to one.
|
||||
- They are generally not free, because they require a lot of bandwidth.
|
||||
- Please make sure that you correctly escape your turn server credentials in the environment variable or use aphostrophes.
|
||||
|
||||
```yaml
|
||||
NEKO_ICESERVERS: '[{"urls": ["turn:<MY-COTURN-SERVER>:443?transport=udp", "turn:<MY-COTURN-SERVER>:443?transport=tcp", "turns:<MY-COTURN-SERVER>:443?transport=udp", "turns:<MY-COTURN-SERVER>:443?transport=tcp"], "credential": "<MY-COTURN-CREDENTIAL"}, {"urls": ["stun:stun.nextcloud.com:443"]}]'
|
||||
```
|
||||
|
||||
### Want to customize and install own add-ons, set custom bookmarks?
|
||||
- You would need to modify the existing policy file and mount it to your container.
|
||||
- For Firefox, copy [this](https://github.com/m1k1o/neko/blob/master/.docker/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/lib/firefox/distribution/policies.json'`
|
||||
@ -155,6 +174,69 @@ services:
|
||||
"RestoreOnStartup": 1,
|
||||
```
|
||||
|
||||
### Nvidia GPU acceleration
|
||||
|
||||
You need to have [nvidia-docker](https://github.com/NVIDIA/nvidia-docker) installed, start the container with `--gpus all` flag and use images built for nvidia (see above).
|
||||
|
||||
```bash
|
||||
docker run -d --gpus all \
|
||||
-p 8080:8080 \
|
||||
-p 56000-56100:56000-56100/udp \
|
||||
-e NEKO_SCREEN=1920x1080@30 \
|
||||
-e NEKO_PASSWORD=neko \
|
||||
-e NEKO_PASSWORD_ADMIN=admin \
|
||||
-e NEKO_EPR=56000-56100 \
|
||||
-e NEKO_NAT1TO1=192.168.1.10 \
|
||||
-e NEKO_ICELITE=1 \
|
||||
-e NEKO_VIDEO_CODEC=h264 \
|
||||
-e NEKO_HWENC=nvenc \
|
||||
--shm-size=2gb \
|
||||
--cap-add=SYS_ADMIN \
|
||||
--name neko \
|
||||
ghcr.io/m1k1o/neko/nvidia-google-chrome:latest
|
||||
```
|
||||
|
||||
If you want to use docker-compose, you can use this example:
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
services:
|
||||
neko:
|
||||
image: "ghcr.io/m1k1o/neko/nvidia-google-chrome:latest"
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "56000-56100:56000-56100/udp"
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
environment:
|
||||
NEKO_SCREEN: '1920x1080@30'
|
||||
NEKO_PASSWORD: neko
|
||||
NEKO_PASSWORD_ADMIN: admin
|
||||
NEKO_EPR: 56000-56100
|
||||
NEKO_NAT1TO1: 192.168.1.10
|
||||
NEKO_VIDEO_CODEC: h264
|
||||
NEKO_HWENC: nvenc
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
- You can verify that GPU is available inside the container by running `docker exec -it neko nvidia-smi` command.
|
||||
- You can verify that GPU is used for encoding by searching for `nvh264enc` in `docker logs neko` output.
|
||||
- If you don'ŧ specify `NEKO_HWENC: nvenc` environment variable, CPU encoding will be used but GPU will still be available for browser rendering.
|
||||
|
||||
Broadcast pipeline is not hardware accelerated by default. You can use this pipeline created by [@evilalmus](https://github.com/m1k1o/neko/issues/276#issuecomment-1498362533).
|
||||
|
||||
```yaml
|
||||
NEKO_BROADCAST_PIPELINE: "flvmux name=mux ! rtmpsink location={url} pulsesrc device={device} ! audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. ximagesrc display-name={display} show-pointer=false use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! nvh264enc name=encoder preset=low-latency-hq gop-size=25 spatial-aq=true temporal-aq=true bitrate=2800 vbv-buffer-size=2800 rc-mode=6 ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,profile=high ! h264parse ! mux."
|
||||
```
|
||||
|
||||
### Want to use VPN for your n.eko browsing?
|
||||
- Check this out: https://github.com/m1k1o/neko-vpn
|
||||
|
||||
@ -172,6 +254,9 @@ services:
|
||||
- Adding `?pwd=<password>` will prefill password.
|
||||
- Adding `?usr=<display-name>` will prefill username.
|
||||
- Adding `?cast=1` will hide all control and show only video.
|
||||
- Adding `?embed=1` will hide most additional components and show only video.
|
||||
- Adding `?volume=<0-1>` will set volume to given value.
|
||||
- Adding `?lang=<language>` will set language to given value.
|
||||
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
|
||||
|
||||
### Screen size
|
||||
|
@ -25,7 +25,7 @@ nat1to1: <ip>
|
||||
- Control protection means, users can gain control only if at least one admin is in the room.
|
||||
- e.g. `false`
|
||||
#### `NEKO_IMPLICIT_CONTROL`:
|
||||
- If enabled members can gain control implicitly, they don't needd to request control.
|
||||
- If enabled members can gain control implicitly, they don't need to request control.
|
||||
- e.g. `false`
|
||||
#### `NEKO_LOCKS`:
|
||||
- Resources, that will be locked when starting, separated by whitespace.
|
||||
@ -79,13 +79,14 @@ nat1to1: <ip>
|
||||
- `gstreamer1.0-plugins-good`
|
||||
- `gstreamer1.0-plugins-bad`
|
||||
- `gstreamer1.0-plugins-ugly`
|
||||
- e.g. `ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream`
|
||||
- e.g. `ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline`
|
||||
#### `NEKO_MAX_FPS`:
|
||||
- The resulting stream frames per seconds should be capped *(0 for uncapped)*.
|
||||
- e.g. `0`
|
||||
#### `NEKO_HWENC`:
|
||||
- Use hardware accelerated encoding, for now supported only `VAAPI`.
|
||||
- e.g. `VAAPI`
|
||||
- none *(default CPU encoding)*
|
||||
- vaapi
|
||||
- nvenc
|
||||
|
||||
### Audio
|
||||
|
||||
@ -167,7 +168,7 @@ Flags:
|
||||
--cert string path to the SSL cert used to secure the neko server
|
||||
--control_protection control protection means, users can gain control only if at least one admin is in the room
|
||||
--cors strings list of allowed origins for CORS (default [*])
|
||||
--device string audio device to capture (default "auto_null.monitor")
|
||||
--device string audio device to capture (default "audio_output.monitor")
|
||||
--display string XDisplay to capture (default ":99.0")
|
||||
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
|
||||
--file_transfer_enabled enable file transfer feature (default false)
|
||||
|
@ -72,7 +72,8 @@ services:
|
||||
version: "3.4"
|
||||
services:
|
||||
neko:
|
||||
image: "m1k1o/neko:arm-chromium"
|
||||
# see docs for more variants
|
||||
image: "ghcr.io/m1k1o/neko/arm-chromium:latest"
|
||||
restart: "unless-stopped"
|
||||
# increase on rpi's with more then 1gb ram.
|
||||
shm_size: "520mb"
|
||||
@ -95,9 +96,9 @@ services:
|
||||
! videoconvert
|
||||
! queue
|
||||
! video/x-raw,framerate=30/1,format=NV12
|
||||
! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;"
|
||||
! v4l2h264enc extra-controls="controls,h264_profile=1,video_bitrate=1250000;"
|
||||
! h264parse config-interval=3
|
||||
! video/x-h264,profile=baseline,stream-format=byte-stream
|
||||
! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
|
||||
NEKO_VIDEO_CODEC: h264
|
||||
```
|
||||
|
||||
|
@ -210,3 +210,17 @@ Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based bro
|
||||
```
|
||||
|
||||
This error originates from browser, that it could not connect to dbus. This does not affect us and can be ignored.
|
||||
|
||||
### Broadcast pipeline not working with some ingest servers
|
||||
|
||||
See [related issue](https://github.com/m1k1o/neko/issues/276).
|
||||
|
||||
```
|
||||
Could not connect to RTMP stream "'rtmp://<ingest-url>/live/<stream-key-removed> live=1'" for writing
|
||||
```
|
||||
|
||||
Some ingest servers require `live=1` parameter in the URL (e.g. nginx-rtmp-module). Some do not and do not accept aphostrophes (e.g. owncast). You can try to change the pipeline to:
|
||||
|
||||
```yaml
|
||||
NEKO_BROADCAST_PIPELINE: "flvmux name=mux ! rtmpsink location={url} pulsesrc device={device} ! audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. ximagesrc display-name={display} show-pointer=false use-damage=false ! video/x-raw,framerate=28/1 ! videoconvert ! queue ! x264enc bframes=0 key-int-max=0 byte-stream=true tune=zerolatency speed-preset=veryfast ! mux."
|
||||
```
|
||||
|
29
server/build
29
server/build
@ -1,10 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
#
|
||||
# aborting if any command returns a non-zero value
|
||||
set -e
|
||||
|
||||
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
||||
|
||||
#
|
||||
# set git build variables if git exists
|
||||
if git status > /dev/null 2>&1 && [ -z $GIT_COMMIT ] && [ -z $GIT_BRANCH ] && [ -z $GIT_TAG ];
|
||||
then
|
||||
GIT_COMMIT=`git rev-parse --short HEAD`
|
||||
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
|
||||
GIT_TAG=`git tag --points-at $GIT_COMMIT | head -n 1`
|
||||
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
|
||||
GIT_COMMIT="${GIT_DIRTY}${GIT_COMMIT}"
|
||||
fi
|
||||
|
||||
go build -o bin/neko -ldflags "-s -X 'm1k1o/neko.buildDate=${BUILD_TIME}' -X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}' -X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'" -i cmd/neko/main.go
|
||||
#
|
||||
# load dependencies
|
||||
go get -v -t -d .
|
||||
|
||||
#
|
||||
# build server
|
||||
go build \
|
||||
-o bin/neko \
|
||||
-ldflags "
|
||||
-s -w
|
||||
-X 'm1k1o/neko.buildDate=${BUILD_TIME}'
|
||||
-X 'm1k1o/neko.gitCommit=${GIT_COMMIT}'
|
||||
-X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'
|
||||
-X 'm1k1o/neko.gitTag=${GIT_TAG}'
|
||||
" \
|
||||
cmd/neko/main.go;
|
||||
|
@ -4,7 +4,7 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/pion/ice/v2 v2.3.0
|
||||
|
@ -63,8 +63,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"m1k1o/neko/internal/capture/gst"
|
||||
"m1k1o/neko/internal/config"
|
||||
"m1k1o/neko/internal/types/codec"
|
||||
)
|
||||
|
||||
@ -52,7 +53,7 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url
|
||||
return pipelineStr, nil
|
||||
}
|
||||
|
||||
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc string) (string, error) {
|
||||
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc config.HwEnc) (string, error) {
|
||||
pipelineStr := " ! appsink name=appsinkvideo"
|
||||
|
||||
// if using custom pipeline
|
||||
@ -68,7 +69,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
|
||||
switch rtpCodec.Name {
|
||||
case codec.VP8().Name:
|
||||
if hwenc == "VAAPI" {
|
||||
if hwenc == config.HwEncVAAPI {
|
||||
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -138,35 +139,40 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
return "", err
|
||||
}
|
||||
|
||||
if hwenc == "VAAPI" {
|
||||
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate)
|
||||
|
||||
} else {
|
||||
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
||||
// gstreamer1.0-plugins-bad
|
||||
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
|
||||
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
|
||||
break
|
||||
}
|
||||
|
||||
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
||||
// gstreamer1.0-plugins-ugly
|
||||
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
|
||||
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
vbvbuf := uint(1000)
|
||||
if bitrate > 1000 {
|
||||
vbvbuf = bitrate
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate, vbvbuf)
|
||||
if hwenc == config.HwEncVAAPI {
|
||||
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate)
|
||||
} else if hwenc == config.HwEncNVENC {
|
||||
if err := gst.CheckPlugins([]string{"nvcodec"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! nvh264enc name=encoder preset=2 gop-size=25 spatial-aq=true temporal-aq=true bitrate=%d vbv-buffer-size=%d rc-mode=6 ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
|
||||
} else {
|
||||
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
||||
// gstreamer1.0-plugins-bad
|
||||
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
|
||||
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
|
||||
break
|
||||
}
|
||||
|
||||
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
||||
// gstreamer1.0-plugins-ugly
|
||||
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
|
||||
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
||||
|
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"m1k1o/neko/internal/types/codec"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -9,11 +10,19 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type HwEnc int
|
||||
|
||||
const (
|
||||
HwEncNone HwEnc = iota
|
||||
HwEncVAAPI
|
||||
HwEncNVENC
|
||||
)
|
||||
|
||||
type Capture struct {
|
||||
// video
|
||||
Display string
|
||||
VideoCodec codec.RTPCodec
|
||||
VideoHWEnc string // TODO: Pipeline builder.
|
||||
VideoHWEnc HwEnc // TODO: Pipeline builder.
|
||||
VideoBitrate uint // TODO: Pipeline builder.
|
||||
VideoMaxFPS int16 // TODO: Pipeline builder.
|
||||
VideoPipeline string
|
||||
@ -92,7 +101,7 @@ func (Capture) Init(cmd *cobra.Command) error {
|
||||
// audio
|
||||
//
|
||||
|
||||
cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture")
|
||||
cmd.PersistentFlags().String("device", "audio_output.monitor", "audio device to capture")
|
||||
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -184,11 +193,19 @@ func (s *Capture) Set() {
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_VIDEO_CODEC=av1' instead")
|
||||
}
|
||||
|
||||
videoHWEnc := ""
|
||||
if viper.GetString("hwenc") == "VAAPI" {
|
||||
videoHWEnc = "VAAPI"
|
||||
videoHWEnc := strings.ToLower(viper.GetString("hwenc"))
|
||||
switch videoHWEnc {
|
||||
case "":
|
||||
fallthrough
|
||||
case "none":
|
||||
s.VideoHWEnc = HwEncNone
|
||||
case "vaapi":
|
||||
s.VideoHWEnc = HwEncVAAPI
|
||||
case "nvenc":
|
||||
s.VideoHWEnc = HwEncNVENC
|
||||
default:
|
||||
log.Warn().Str("hwenc", videoHWEnc).Msgf("unknown video hw encoder, using CPU")
|
||||
}
|
||||
s.VideoHWEnc = videoHWEnc
|
||||
|
||||
s.VideoBitrate = viper.GetUint("video_bitrate")
|
||||
s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
|
||||
|
@ -11,8 +11,8 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
|
@ -131,6 +131,17 @@ func (session *Session) SignalLocalAnswer(sdp string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (session *Session) SignalLocalCandidate(data string) error {
|
||||
if session.socket == nil {
|
||||
return nil
|
||||
}
|
||||
session.logger.Info().Msg("signal update - LocalCandidate")
|
||||
return session.socket.Send(&message.SignalCandidate{
|
||||
Event: event.SIGNAL_CANDIDATE,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func (session *Session) SignalRemoteOffer(sdp string) error {
|
||||
if session.peer == nil {
|
||||
return nil
|
||||
@ -154,14 +165,12 @@ func (session *Session) SignalRemoteAnswer(sdp string) error {
|
||||
return session.peer.SetAnswer(sdp)
|
||||
}
|
||||
|
||||
func (session *Session) SignalCandidate(data string) error {
|
||||
func (session *Session) SignalRemoteCandidate(data string) error {
|
||||
if session.socket == nil {
|
||||
return nil
|
||||
}
|
||||
return session.socket.Send(&message.SignalCandidate{
|
||||
Event: event.SIGNAL_CANDIDATE,
|
||||
Data: data,
|
||||
})
|
||||
session.logger.Info().Msg("signal update - RemoteCandidate")
|
||||
return session.peer.SetCandidate(data)
|
||||
}
|
||||
|
||||
func (session *Session) destroy() error {
|
||||
|
@ -40,9 +40,10 @@ type Session interface {
|
||||
Send(v interface{}) error
|
||||
SignalLocalOffer(sdp string) error
|
||||
SignalLocalAnswer(sdp string) error
|
||||
SignalLocalCandidate(data string) error
|
||||
SignalRemoteOffer(sdp string) error
|
||||
SignalRemoteAnswer(sdp string) error
|
||||
SignalCandidate(data string) error
|
||||
SignalRemoteCandidate(data string) error
|
||||
}
|
||||
|
||||
type SessionManager interface {
|
||||
|
@ -21,6 +21,7 @@ type Peer interface {
|
||||
CreateAnswer() (string, error)
|
||||
SetOffer(sdp string) error
|
||||
SetAnswer(sdp string) error
|
||||
SetCandidate(candidateString string) error
|
||||
WriteData(v interface{}) error
|
||||
Destroy() error
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@ -31,7 +31,7 @@ func GetIP(serverUrl string) (string, error) {
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
|
||||
buf, err := ioutil.ReadAll(rsp.Body)
|
||||
buf, err := io.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
@ -49,6 +50,16 @@ func (peer *Peer) SetAnswer(sdp string) error {
|
||||
return peer.connection.SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
|
||||
}
|
||||
|
||||
func (peer *Peer) SetCandidate(candidateString string) error {
|
||||
var candidate webrtc.ICECandidateInit
|
||||
err := json.Unmarshal([]byte(candidateString), &candidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return peer.connection.AddICECandidate(candidate)
|
||||
}
|
||||
|
||||
func (peer *Peer) WriteData(v interface{}) error {
|
||||
peer.mu.Lock()
|
||||
defer peer.mu.Unlock()
|
||||
|
@ -123,7 +123,6 @@ func (manager *WebRTCManager) initAPI() error {
|
||||
LoggerFactory: logger,
|
||||
}
|
||||
|
||||
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
||||
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
|
||||
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
|
||||
settings.SetSRTPReplayProtectionWindow(512)
|
||||
@ -168,12 +167,15 @@ func (manager *WebRTCManager) initAPI() error {
|
||||
|
||||
networkType = append(networkType, webrtc.NetworkTypeUDP4)
|
||||
manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX")
|
||||
} else if manager.config.EphemeralMax != 0 {
|
||||
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
||||
networkType = append(networkType,
|
||||
webrtc.NetworkTypeUDP4,
|
||||
webrtc.NetworkTypeUDP6,
|
||||
)
|
||||
}
|
||||
|
||||
// Enable support for TCP and UDP ICE candidates
|
||||
if len(networkType) > 0 {
|
||||
settings.SetNetworkTypes(networkType)
|
||||
}
|
||||
|
||||
// Create MediaEngine with selected codecs
|
||||
engine := webrtc.MediaEngine{}
|
||||
@ -299,7 +301,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (type
|
||||
return
|
||||
}
|
||||
|
||||
if err := session.SignalCandidate(string(candidateString)); err != nil {
|
||||
if err := session.SignalLocalCandidate(string(candidateString)); err != nil {
|
||||
manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed")
|
||||
return
|
||||
}
|
||||
|
@ -87,6 +87,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
||||
utils.Unmarshal(payload, raw, func() error {
|
||||
return h.signalRemoteAnswer(id, session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
case event.SIGNAL_CANDIDATE:
|
||||
payload := &message.SignalCandidate{}
|
||||
return errors.Wrapf(
|
||||
utils.Unmarshal(payload, raw, func() error {
|
||||
return h.signalRemoteCandidate(id, session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
|
||||
// Control Events
|
||||
case event.CONTROL_RELEASE:
|
||||
|
@ -45,3 +45,7 @@ func (h *MessageHandler) signalRemoteAnswer(id string, session types.Session, pa
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MessageHandler) signalRemoteCandidate(id string, session types.Session, payload *message.SignalCandidate) error {
|
||||
return session.SignalRemoteCandidate(payload.Data)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"m1k1o/neko/internal/capture"
|
||||
"m1k1o/neko/internal/config"
|
||||
@ -25,7 +26,7 @@ const Header = `&34
|
||||
/ |/ / _ \/ //_/ __ \ ) ( ')
|
||||
/ /| / __/ ,< / /_/ / ( / )
|
||||
/_/ |_/\___/_/|_|\____/ \(__)|
|
||||
&1&37 nurdism/m1k1o &33%s v%s&0
|
||||
&1&37 nurdism/m1k1o &33%s %s&0
|
||||
`
|
||||
|
||||
var (
|
||||
@ -35,13 +36,8 @@ var (
|
||||
gitCommit = "dev"
|
||||
//
|
||||
gitBranch = "dev"
|
||||
|
||||
// Major version when you make incompatible API changes,
|
||||
major = "2"
|
||||
// Minor version when you add functionality in a backwards-compatible manner, and
|
||||
minor = "7"
|
||||
// Patch version when you make backwards-compatible bug fixes.
|
||||
patch = "0"
|
||||
//
|
||||
gitTag = "dev"
|
||||
)
|
||||
|
||||
var Service *Neko
|
||||
@ -49,11 +45,9 @@ var Service *Neko
|
||||
func init() {
|
||||
Service = &Neko{
|
||||
Version: &Version{
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Patch: patch,
|
||||
GitCommit: gitCommit,
|
||||
GitBranch: gitBranch,
|
||||
GitTag: gitTag,
|
||||
BuildDate: buildDate,
|
||||
GoVersion: runtime.Version(),
|
||||
Compiler: runtime.Compiler,
|
||||
@ -69,11 +63,9 @@ func init() {
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Major string
|
||||
Minor string
|
||||
Patch string
|
||||
GitCommit string
|
||||
GitBranch string
|
||||
GitTag string
|
||||
BuildDate string
|
||||
GoVersion string
|
||||
Compiler string
|
||||
@ -81,20 +73,25 @@ type Version struct {
|
||||
}
|
||||
|
||||
func (i *Version) String() string {
|
||||
return fmt.Sprintf("%s.%s.%s %s", i.Major, i.Minor, i.Patch, i.GitCommit)
|
||||
version := i.GitTag
|
||||
if version == "" || version == "dev" {
|
||||
version = i.GitBranch
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s@%s", version, i.GitCommit)
|
||||
}
|
||||
|
||||
func (i *Version) Details() string {
|
||||
return fmt.Sprintf(
|
||||
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
|
||||
fmt.Sprintf("Version %s.%s.%s", i.Major, i.Minor, i.Patch),
|
||||
return "\n" + strings.Join([]string{
|
||||
fmt.Sprintf("Version %s", i.String()),
|
||||
fmt.Sprintf("GitCommit %s", i.GitCommit),
|
||||
fmt.Sprintf("GitBranch %s", i.GitBranch),
|
||||
fmt.Sprintf("GitTag %s", i.GitTag),
|
||||
fmt.Sprintf("BuildDate %s", i.BuildDate),
|
||||
fmt.Sprintf("GoVersion %s", i.GoVersion),
|
||||
fmt.Sprintf("Compiler %s", i.Compiler),
|
||||
fmt.Sprintf("Platform %s", i.Platform),
|
||||
)
|
||||
}, "\n") + "\n"
|
||||
}
|
||||
|
||||
type Neko struct {
|
||||
|
64
test/docker-compose.yaml
Normal file
64
test/docker-compose.yaml
Normal file
@ -0,0 +1,64 @@
|
||||
version: "3.4"
|
||||
|
||||
volumes:
|
||||
X11-unix:
|
||||
|
||||
services:
|
||||
xserver:
|
||||
build: "./xserver"
|
||||
restart: "unless-stopped"
|
||||
user: "1000:1000"
|
||||
command: ":0.0"
|
||||
volumes:
|
||||
- "X11-unix:/tmp/.X11-unix:rw"
|
||||
|
||||
pulseaudio:
|
||||
build: "./pulseaudio"
|
||||
restart: "unless-stopped"
|
||||
|
||||
neko:
|
||||
build:
|
||||
context: "../"
|
||||
dockerfile: "test/neko/Dockerfile"
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "8081:8080"
|
||||
- "52000-52100:52000-52100/udp"
|
||||
environment:
|
||||
DISPLAY: ":0.0"
|
||||
NEKO_DISPLAY: ":0.0 remote=true"
|
||||
PULSE_SERVER: tcp:pulseaudio:4713
|
||||
NEKO_SCREEN: 1920x1080@30
|
||||
NEKO_PASSWORD: neko
|
||||
NEKO_PASSWORD_ADMIN: admin
|
||||
NEKO_EPR: 52000-52100
|
||||
NEKO_NAT1TO1: 192.168.1.38
|
||||
NEKO_ICELITE: 1
|
||||
volumes:
|
||||
- "X11-unix:/tmp/.X11-unix:ro"
|
||||
depends_on:
|
||||
- xserver
|
||||
- pulseaudio
|
||||
|
||||
openbox:
|
||||
build: "./openbox"
|
||||
restart: "unless-stopped"
|
||||
environment:
|
||||
DISPLAY: ":0.0"
|
||||
volumes:
|
||||
- "X11-unix:/tmp/.X11-unix:ro"
|
||||
depends_on:
|
||||
- xserver
|
||||
|
||||
firefox:
|
||||
build: "./firefox"
|
||||
restart: "unless-stopped"
|
||||
environment:
|
||||
DISPLAY: ":0.0"
|
||||
PULSE_SERVER: tcp:pulseaudio:4713
|
||||
volumes:
|
||||
- "X11-unix:/tmp/.X11-unix:ro"
|
||||
depends_on:
|
||||
- neko
|
||||
- openbox
|
32
test/firefox/Dockerfile
Normal file
32
test/firefox/Dockerfile
Normal file
@ -0,0 +1,32 @@
|
||||
FROM m1k1o/neko:base
|
||||
|
||||
#
|
||||
# install firefox
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends firefox-esr; \
|
||||
#
|
||||
# create a non-root user
|
||||
#groupadd --gid 1000 neko; \
|
||||
#useradd --uid 1000 --gid neko --shell /bin/bash --create-home neko; \
|
||||
#
|
||||
# install fonts
|
||||
apt-get install -y --no-install-recommends \
|
||||
# Google emojis
|
||||
fonts-noto-color-emoji \
|
||||
# Japanese fonts
|
||||
fonts-takao-mincho \
|
||||
# Chinese fonts
|
||||
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||
# Korean fonts
|
||||
fonts-wqy-microhei; \
|
||||
#
|
||||
# clean up
|
||||
apt-get --purge autoremove -y xz-utils bzip2; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
USER neko
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/firefox" ]
|
||||
|
||||
CMD [ "--display", $DISPLAY, "-setDefaultBrowser", "-width", "1280", "-height", "720" ]
|
102
test/neko/Dockerfile
Normal file
102
test/neko/Dockerfile
Normal file
@ -0,0 +1,102 @@
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends git cmake make libx11-dev libxrandr-dev libxtst-dev \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly; \
|
||||
#
|
||||
# install libclipboard
|
||||
set -eux; \
|
||||
cd /tmp; \
|
||||
git clone --depth=1 https://github.com/jtanx/libclipboard; \
|
||||
cd libclipboard; \
|
||||
cmake .; \
|
||||
make -j4; \
|
||||
make install; \
|
||||
rm -rf /tmp/libclipboard; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:18-bullseye-slim as client
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
COPY client/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
#
|
||||
# build client
|
||||
COPY client/ .
|
||||
RUN npm run build
|
||||
|
||||
#
|
||||
# STAGE 3: RUNTIME
|
||||
#
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
#
|
||||
# set custom user
|
||||
ARG USERNAME=neko
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
#
|
||||
# install dependencies
|
||||
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
|
||||
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6; \
|
||||
#
|
||||
# gst
|
||||
apt-get install -y --no-install-recommends \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
|
||||
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
|
||||
gstreamer1.0-pulseaudio; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
|
||||
#
|
||||
# copy static files from previous stages
|
||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||
COPY --from=client /src/dist/ /var/www
|
||||
|
||||
USER $USERNAME
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/neko" ]
|
||||
|
||||
CMD [ "serve", "--static", "/var/www" ]
|
34
test/openbox/Dockerfile
Normal file
34
test/openbox/Dockerfile
Normal file
@ -0,0 +1,34 @@
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
#
|
||||
# set custom user
|
||||
ARG USERNAME=neko
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends openbox; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
||||
|
||||
USER $USERNAME
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/openbox" ]
|
||||
|
||||
CMD [ "--config-file", "/etc/neko/openbox.xml" ]
|
763
test/openbox/openbox.xml
Normal file
763
test/openbox/openbox.xml
Normal file
@ -0,0 +1,763 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- Default openbox config but all window decorations are moved
|
||||
thereby making it harder to accidentally close the virtual browser -->
|
||||
|
||||
<openbox_config xmlns="http://openbox.org/3.4/rc"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<resistance>
|
||||
<strength>10</strength>
|
||||
<screen_edge_strength>20</screen_edge_strength>
|
||||
</resistance>
|
||||
|
||||
<applications>
|
||||
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
|
||||
<application class="firefox" name="Navigator" role="browser">
|
||||
<decor>no</decor>
|
||||
<maximized>true</maximized>
|
||||
<focus>yes</focus>
|
||||
<layer>normal</layer>
|
||||
</application>
|
||||
</applications>
|
||||
|
||||
<focus>
|
||||
<focusNew>yes</focusNew>
|
||||
<!-- always try to focus new windows when they appear. other rules do
|
||||
apply -->
|
||||
<followMouse>no</followMouse>
|
||||
<!-- move focus to a window when you move the mouse into it -->
|
||||
<focusLast>yes</focusLast>
|
||||
<!-- focus the last used window when changing desktops, instead of the one
|
||||
under the mouse pointer. when followMouse is enabled -->
|
||||
<underMouse>no</underMouse>
|
||||
<!-- move focus under the mouse, even when the mouse is not moving -->
|
||||
<focusDelay>200</focusDelay>
|
||||
<!-- when followMouse is enabled, the mouse must be inside the window for
|
||||
this many milliseconds (1000 = 1 sec) before moving focus to it -->
|
||||
<raiseOnFocus>no</raiseOnFocus>
|
||||
<!-- when followMouse is enabled, and a window is given focus by moving the
|
||||
mouse into it, also raise the window -->
|
||||
</focus>
|
||||
|
||||
<placement>
|
||||
<policy>Smart</policy>
|
||||
<!-- 'Smart' or 'UnderMouse' -->
|
||||
<center>yes</center>
|
||||
<!-- whether to place windows in the center of the free area found or
|
||||
the top left corner -->
|
||||
<monitor>Primary</monitor>
|
||||
<!-- with Smart placement on a multi-monitor system, try to place new windows
|
||||
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
|
||||
the active window is, 'Primary' - only on the primary monitor -->
|
||||
<primaryMonitor>1</primaryMonitor>
|
||||
<!-- The monitor where Openbox should place popup dialogs such as the
|
||||
focus cycling popup, or the desktop switch popup. It can be an index
|
||||
from 1, specifying a particular monitor. Or it can be one of the
|
||||
following: 'Mouse' - where the mouse is, or
|
||||
'Active' - where the active window is -->
|
||||
</placement>
|
||||
|
||||
<theme>
|
||||
<name>Clearlooks</name>
|
||||
<titleLayout>NLIMC</titleLayout>
|
||||
<!--
|
||||
available characters are NDSLIMC, each can occur at most once.
|
||||
N: window icon
|
||||
L: window label (AKA title).
|
||||
I: iconify
|
||||
M: maximize
|
||||
C: close
|
||||
S: shade (roll up/down)
|
||||
D: omnipresent (on all desktops).
|
||||
-->
|
||||
<keepBorder>yes</keepBorder>
|
||||
<animateIconify>yes</animateIconify>
|
||||
<font place="ActiveWindow">
|
||||
<name>sans</name>
|
||||
<size>8</size>
|
||||
<!-- font size in points -->
|
||||
<weight>bold</weight>
|
||||
<!-- 'bold' or 'normal' -->
|
||||
<slant>normal</slant>
|
||||
<!-- 'italic' or 'normal' -->
|
||||
</font>
|
||||
<font place="InactiveWindow">
|
||||
<name>sans</name>
|
||||
<size>8</size>
|
||||
<!-- font size in points -->
|
||||
<weight>bold</weight>
|
||||
<!-- 'bold' or 'normal' -->
|
||||
<slant>normal</slant>
|
||||
<!-- 'italic' or 'normal' -->
|
||||
</font>
|
||||
<font place="MenuHeader">
|
||||
<name>sans</name>
|
||||
<size>9</size>
|
||||
<!-- font size in points -->
|
||||
<weight>normal</weight>
|
||||
<!-- 'bold' or 'normal' -->
|
||||
<slant>normal</slant>
|
||||
<!-- 'italic' or 'normal' -->
|
||||
</font>
|
||||
<font place="MenuItem">
|
||||
<name>sans</name>
|
||||
<size>9</size>
|
||||
<!-- font size in points -->
|
||||
<weight>normal</weight>
|
||||
<!-- 'bold' or 'normal' -->
|
||||
<slant>normal</slant>
|
||||
<!-- 'italic' or 'normal' -->
|
||||
</font>
|
||||
<font place="ActiveOnScreenDisplay">
|
||||
<name>sans</name>
|
||||
<size>9</size>
|
||||
<!-- font size in points -->
|
||||
<weight>bold</weight>
|
||||
<!-- 'bold' or 'normal' -->
|
||||
<slant>normal</slant>
|
||||
<!-- 'italic' or 'normal' -->
|
||||
</font>
|
||||
<font place="InactiveOnScreenDisplay">
|
||||
<name>sans</name>
|
||||
<size>9</size>
|
||||
<!-- font size in points -->
|
||||
<weight>bold</weight>
|
||||
<!-- 'bold' or 'normal' -->
|
||||
<slant>normal</slant>
|
||||
<!-- 'italic' or 'normal' -->
|
||||
</font>
|
||||
</theme>
|
||||
|
||||
<desktops>
|
||||
<!-- this stuff is only used at startup, pagers allow you to change them
|
||||
during a session
|
||||
|
||||
these are default values to use when other ones are not already set
|
||||
by other applications, or saved in your session
|
||||
|
||||
use obconf if you want to change these without having to log out
|
||||
and back in -->
|
||||
<number>1</number>
|
||||
<firstdesk>1</firstdesk>
|
||||
<names>
|
||||
<!-- set names up here if you want to, like this:
|
||||
<name>desktop 1</name>
|
||||
<name>desktop 2</name>
|
||||
-->
|
||||
</names>
|
||||
<popupTime>875</popupTime>
|
||||
<!-- The number of milliseconds to show the popup for when switching
|
||||
desktops. Set this to 0 to disable the popup. -->
|
||||
</desktops>
|
||||
|
||||
<resize>
|
||||
<drawContents>yes</drawContents>
|
||||
<popupShow>Nonpixel</popupShow>
|
||||
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
|
||||
<popupPosition>Center</popupPosition>
|
||||
<!-- 'Center', 'Top', or 'Fixed' -->
|
||||
<popupFixedPosition>
|
||||
<!-- these are used if popupPosition is set to 'Fixed' -->
|
||||
|
||||
<x>10</x>
|
||||
<!-- positive number for distance from left edge, negative number for
|
||||
distance from right edge, or 'Center' -->
|
||||
<y>10</y>
|
||||
<!-- positive number for distance from top edge, negative number for
|
||||
distance from bottom edge, or 'Center' -->
|
||||
</popupFixedPosition>
|
||||
</resize>
|
||||
|
||||
<!-- You can reserve a portion of your screen where windows will not cover when
|
||||
they are maximized, or when they are initially placed.
|
||||
Many programs reserve space automatically, but you can use this in other
|
||||
cases. -->
|
||||
<margins>
|
||||
<top>0</top>
|
||||
<bottom>0</bottom>
|
||||
<left>0</left>
|
||||
<right>0</right>
|
||||
</margins>
|
||||
|
||||
<dock>
|
||||
<position>TopLeft</position>
|
||||
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
|
||||
<floatingX>0</floatingX>
|
||||
<floatingY>0</floatingY>
|
||||
<noStrut>no</noStrut>
|
||||
<stacking>Above</stacking>
|
||||
<!-- 'Above', 'Normal', or 'Below' -->
|
||||
<direction>Vertical</direction>
|
||||
<!-- 'Vertical' or 'Horizontal' -->
|
||||
<autoHide>no</autoHide>
|
||||
<hideDelay>300</hideDelay>
|
||||
<!-- in milliseconds (1000 = 1 second) -->
|
||||
<showDelay>300</showDelay>
|
||||
<!-- in milliseconds (1000 = 1 second) -->
|
||||
<moveButton>Middle</moveButton>
|
||||
<!-- 'Left', 'Middle', 'Right' -->
|
||||
</dock>
|
||||
|
||||
<keyboard>
|
||||
<chainQuitKey>C-g</chainQuitKey>
|
||||
|
||||
<!-- Keybindings for desktop switching -->
|
||||
<keybind key="C-A-Left">
|
||||
<action name="GoToDesktop"><to>left</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="C-A-Right">
|
||||
<action name="GoToDesktop"><to>right</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="C-A-Up">
|
||||
<action name="GoToDesktop"><to>up</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="C-A-Down">
|
||||
<action name="GoToDesktop"><to>down</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="S-A-Left">
|
||||
<action name="SendToDesktop"><to>left</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="S-A-Right">
|
||||
<action name="SendToDesktop"><to>right</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="S-A-Up">
|
||||
<action name="SendToDesktop"><to>up</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="S-A-Down">
|
||||
<action name="SendToDesktop"><to>down</to><wrap>no</wrap></action>
|
||||
</keybind>
|
||||
<keybind key="W-F1">
|
||||
<action name="GoToDesktop"><to>1</to></action>
|
||||
</keybind>
|
||||
<keybind key="W-F2">
|
||||
<action name="GoToDesktop"><to>2</to></action>
|
||||
</keybind>
|
||||
<keybind key="W-F3">
|
||||
<action name="GoToDesktop"><to>3</to></action>
|
||||
</keybind>
|
||||
<keybind key="W-F4">
|
||||
<action name="GoToDesktop"><to>4</to></action>
|
||||
</keybind>
|
||||
<keybind key="W-d">
|
||||
<action name="ToggleShowDesktop"/>
|
||||
</keybind>
|
||||
|
||||
<!-- Keybindings for windows -->
|
||||
<keybind key="A-F4">
|
||||
<action name="Close"/>
|
||||
</keybind>
|
||||
<keybind key="A-Escape">
|
||||
<action name="Lower"/>
|
||||
<action name="FocusToBottom"/>
|
||||
<action name="Unfocus"/>
|
||||
</keybind>
|
||||
<keybind key="A-space">
|
||||
<!--action name="ShowMenu"><menu>client-menu</menu></action-->
|
||||
</keybind>
|
||||
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
|
||||
<keybind key="A-Print">
|
||||
<action name="Execute"><command>scrot -s</command></action>
|
||||
</keybind>
|
||||
|
||||
<!-- Keybindings for window switching -->
|
||||
<keybind key="A-Tab">
|
||||
<action name="NextWindow">
|
||||
<finalactions>
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</finalactions>
|
||||
</action>
|
||||
</keybind>
|
||||
<keybind key="A-S-Tab">
|
||||
<action name="PreviousWindow">
|
||||
<finalactions>
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</finalactions>
|
||||
</action>
|
||||
</keybind>
|
||||
<keybind key="C-A-Tab">
|
||||
<action name="NextWindow">
|
||||
<panels>yes</panels><desktop>yes</desktop>
|
||||
<finalactions>
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</finalactions>
|
||||
</action>
|
||||
</keybind>
|
||||
|
||||
<!-- Keybindings for window switching with the arrow keys -->
|
||||
<keybind key="W-S-Right">
|
||||
<action name="DirectionalCycleWindows">
|
||||
<direction>right</direction>
|
||||
</action>
|
||||
</keybind>
|
||||
<keybind key="W-S-Left">
|
||||
<action name="DirectionalCycleWindows">
|
||||
<direction>left</direction>
|
||||
</action>
|
||||
</keybind>
|
||||
<keybind key="W-S-Up">
|
||||
<action name="DirectionalCycleWindows">
|
||||
<direction>up</direction>
|
||||
</action>
|
||||
</keybind>
|
||||
<keybind key="W-S-Down">
|
||||
<action name="DirectionalCycleWindows">
|
||||
<direction>down</direction>
|
||||
</action>
|
||||
</keybind>
|
||||
|
||||
<!-- Keybindings for running applications -->
|
||||
<keybind key="W-e">
|
||||
<action name="Execute">
|
||||
<startupnotify>
|
||||
<enabled>true</enabled>
|
||||
<name>Konqueror</name>
|
||||
</startupnotify>
|
||||
<command>kfmclient openProfile filemanagement</command>
|
||||
</action>
|
||||
</keybind>
|
||||
<!-- Launch scrot when Print is pressed -->
|
||||
<keybind key="Print">
|
||||
<action name="Execute"><command>scrot</command></action>
|
||||
</keybind>
|
||||
</keyboard>
|
||||
|
||||
<mouse>
|
||||
<dragThreshold>1</dragThreshold>
|
||||
<!-- number of pixels the mouse must move before a drag begins -->
|
||||
<doubleClickTime>500</doubleClickTime>
|
||||
<!-- in milliseconds (1000 = 1 second) -->
|
||||
<screenEdgeWarpTime>400</screenEdgeWarpTime>
|
||||
<!-- Time before changing desktops when the pointer touches the edge of the
|
||||
screen while moving a window, in milliseconds (1000 = 1 second).
|
||||
Set this to 0 to disable warping -->
|
||||
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
|
||||
<!-- Set this to TRUE to move the mouse pointer across the desktop when
|
||||
switching due to hitting the edge of the screen -->
|
||||
|
||||
<context name="Frame">
|
||||
<mousebind button="A-Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
<mousebind button="A-Left" action="Click">
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="A-Left" action="Drag">
|
||||
<action name="Move"/>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="A-Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="A-Right" action="Drag">
|
||||
<action name="Resize"/>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="A-Middle" action="Press">
|
||||
<action name="Lower"/>
|
||||
<action name="FocusToBottom"/>
|
||||
<action name="Unfocus"/>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="A-Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="A-Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="C-A-Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="C-A-Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="A-S-Up" action="Click">
|
||||
<action name="SendToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="A-S-Down" action="Click">
|
||||
<action name="SendToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Titlebar">
|
||||
<mousebind button="Left" action="Drag">
|
||||
<action name="Move"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="DoubleClick">
|
||||
<action name="ToggleMaximize"/>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="Up" action="Click">
|
||||
<action name="if">
|
||||
<shaded>no</shaded>
|
||||
<then>
|
||||
<action name="Shade"/>
|
||||
<action name="FocusToBottom"/>
|
||||
<action name="Unfocus"/>
|
||||
<action name="Lower"/>
|
||||
</then>
|
||||
</action>
|
||||
</mousebind>
|
||||
<mousebind button="Down" action="Click">
|
||||
<action name="if">
|
||||
<shaded>yes</shaded>
|
||||
<then>
|
||||
<action name="Unshade"/>
|
||||
<action name="Raise"/>
|
||||
</then>
|
||||
</action>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="Middle" action="Press">
|
||||
<action name="Lower"/>
|
||||
<action name="FocusToBottom"/>
|
||||
<action name="Unfocus"/>
|
||||
</mousebind>
|
||||
|
||||
<!--mousebind button="Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="ShowMenu"><menu>client-menu</menu></action>
|
||||
</mousebind-->
|
||||
</context>
|
||||
|
||||
<context name="Top">
|
||||
<mousebind button="Left" action="Drag">
|
||||
<action name="Resize"><edge>top</edge></action>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Left">
|
||||
<mousebind button="Left" action="Drag">
|
||||
<action name="Resize"><edge>left</edge></action>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Right">
|
||||
<mousebind button="Left" action="Drag">
|
||||
<action name="Resize"><edge>right</edge></action>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Bottom">
|
||||
<mousebind button="Left" action="Drag">
|
||||
<action name="Resize"><edge>bottom</edge></action>
|
||||
</mousebind>
|
||||
|
||||
<!--mousebind button="Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="ShowMenu"><menu>client-menu</menu></action>
|
||||
</mousebind-->
|
||||
</context>
|
||||
|
||||
<context name="TRCorner BRCorner TLCorner BLCorner">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="Drag">
|
||||
<action name="Resize"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Client">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
<mousebind button="Middle" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
<mousebind button="Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Icon">
|
||||
<!--mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
<action name="ShowMenu"><menu>client-menu</menu></action>
|
||||
</mousebind>
|
||||
<mousebind button="Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="ShowMenu"><menu>client-menu</menu></action>
|
||||
</mousebind-->
|
||||
</context>
|
||||
|
||||
<context name="AllDesktops">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="Click">
|
||||
<action name="ToggleOmnipresent"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Shade">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="Click">
|
||||
<action name="ToggleShade"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Iconify">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="Click">
|
||||
<action name="Iconify"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Maximize">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="Middle" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="Click">
|
||||
<action name="ToggleMaximize"/>
|
||||
</mousebind>
|
||||
<mousebind button="Middle" action="Click">
|
||||
<action name="ToggleMaximize"><direction>vertical</direction></action>
|
||||
</mousebind>
|
||||
<mousebind button="Right" action="Click">
|
||||
<action name="ToggleMaximize"><direction>horizontal</direction></action>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Close">
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
<action name="Unshade"/>
|
||||
</mousebind>
|
||||
<mousebind button="Left" action="Click">
|
||||
<action name="Close"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Desktop">
|
||||
<mousebind button="Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="A-Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="A-Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="C-A-Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="C-A-Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
|
||||
<mousebind button="Left" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
<mousebind button="Right" action="Press">
|
||||
<action name="Focus"/>
|
||||
<action name="Raise"/>
|
||||
</mousebind>
|
||||
</context>
|
||||
|
||||
<context name="Root">
|
||||
<!-- Menus -->
|
||||
<!--mousebind button="Middle" action="Press">
|
||||
<action name="ShowMenu"><menu>client-list-combined-menu</menu></action>
|
||||
</mousebind>
|
||||
<mousebind button="Right" action="Press">
|
||||
<action name="ShowMenu"><menu>root-menu</menu></action>
|
||||
</mousebind-->
|
||||
</context>
|
||||
|
||||
<context name="MoveResize">
|
||||
<mousebind button="Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="A-Up" action="Click">
|
||||
<action name="GoToDesktop"><to>previous</to></action>
|
||||
</mousebind>
|
||||
<mousebind button="A-Down" action="Click">
|
||||
<action name="GoToDesktop"><to>next</to></action>
|
||||
</mousebind>
|
||||
</context>
|
||||
</mouse>
|
||||
|
||||
<menu>
|
||||
<!-- You can specify more than one menu file in here and they are all loaded,
|
||||
just don't make menu ids clash or, well, it'll be kind of pointless -->
|
||||
|
||||
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
|
||||
<!-- system menu files on Debian systems -->
|
||||
<!--file>/var/lib/openbox/debian-menu.xml</file-->
|
||||
<file>menu.xml</file>
|
||||
<hideDelay>200</hideDelay>
|
||||
<!-- if a press-release lasts longer than this setting (in milliseconds), the
|
||||
menu is hidden again -->
|
||||
<middle>no</middle>
|
||||
<!-- center submenus vertically about the parent entry -->
|
||||
<submenuShowDelay>100</submenuShowDelay>
|
||||
<!-- time to delay before showing a submenu after hovering over the parent
|
||||
entry.
|
||||
if this is a negative value, then the delay is infinite and the
|
||||
submenu will not be shown until it is clicked on -->
|
||||
<submenuHideDelay>400</submenuHideDelay>
|
||||
<!-- time to delay before hiding a submenu when selecting another
|
||||
entry in parent menu
|
||||
if this is a negative value, then the delay is infinite and the
|
||||
submenu will not be hidden until a different submenu is opened -->
|
||||
<showIcons>yes</showIcons>
|
||||
<!-- controls if icons appear in the client-list-(combined-)menu -->
|
||||
<manageDesktops>yes</manageDesktops>
|
||||
<!-- show the manage desktops section in the client-list-(combined-)menu -->
|
||||
</menu>
|
||||
|
||||
<applications>
|
||||
<!--
|
||||
# this is an example with comments through out. use these to make your
|
||||
# own rules, but without the comments of course.
|
||||
# you may use one or more of the name/class/role/title/type rules to specify
|
||||
# windows to match
|
||||
|
||||
<application name="the window's _OB_APP_NAME property (see obxprop)"
|
||||
class="the window's _OB_APP_CLASS property (see obxprop)"
|
||||
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
|
||||
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
|
||||
role="the window's _OB_APP_ROLE property (see obxprop)"
|
||||
title="the window's _OB_APP_TITLE property (see obxprop)"
|
||||
type="the window's _OB_APP_TYPE property (see obxprob)..
|
||||
(if unspecified, then it is 'dialog' for child windows)">
|
||||
# you may set only one of name/class/role/title/type, or you may use more
|
||||
# than one together to restrict your matches.
|
||||
|
||||
# the name, class, role, and title use simple wildcard matching such as those
|
||||
# used by a shell. you can use * to match any characters and ? to match
|
||||
# any single character.
|
||||
|
||||
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
|
||||
# or desktop
|
||||
|
||||
# when multiple rules match a window, they will all be applied, in the
|
||||
# order that they appear in this list
|
||||
|
||||
|
||||
# each rule element can be left out or set to 'default' to specify to not
|
||||
# change that attribute of the window
|
||||
|
||||
<decor>yes</decor>
|
||||
# enable or disable window decorations
|
||||
|
||||
<shade>no</shade>
|
||||
# make the window shaded when it appears, or not
|
||||
|
||||
<position force="no">
|
||||
# the position is only used if both an x and y coordinate are provided
|
||||
# (and not set to 'default')
|
||||
# when force is "yes", then the window will be placed here even if it
|
||||
# says you want it placed elsewhere. this is to override buggy
|
||||
# applications who refuse to behave
|
||||
<x>center</x>
|
||||
# a number like 50, or 'center' to center on screen. use a negative number
|
||||
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
|
||||
# the right edge (or bottom). use 'default' to specify using value
|
||||
# provided by the application, or chosen by openbox, instead.
|
||||
<y>200</y>
|
||||
<monitor>1</monitor>
|
||||
# specifies the monitor in a xinerama setup.
|
||||
# 1 is the first head, or 'mouse' for wherever the mouse is
|
||||
</position>
|
||||
|
||||
<size>
|
||||
# the size to make the window.
|
||||
<width>20</width>
|
||||
# a number like 20, or 'default' to use the size given by the application.
|
||||
# you can use fractions such as 1/2 or percentages such as 75% in which
|
||||
# case the value is relative to the size of the monitor that the window
|
||||
# appears on.
|
||||
<height>30%</height>
|
||||
</size>
|
||||
|
||||
<focus>yes</focus>
|
||||
# if the window should try be given focus when it appears. if this is set
|
||||
# to yes it doesn't guarantee the window will be given focus. some
|
||||
# restrictions may apply, but Openbox will try to
|
||||
|
||||
<desktop>1</desktop>
|
||||
# 1 is the first desktop, 'all' for all desktops
|
||||
|
||||
<layer>normal</layer>
|
||||
# 'above', 'normal', or 'below'
|
||||
|
||||
<iconic>no</iconic>
|
||||
# make the window iconified when it appears, or not
|
||||
|
||||
<skip_pager>no</skip_pager>
|
||||
# asks to not be shown in pagers
|
||||
|
||||
<skip_taskbar>no</skip_taskbar>
|
||||
# asks to not be shown in taskbars. window cycling actions will also
|
||||
# skip past such windows
|
||||
|
||||
<fullscreen>yes</fullscreen>
|
||||
# make the window in fullscreen mode when it appears
|
||||
|
||||
<maximized>true</maximized>
|
||||
# 'Horizontal', 'Vertical' or boolean (yes/no)
|
||||
</application>
|
||||
|
||||
# end of the example
|
||||
-->
|
||||
</applications>
|
||||
|
||||
</openbox_config>
|
38
test/pulseaudio/Dockerfile
Normal file
38
test/pulseaudio/Dockerfile
Normal file
@ -0,0 +1,38 @@
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
#
|
||||
# set custom user
|
||||
ARG USERNAME=neko
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends pulseaudio; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
|
||||
#
|
||||
# make directories
|
||||
mkdir -p /home/$USERNAME/.config/pulse; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY default.pa /etc/pulse/default.pa
|
||||
|
||||
USER $USERNAME
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/pulseaudio" ]
|
||||
|
||||
CMD [ "--log-level=info", "--disallow-module-loading", "--disallow-exit", "--exit-idle-time=-1" ]
|
10
test/pulseaudio/default.pa
Normal file
10
test/pulseaudio/default.pa
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/pulseaudio -nF
|
||||
|
||||
### Create virtual output device sink
|
||||
load-module module-null-sink sink_name=audio_output sink_properties=device.description="Virtual\ Audio\ Output"
|
||||
|
||||
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
|
||||
load-module module-native-protocol-tcp port=4713 auth-anonymous=1
|
||||
|
||||
### Make sure we always have a sink around, even if it is a null sink.
|
||||
load-module module-always-sink
|
17
test/xserver/Dockerfile
Normal file
17
test/xserver/Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends x11-xserver-utils xserver-xorg-video-dummy; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY xorg.conf /etc/neko/xorg.conf
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/X", "-config", "/etc/neko/xorg.conf", "-nolisten", "local", "-logfile", "/dev/stderr" ]
|
88
test/xserver/xorg.conf
Normal file
88
test/xserver/xorg.conf
Normal file
@ -0,0 +1,88 @@
|
||||
# This xorg configuration file is meant to be used by xpra
|
||||
# to start a dummy X11 server.
|
||||
# For details, please see:
|
||||
# https://xpra.org/trac/wiki/Xdummy
|
||||
|
||||
Section "ServerFlags"
|
||||
Option "DontVTSwitch" "true"
|
||||
Option "AllowMouseOpenFail" "true"
|
||||
Option "PciForceNone" "true"
|
||||
Option "AutoEnableDevices" "false"
|
||||
Option "AutoAddDevices" "false"
|
||||
EndSection
|
||||
|
||||
Section "InputDevice"
|
||||
Identifier "dummy_mouse"
|
||||
Option "CorePointer" "true"
|
||||
Driver "void"
|
||||
EndSection
|
||||
|
||||
Section "InputDevice"
|
||||
Identifier "dummy_keyboard"
|
||||
Option "CoreKeyboard" "true"
|
||||
Driver "void"
|
||||
EndSection
|
||||
|
||||
Section "Device"
|
||||
Identifier "dummy_videocard"
|
||||
Driver "dummy"
|
||||
Option "ConstantDPI" "true"
|
||||
#VideoRam 4096000
|
||||
#VideoRam 256000
|
||||
VideoRam 192000
|
||||
EndSection
|
||||
|
||||
Section "Monitor"
|
||||
Identifier "dummy_monitor"
|
||||
HorizSync 5.0 - 1000.0
|
||||
VertRefresh 5.0 - 200.0
|
||||
#This can be used to get a specific DPI, but only for the default resolution:
|
||||
#DisplaySize 508 317
|
||||
#NOTE: the highest modes will not work without increasing the VideoRam
|
||||
# for the dummy video card.
|
||||
# https://arachnoid.com/modelines/
|
||||
|
||||
# 1280x720 @ 30.00 Hz (GTF) hsync: 21.99 kHz; pclk: 33.78 MHz
|
||||
Modeline "1280x720_30.00" 33.78 1280 1288 1408 1536 720 721 724 733 -HSync +Vsync
|
||||
|
||||
# 1280x720 @ 60.00 Hz (GTF) hsync: 44.76 kHz; pclk: 74.48 MHz
|
||||
Modeline "1280x720_60.00" 74.48 1280 1336 1472 1664 720 721 724 746 -HSync +Vsync
|
||||
# 1152x648 @ 60.00 Hz (GTF) hsync: 40.26 kHz; pclk: 59.91 MHz
|
||||
Modeline "1152x648_60.00" 59.91 1152 1200 1320 1488 648 649 652 671 -HSync +Vsync
|
||||
# 1024x576 @ 60.00 Hz (GTF) hsync: 35.82 kHz; pclk: 47.00 MHz
|
||||
Modeline "1024x576_60.00" 47.00 1024 1064 1168 1312 576 577 580 597 -HSync +Vsync
|
||||
# 960x720 @ 60.00 Hz (GTF) hsync: 44.76 kHz; pclk: 55.86 MHz
|
||||
Modeline "960x720_60.00" 55.86 960 1008 1104 1248 720 721 724 746 -HSync +Vsync
|
||||
# 800x600 @ 60.00 Hz (GTF) hsync: 37.32 kHz; pclk: 38.22 MHz
|
||||
Modeline "800x600_60.00" 38.22 800 832 912 1024 600 601 604 622 -HSync +Vsync
|
||||
|
||||
# 1920x1080 @ 30.00 Hz (GTF) hsync: 32.97 kHz; pclk: 80.18 MHz
|
||||
Modeline "1920x1080_30.00" 80.18 1920 1984 2176 2432 1080 1081 1084 1099 -HSync +Vsync
|
||||
# 1152x648 @ 30.00 Hz (GTF) hsync: 19.80 kHz; pclk: 26.93 MHz
|
||||
Modeline "1152x648_30.00" 26.93 1152 1144 1256 1360 648 649 652 660 -HSync +Vsync
|
||||
# 1024x576 @ 30.00 Hz (GTF) hsync: 17.61 kHz; pclk: 20.85 MHz
|
||||
Modeline "1024x576_30.00" 20.85 1024 1008 1104 1184 576 577 580 587 -HSync +Vsync
|
||||
# 960x720 @ 30.00 Hz (GTF) hsync: 21.99 kHz; pclk: 25.33 MHz
|
||||
Modeline "960x720_30.00" 25.33 960 960 1056 1152 720 721 724 733 -HSync +Vsync
|
||||
# 800x600 @ 30.00 Hz (GTF) hsync: 18.33 kHz; pclk: 17.01 MHz
|
||||
Modeline "800x600_30.00" 17.01 800 792 864 928 600 601 604 611 -HSync +Vsync
|
||||
EndSection
|
||||
|
||||
Section "Screen"
|
||||
Identifier "dummy_screen"
|
||||
Device "dummy_videocard"
|
||||
Monitor "dummy_monitor"
|
||||
DefaultDepth 24
|
||||
SubSectionSub "Display"
|
||||
Viewport 0 0
|
||||
Depth 24
|
||||
Modes "1280x720_30.00" "1920x1080_60.00" "1280x720_60.00" "1152x648_60.00" "1024x576_60.00" "960x720_60.00" "800x600_60.00" "1920x1080_30.00" "1152x648_30.00" "1024x576_30.00" "960x720_30.00" "800x600_30.00"
|
||||
EndSubSection
|
||||
EndSection
|
||||
|
||||
Section "ServerLayout"
|
||||
Identifier "dummy_layout"
|
||||
Screen "dummy_screen"
|
||||
InputDevice "dummy_mouse"
|
||||
InputDevice "dummy_keyboard"
|
||||
EndSection
|
Reference in New Issue
Block a user