mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
683b750189 | |||
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
|
# build server
|
||||||
COPY server/ .
|
COPY server/ .
|
||||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
RUN ./build
|
||||||
|
|
||||||
#
|
#
|
||||||
# STAGE 2: CLIENT
|
# STAGE 2: CLIENT
|
||||||
@ -79,7 +79,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -88,19 +90,18 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
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
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# 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; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
#
|
#
|
||||||
# clean up
|
# clean up
|
||||||
@ -118,6 +119,8 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
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=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
|
@ -27,29 +27,32 @@ RUN set -eux; apt-get update; \
|
|||||||
#
|
#
|
||||||
# build server
|
# build server
|
||||||
COPY server/ .
|
COPY server/ .
|
||||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
RUN ./build
|
||||||
|
|
||||||
#
|
#
|
||||||
# STAGE 2: CLIENT
|
# 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
|
# Because client builds fail in Github Actions, therefor we build it outside of Docker.
|
||||||
COPY client/package*.json ./
|
#
|
||||||
RUN npm install
|
# 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
|
# STAGE 3: RUNTIME
|
||||||
#
|
#
|
||||||
@ -85,7 +88,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -94,19 +99,18 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
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
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# 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; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
#
|
#
|
||||||
# clean up
|
# clean up
|
||||||
@ -124,6 +128,8 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
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=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
@ -131,7 +137,8 @@ ENV NEKO_BIND=:8080
|
|||||||
#
|
#
|
||||||
# copy static files from previous stages
|
# copy static files from previous stages
|
||||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
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 \
|
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||||
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
|
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
|
||||||
|
@ -27,7 +27,7 @@ RUN set -eux; apt-get update; \
|
|||||||
#
|
#
|
||||||
# build server
|
# build server
|
||||||
COPY server/ .
|
COPY server/ .
|
||||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
RUN ./build
|
||||||
|
|
||||||
#
|
#
|
||||||
# STAGE 2: CLIENT
|
# STAGE 2: CLIENT
|
||||||
@ -88,7 +88,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -97,19 +99,18 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
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
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# 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; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
#
|
#
|
||||||
# clean up
|
# clean up
|
||||||
@ -128,9 +129,12 @@ COPY .docker/base/intel/add-render-group.sh /usr/bin/add-render-group.sh
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
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=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
|
ENV NEKO_HWENC=VAAPI
|
||||||
ENV RENDER_GID=
|
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
|
#!/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
|
# 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
|
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_maxbytes=100MB
|
||||||
stdout_logfile_backups=10
|
stdout_logfile_backups=10
|
||||||
redirect_stderr=true
|
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]
|
[program:brave]
|
||||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
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
|
stopsignal=INT
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=800
|
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
|
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
|
case $1 in
|
||||||
client) build_client;;
|
client) build_client;;
|
||||||
server) build_server;;
|
server) build_server;;
|
||||||
@ -102,6 +117,9 @@ case $1 in
|
|||||||
# build intel- images
|
# build intel- images
|
||||||
intel-*) build_intel "${1#intel-}";;
|
intel-*) build_intel "${1#intel-}";;
|
||||||
|
|
||||||
|
# build nvidia- images
|
||||||
|
nvidia-*) build_nvidia "${1#nvidia-}";;
|
||||||
|
|
||||||
# build images
|
# build images
|
||||||
*) build "$1";;
|
*) build "$1";;
|
||||||
esac
|
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]
|
[program:chromium]
|
||||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
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
|
stopsignal=INT
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=800
|
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]
|
[program:google-chrome]
|
||||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
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
|
stopsignal=INT
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=800
|
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; \
|
apt-get clean -y; \
|
||||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
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 configuation files
|
||||||
COPY supervisord.conf /etc/neko/supervisord/kde.conf
|
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]
|
[program:microsoft-edge]
|
||||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
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
|
stopsignal=INT
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=800
|
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 \
|
docker run --rm -it \
|
||||||
-v "${PWD}/../server:/src" \
|
-v "${PWD}/../server:/src" \
|
||||||
--entrypoint="go" \
|
-e GIT_COMMIT=`git rev-parse --short HEAD` \
|
||||||
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"
|
-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" \
|
-v "${PWD}/../client:/app" \
|
||||||
--workdir="/app" \
|
--workdir="/app" \
|
||||||
--entrypoint="npm" \
|
--entrypoint="npm" \
|
||||||
node:14-buster-slim install
|
node:18-bullseye-slim install
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker run --rm -it \
|
docker run --rm -it \
|
||||||
@ -25,5 +25,5 @@ docker run --rm -it \
|
|||||||
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
|
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
|
||||||
--workdir="/app" \
|
--workdir="/app" \
|
||||||
--entrypoint="npm" \
|
--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
|
./rebuild-server
|
||||||
fi
|
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 \
|
docker run --rm -it \
|
||||||
--name "neko_dev" \
|
--name "neko_dev" \
|
||||||
|
$GPU_FLAG \
|
||||||
-p "${SERVER_PORT}:8080" \
|
-p "${SERVER_PORT}:8080" \
|
||||||
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
|
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
|
||||||
-e "NEKO_SCREEN=1920x1080@60" \
|
-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
|
PLATFORMS: linux/arm64,linux/arm/v7
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-base:
|
build-client:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
#
|
#
|
||||||
# do not run on forks
|
# do not run on forks
|
||||||
@ -23,6 +23,41 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
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
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
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
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="neko" :class="[side ? 'expanded' : '']">
|
<div id="neko" :class="[!videoOnly && side ? 'expanded' : '']">
|
||||||
<template v-if="!$client.supported">
|
<template v-if="!$client.supported">
|
||||||
<neko-unsupported />
|
<neko-unsupported />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<main class="neko-main">
|
<main class="neko-main">
|
||||||
<div v-if="!hideControls" class="header-container">
|
<div v-if="!videoOnly" class="header-container">
|
||||||
<neko-header />
|
<neko-header />
|
||||||
</div>
|
</div>
|
||||||
<div class="video-container">
|
<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>
|
||||||
<div v-if="!hideControls" class="room-container">
|
<div v-if="!videoOnly" class="room-container">
|
||||||
<neko-members />
|
<neko-members />
|
||||||
<div class="room-menu">
|
<div class="room-menu">
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
@ -26,11 +31,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<neko-side v-if="!hideControls && side" />
|
<neko-side v-if="!videoOnly && side" />
|
||||||
<neko-connect v-if="!connected" />
|
<neko-connect v-if="!connected" />
|
||||||
<neko-about v-if="about" />
|
<neko-about v-if="about" />
|
||||||
<notifications
|
<notifications
|
||||||
v-if="!hideControls"
|
v-if="!videoOnly"
|
||||||
group="neko"
|
group="neko"
|
||||||
position="top left"
|
position="top left"
|
||||||
style="top: 50px; pointer-events: none"
|
style="top: 50px; pointer-events: none"
|
||||||
@ -176,10 +181,32 @@
|
|||||||
|
|
||||||
shakeKbd = false
|
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')
|
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 })
|
@Watch('hideControls', { immediate: true })
|
||||||
onHideControls(enabled: boolean) {
|
onHideControls(enabled: boolean) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
@ -76,5 +76,12 @@
|
|||||||
about() {
|
about() {
|
||||||
this.$accessor.client.toggleAbout()
|
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>
|
</script>
|
||||||
|
@ -21,26 +21,29 @@
|
|||||||
@mouseup.stop.prevent="onMouseUp"
|
@mouseup.stop.prevent="onMouseUp"
|
||||||
@mouseenter.stop.prevent="onMouseEnter"
|
@mouseenter.stop.prevent="onMouseEnter"
|
||||||
@mouseleave.stop.prevent="onMouseLeave"
|
@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" />
|
<i class="fas fa-play-circle" />
|
||||||
</div>
|
</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" />
|
<i class="fas fa-volume-up" />
|
||||||
</div>
|
</div>
|
||||||
<div ref="aspect" class="player-aspect" />
|
<div ref="aspect" class="player-aspect" />
|
||||||
</div>
|
</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><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 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
|
<i
|
||||||
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
||||||
@click.stop.prevent="toggleControl"
|
@click.stop.prevent="toggleControl"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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)">
|
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
|
||||||
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
|
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
|
||||||
</li>
|
</li>
|
||||||
@ -106,12 +109,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.request-control {
|
/* usually extra controls are only shown on mobile */
|
||||||
|
&.extra-control {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
&.request-control {
|
&.extra-control {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,13 +226,15 @@
|
|||||||
@Ref('resolution') readonly _resolution!: Resolution
|
@Ref('resolution') readonly _resolution!: Resolution
|
||||||
@Ref('clipboard') readonly _clipboard!: Clipboard
|
@Ref('clipboard') readonly _clipboard!: Clipboard
|
||||||
|
|
||||||
|
// all controls are hidden (e.g. for cast mode)
|
||||||
@Prop(Boolean) readonly hideControls!: boolean
|
@Prop(Boolean) readonly hideControls!: boolean
|
||||||
|
// extra controls are shown (e.g. for embed mode)
|
||||||
|
@Prop(Boolean) readonly extraControls!: boolean
|
||||||
|
|
||||||
private keyboard = GuacamoleKeyboard()
|
private keyboard = GuacamoleKeyboard()
|
||||||
private observer = new ResizeObserver(this.onResize.bind(this))
|
private observer = new ResizeObserver(this.onResize.bind(this))
|
||||||
private focused = false
|
private focused = false
|
||||||
private fullscreen = false
|
private fullscreen = false
|
||||||
private startsMuted = true
|
|
||||||
private mutedOverlay = true
|
private mutedOverlay = true
|
||||||
|
|
||||||
get admin() {
|
get admin() {
|
||||||
@ -361,7 +366,6 @@
|
|||||||
onMutedChanged(muted: boolean) {
|
onMutedChanged(muted: boolean) {
|
||||||
if (this._video && this._video.muted != muted) {
|
if (this._video && this._video.muted != muted) {
|
||||||
this._video.muted = muted
|
this._video.muted = muted
|
||||||
this.startsMuted = muted
|
|
||||||
|
|
||||||
if (!muted) {
|
if (!muted) {
|
||||||
this.mutedOverlay = false
|
this.mutedOverlay = false
|
||||||
@ -384,9 +388,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('playing')
|
@Watch('playing')
|
||||||
onPlayingChanged(playing: boolean) {
|
async onPlayingChanged(playing: boolean) {
|
||||||
if (this._video && this._video.paused && playing) {
|
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) {
|
if (this._video && !this._video.paused && !playing) {
|
||||||
@ -424,11 +448,6 @@
|
|||||||
this._video.addEventListener('canplaythrough', () => {
|
this._video.addEventListener('canplaythrough', () => {
|
||||||
this.$accessor.video.setPlayable(true)
|
this.$accessor.video.setPlayable(true)
|
||||||
if (this.autoplay) {
|
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.$nextTick(() => {
|
||||||
this.$accessor.video.play()
|
this.$accessor.video.play()
|
||||||
})
|
})
|
||||||
@ -560,6 +579,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playAndUnmute() {
|
||||||
|
this.$accessor.video.play()
|
||||||
|
this.$accessor.video.setMuted(false)
|
||||||
|
}
|
||||||
|
|
||||||
unmute() {
|
unmute() {
|
||||||
this.$accessor.video.setMuted(false)
|
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) {
|
onMouseDown(e: MouseEvent) {
|
||||||
if (!this.hosting) {
|
if (!this.hosting) {
|
||||||
this.$emit('control-attempt', e)
|
this.$emit('control-attempt', e)
|
||||||
|
@ -203,11 +203,12 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer = new RTCPeerConnection()
|
|
||||||
if (lite !== true) {
|
if (lite !== true) {
|
||||||
this._peer = new RTCPeerConnection({
|
this._peer = new RTCPeerConnection({
|
||||||
iceServers: servers,
|
iceServers: servers,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
this._peer = new RTCPeerConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer.onconnectionstatechange = () => {
|
this._peer.onconnectionstatechange = () => {
|
||||||
@ -251,11 +252,28 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
|
|
||||||
this._peer.ontrack = this.onTrack.bind(this)
|
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._peer.onnegotiationneeded = async () => {
|
||||||
this.emit('warn', `negotiation is needed`)
|
this.emit('warn', `negotiation is needed`)
|
||||||
|
|
||||||
const d = await this._peer!.createOffer()
|
const d = await this._peer!.createOffer()
|
||||||
this._peer!.setLocalDescription(d)
|
await this._peer!.setLocalDescription(d)
|
||||||
|
|
||||||
this._ws!.send(
|
this._ws!.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@ -277,10 +295,10 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer.setRemoteDescription({ type: 'offer', sdp })
|
await this._peer.setRemoteDescription({ type: 'offer', sdp })
|
||||||
|
|
||||||
for (const candidate of this._candidates) {
|
for (const candidate of this._candidates) {
|
||||||
this._peer.addIceCandidate(candidate)
|
await this._peer.addIceCandidate(candidate)
|
||||||
}
|
}
|
||||||
this._candidates = []
|
this._candidates = []
|
||||||
|
|
||||||
@ -310,7 +328,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer.setRemoteDescription({ type: 'answer', sdp })
|
await this._peer.setRemoteDescription({ type: 'answer', sdp })
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessage(e: MessageEvent) {
|
private async onMessage(e: MessageEvent) {
|
||||||
|
@ -6,5 +6,6 @@ Vue.use(VueI18n)
|
|||||||
|
|
||||||
export const i18n = new VueI18n({
|
export const i18n = new VueI18n({
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
messages,
|
messages,
|
||||||
})
|
})
|
||||||
|
@ -2,19 +2,41 @@
|
|||||||
|
|
||||||
## master branch
|
## 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
|
### New Features
|
||||||
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
|
- 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`.
|
- 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
|
### Bugs
|
||||||
- Fixed TCP mux occasional freeze by adding write buffer to it.
|
- 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 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 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 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
|
### Misc
|
||||||
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
|
- 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.
|
- 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)
|
## [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/xfce:latest`
|
||||||
- `ghcr.io/m1k1o/neko/kde: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-firefox:latest`
|
||||||
- `ghcr.io/m1k1o/neko/arm-chromium: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-xfce:latest`
|
||||||
- `ghcr.io/m1k1o/neko/intel-kde: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.
|
GHCR images are built using GitHub actions for every tag.
|
||||||
|
|
||||||
### Networking:
|
### 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.
|
- 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.
|
- 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?
|
### 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.
|
- 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'`
|
- 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,
|
"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?
|
### Want to use VPN for your n.eko browsing?
|
||||||
- Check this out: https://github.com/m1k1o/neko-vpn
|
- Check this out: https://github.com/m1k1o/neko-vpn
|
||||||
|
|
||||||
@ -172,6 +254,9 @@ services:
|
|||||||
- Adding `?pwd=<password>` will prefill password.
|
- Adding `?pwd=<password>` will prefill password.
|
||||||
- Adding `?usr=<display-name>` will prefill username.
|
- Adding `?usr=<display-name>` will prefill username.
|
||||||
- Adding `?cast=1` will hide all control and show only video.
|
- 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`
|
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
|
||||||
|
|
||||||
### Screen size
|
### 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.
|
- Control protection means, users can gain control only if at least one admin is in the room.
|
||||||
- e.g. `false`
|
- e.g. `false`
|
||||||
#### `NEKO_IMPLICIT_CONTROL`:
|
#### `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`
|
- e.g. `false`
|
||||||
#### `NEKO_LOCKS`:
|
#### `NEKO_LOCKS`:
|
||||||
- Resources, that will be locked when starting, separated by whitespace.
|
- Resources, that will be locked when starting, separated by whitespace.
|
||||||
@ -79,13 +79,14 @@ nat1to1: <ip>
|
|||||||
- `gstreamer1.0-plugins-good`
|
- `gstreamer1.0-plugins-good`
|
||||||
- `gstreamer1.0-plugins-bad`
|
- `gstreamer1.0-plugins-bad`
|
||||||
- `gstreamer1.0-plugins-ugly`
|
- `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`:
|
#### `NEKO_MAX_FPS`:
|
||||||
- The resulting stream frames per seconds should be capped *(0 for uncapped)*.
|
- The resulting stream frames per seconds should be capped *(0 for uncapped)*.
|
||||||
- e.g. `0`
|
- e.g. `0`
|
||||||
#### `NEKO_HWENC`:
|
#### `NEKO_HWENC`:
|
||||||
- Use hardware accelerated encoding, for now supported only `VAAPI`.
|
- none *(default CPU encoding)*
|
||||||
- e.g. `VAAPI`
|
- vaapi
|
||||||
|
- nvenc
|
||||||
|
|
||||||
### Audio
|
### Audio
|
||||||
|
|
||||||
@ -167,7 +168,7 @@ Flags:
|
|||||||
--cert string path to the SSL cert used to secure the neko server
|
--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
|
--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 [*])
|
--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")
|
--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")
|
--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)
|
--file_transfer_enabled enable file transfer feature (default false)
|
||||||
|
@ -72,7 +72,8 @@ services:
|
|||||||
version: "3.4"
|
version: "3.4"
|
||||||
services:
|
services:
|
||||||
neko:
|
neko:
|
||||||
image: "m1k1o/neko:arm-chromium"
|
# see docs for more variants
|
||||||
|
image: "ghcr.io/m1k1o/neko/arm-chromium:latest"
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
# increase on rpi's with more then 1gb ram.
|
# increase on rpi's with more then 1gb ram.
|
||||||
shm_size: "520mb"
|
shm_size: "520mb"
|
||||||
@ -95,9 +96,9 @@ services:
|
|||||||
! videoconvert
|
! videoconvert
|
||||||
! queue
|
! queue
|
||||||
! video/x-raw,framerate=30/1,format=NV12
|
! 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
|
! 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
|
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.
|
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."
|
||||||
|
```
|
||||||
|
35
server/build
35
server/build
@ -1,10 +1,35 @@
|
|||||||
#!/bin/bash
|
#!/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'`
|
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
||||||
GIT_COMMIT=`git rev-parse --short HEAD`
|
|
||||||
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
|
|
||||||
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
|
|
||||||
|
|
||||||
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
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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 (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
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/go-chi/cors v1.2.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/pion/ice/v2 v2.3.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.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 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
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/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
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 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
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=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"m1k1o/neko/internal/capture/gst"
|
"m1k1o/neko/internal/capture/gst"
|
||||||
|
"m1k1o/neko/internal/config"
|
||||||
"m1k1o/neko/internal/types/codec"
|
"m1k1o/neko/internal/types/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url
|
|||||||
return pipelineStr, nil
|
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"
|
pipelineStr := " ! appsink name=appsinkvideo"
|
||||||
|
|
||||||
// if using custom pipeline
|
// if using custom pipeline
|
||||||
@ -68,7 +69,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
|||||||
|
|
||||||
switch rtpCodec.Name {
|
switch rtpCodec.Name {
|
||||||
case codec.VP8().Name:
|
case codec.VP8().Name:
|
||||||
if hwenc == "VAAPI" {
|
if hwenc == config.HwEncVAAPI {
|
||||||
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -138,35 +139,40 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
|||||||
return "", err
|
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)
|
vbvbuf := uint(1000)
|
||||||
if bitrate > 1000 {
|
if bitrate > 1000 {
|
||||||
vbvbuf = bitrate
|
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:
|
default:
|
||||||
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
||||||
|
@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types/codec"
|
"m1k1o/neko/internal/types/codec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -9,11 +10,19 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HwEnc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
HwEncNone HwEnc = iota
|
||||||
|
HwEncVAAPI
|
||||||
|
HwEncNVENC
|
||||||
|
)
|
||||||
|
|
||||||
type Capture struct {
|
type Capture struct {
|
||||||
// video
|
// video
|
||||||
Display string
|
Display string
|
||||||
VideoCodec codec.RTPCodec
|
VideoCodec codec.RTPCodec
|
||||||
VideoHWEnc string // TODO: Pipeline builder.
|
VideoHWEnc HwEnc // TODO: Pipeline builder.
|
||||||
VideoBitrate uint // TODO: Pipeline builder.
|
VideoBitrate uint // TODO: Pipeline builder.
|
||||||
VideoMaxFPS int16 // TODO: Pipeline builder.
|
VideoMaxFPS int16 // TODO: Pipeline builder.
|
||||||
VideoPipeline string
|
VideoPipeline string
|
||||||
@ -92,7 +101,7 @@ func (Capture) Init(cmd *cobra.Command) error {
|
|||||||
// audio
|
// 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 {
|
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
||||||
return err
|
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")
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_VIDEO_CODEC=av1' instead")
|
||||||
}
|
}
|
||||||
|
|
||||||
videoHWEnc := ""
|
videoHWEnc := strings.ToLower(viper.GetString("hwenc"))
|
||||||
if viper.GetString("hwenc") == "VAAPI" {
|
switch videoHWEnc {
|
||||||
videoHWEnc = "VAAPI"
|
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.VideoBitrate = viper.GetUint("video_bitrate")
|
||||||
s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
|
s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
|
||||||
|
@ -14,6 +14,7 @@ type Server struct {
|
|||||||
Cert string
|
Cert string
|
||||||
Key string
|
Key string
|
||||||
Bind string
|
Bind string
|
||||||
|
Proxy bool
|
||||||
Static string
|
Static string
|
||||||
PathPrefix string
|
PathPrefix string
|
||||||
CORS []string
|
CORS []string
|
||||||
@ -35,6 +36,11 @@ func (Server) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
|
||||||
|
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().String("static", "./www", "path to neko client files to serve")
|
cmd.PersistentFlags().String("static", "./www", "path to neko client files to serve")
|
||||||
if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil {
|
if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -57,6 +63,7 @@ func (s *Server) Set() {
|
|||||||
s.Cert = viper.GetString("cert")
|
s.Cert = viper.GetString("cert")
|
||||||
s.Key = viper.GetString("key")
|
s.Key = viper.GetString("key")
|
||||||
s.Bind = viper.GetString("bind")
|
s.Bind = viper.GetString("bind")
|
||||||
|
s.Proxy = viper.GetBool("proxy")
|
||||||
s.Static = viper.GetString("static")
|
s.Static = viper.GetString("static")
|
||||||
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))
|
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
type WebSocket struct {
|
type WebSocket struct {
|
||||||
Password string
|
Password string
|
||||||
AdminPassword string
|
AdminPassword string
|
||||||
Proxy bool
|
|
||||||
Locks []string
|
Locks []string
|
||||||
|
|
||||||
ControlProtection bool
|
ControlProtection bool
|
||||||
@ -30,11 +29,6 @@ func (WebSocket) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
|
|
||||||
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().StringSlice("locks", []string{}, "resources, that will be locked when starting (control, login)")
|
cmd.PersistentFlags().StringSlice("locks", []string{}, "resources, that will be locked when starting (control, login)")
|
||||||
if err := viper.BindPFlag("locks", cmd.PersistentFlags().Lookup("locks")); err != nil {
|
if err := viper.BindPFlag("locks", cmd.PersistentFlags().Lookup("locks")); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -63,7 +57,6 @@ func (WebSocket) Init(cmd *cobra.Command) error {
|
|||||||
func (s *WebSocket) Set() {
|
func (s *WebSocket) Set() {
|
||||||
s.Password = viper.GetString("password")
|
s.Password = viper.GetString("password")
|
||||||
s.AdminPassword = viper.GetString("password_admin")
|
s.AdminPassword = viper.GetString("password_admin")
|
||||||
s.Proxy = viper.GetBool("proxy")
|
|
||||||
s.Locks = viper.GetStringSlice("locks")
|
s.Locks = viper.GetStringSlice("locks")
|
||||||
|
|
||||||
s.ControlProtection = viper.GetBool("control_protection")
|
s.ControlProtection = viper.GetBool("control_protection")
|
||||||
|
@ -11,8 +11,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -35,6 +35,9 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
|
|||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
router.Use(middleware.RequestID) // Create a request ID for each request
|
router.Use(middleware.RequestID) // Create a request ID for each request
|
||||||
|
if conf.Proxy {
|
||||||
|
router.Use(middleware.RealIP)
|
||||||
|
}
|
||||||
router.Use(middleware.RequestLogger(&logformatter{logger}))
|
router.Use(middleware.RequestLogger(&logformatter{logger}))
|
||||||
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
||||||
router.Use(middleware.Compress(5, "application/octet-stream"))
|
router.Use(middleware.Compress(5, "application/octet-stream"))
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/rs/zerolog"
|
"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 {
|
func (session *Session) SignalRemoteOffer(sdp string) error {
|
||||||
if session.peer == nil {
|
if session.peer == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -154,14 +165,12 @@ func (session *Session) SignalRemoteAnswer(sdp string) error {
|
|||||||
return session.peer.SetAnswer(sdp)
|
return session.peer.SetAnswer(sdp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) SignalCandidate(data string) error {
|
func (session *Session) SignalRemoteCandidate(data string) error {
|
||||||
if session.socket == nil {
|
if session.socket == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return session.socket.Send(&message.SignalCandidate{
|
session.logger.Info().Msg("signal update - RemoteCandidate")
|
||||||
Event: event.SIGNAL_CANDIDATE,
|
return session.peer.SetCandidate(data)
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) destroy() error {
|
func (session *Session) destroy() error {
|
||||||
|
@ -40,9 +40,10 @@ type Session interface {
|
|||||||
Send(v interface{}) error
|
Send(v interface{}) error
|
||||||
SignalLocalOffer(sdp string) error
|
SignalLocalOffer(sdp string) error
|
||||||
SignalLocalAnswer(sdp string) error
|
SignalLocalAnswer(sdp string) error
|
||||||
|
SignalLocalCandidate(data string) error
|
||||||
SignalRemoteOffer(sdp string) error
|
SignalRemoteOffer(sdp string) error
|
||||||
SignalRemoteAnswer(sdp string) error
|
SignalRemoteAnswer(sdp string) error
|
||||||
SignalCandidate(data string) error
|
SignalRemoteCandidate(data string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionManager interface {
|
type SessionManager interface {
|
||||||
|
@ -21,6 +21,7 @@ type Peer interface {
|
|||||||
CreateAnswer() (string, error)
|
CreateAnswer() (string, error)
|
||||||
SetOffer(sdp string) error
|
SetOffer(sdp string) error
|
||||||
SetAnswer(sdp string) error
|
SetAnswer(sdp string) error
|
||||||
|
SetCandidate(candidateString string) error
|
||||||
WriteData(v interface{}) error
|
WriteData(v interface{}) error
|
||||||
Destroy() error
|
Destroy() error
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -31,21 +31,10 @@ func GetIP(serverUrl string) (string, error) {
|
|||||||
}
|
}
|
||||||
defer rsp.Body.Close()
|
defer rsp.Body.Close()
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(rsp.Body)
|
buf, err := io.ReadAll(rsp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(bytes.TrimSpace(buf)), nil
|
return string(bytes.TrimSpace(buf)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHttpRequestIP(r *http.Request, proxy bool) string {
|
|
||||||
IPAddress := r.Header.Get("X-Real-Ip")
|
|
||||||
if IPAddress == "" {
|
|
||||||
IPAddress = r.Header.Get("X-Forwarded-For")
|
|
||||||
}
|
|
||||||
if IPAddress == "" || !proxy {
|
|
||||||
IPAddress = r.RemoteAddr
|
|
||||||
}
|
|
||||||
return IPAddress
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v3"
|
"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})
|
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 {
|
func (peer *Peer) WriteData(v interface{}) error {
|
||||||
peer.mu.Lock()
|
peer.mu.Lock()
|
||||||
defer peer.mu.Unlock()
|
defer peer.mu.Unlock()
|
||||||
|
@ -123,7 +123,6 @@ func (manager *WebRTCManager) initAPI() error {
|
|||||||
LoggerFactory: logger,
|
LoggerFactory: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
|
||||||
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
|
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
|
||||||
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
|
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
|
||||||
settings.SetSRTPReplayProtectionWindow(512)
|
settings.SetSRTPReplayProtectionWindow(512)
|
||||||
@ -168,12 +167,15 @@ func (manager *WebRTCManager) initAPI() error {
|
|||||||
|
|
||||||
networkType = append(networkType, webrtc.NetworkTypeUDP4)
|
networkType = append(networkType, webrtc.NetworkTypeUDP4)
|
||||||
manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX")
|
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)
|
settings.SetNetworkTypes(networkType)
|
||||||
}
|
|
||||||
|
|
||||||
// Create MediaEngine with selected codecs
|
// Create MediaEngine with selected codecs
|
||||||
engine := webrtc.MediaEngine{}
|
engine := webrtc.MediaEngine{}
|
||||||
@ -299,7 +301,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (type
|
|||||||
return
|
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")
|
manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
utils.Unmarshal(payload, raw, func() error {
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
return h.signalRemoteAnswer(id, session, payload)
|
return h.signalRemoteAnswer(id, session, payload)
|
||||||
}), "%s failed", header.Event)
|
}), "%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
|
// Control Events
|
||||||
case event.CONTROL_RELEASE:
|
case event.CONTROL_RELEASE:
|
||||||
|
@ -45,3 +45,7 @@ func (h *MessageHandler) signalRemoteAnswer(id string, session types.Session, pa
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) signalRemoteCandidate(id string, session types.Session, payload *message.SignalCandidate) error {
|
||||||
|
return session.SignalRemoteCandidate(payload.Data)
|
||||||
|
}
|
||||||
|
@ -290,7 +290,7 @@ func (ws *WebSocketHandler) Upgrade(w http.ResponseWriter, r *http.Request) erro
|
|||||||
socket := &WebSocket{
|
socket := &WebSocket{
|
||||||
id: id,
|
id: id,
|
||||||
ws: ws,
|
ws: ws,
|
||||||
address: utils.GetHttpRequestIP(r, ws.conf.Proxy),
|
address: r.RemoteAddr,
|
||||||
connection: connection,
|
connection: connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"m1k1o/neko/internal/capture"
|
"m1k1o/neko/internal/capture"
|
||||||
"m1k1o/neko/internal/config"
|
"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 (
|
var (
|
||||||
@ -35,13 +36,8 @@ var (
|
|||||||
gitCommit = "dev"
|
gitCommit = "dev"
|
||||||
//
|
//
|
||||||
gitBranch = "dev"
|
gitBranch = "dev"
|
||||||
|
//
|
||||||
// Major version when you make incompatible API changes,
|
gitTag = "dev"
|
||||||
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Service *Neko
|
var Service *Neko
|
||||||
@ -49,11 +45,9 @@ var Service *Neko
|
|||||||
func init() {
|
func init() {
|
||||||
Service = &Neko{
|
Service = &Neko{
|
||||||
Version: &Version{
|
Version: &Version{
|
||||||
Major: major,
|
|
||||||
Minor: minor,
|
|
||||||
Patch: patch,
|
|
||||||
GitCommit: gitCommit,
|
GitCommit: gitCommit,
|
||||||
GitBranch: gitBranch,
|
GitBranch: gitBranch,
|
||||||
|
GitTag: gitTag,
|
||||||
BuildDate: buildDate,
|
BuildDate: buildDate,
|
||||||
GoVersion: runtime.Version(),
|
GoVersion: runtime.Version(),
|
||||||
Compiler: runtime.Compiler,
|
Compiler: runtime.Compiler,
|
||||||
@ -69,11 +63,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Major string
|
|
||||||
Minor string
|
|
||||||
Patch string
|
|
||||||
GitCommit string
|
GitCommit string
|
||||||
GitBranch string
|
GitBranch string
|
||||||
|
GitTag string
|
||||||
BuildDate string
|
BuildDate string
|
||||||
GoVersion string
|
GoVersion string
|
||||||
Compiler string
|
Compiler string
|
||||||
@ -81,20 +73,25 @@ type Version struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Version) String() string {
|
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 {
|
func (i *Version) Details() string {
|
||||||
return fmt.Sprintf(
|
return "\n" + strings.Join([]string{
|
||||||
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
|
fmt.Sprintf("Version %s", i.String()),
|
||||||
fmt.Sprintf("Version %s.%s.%s", i.Major, i.Minor, i.Patch),
|
|
||||||
fmt.Sprintf("GitCommit %s", i.GitCommit),
|
fmt.Sprintf("GitCommit %s", i.GitCommit),
|
||||||
fmt.Sprintf("GitBranch %s", i.GitBranch),
|
fmt.Sprintf("GitBranch %s", i.GitBranch),
|
||||||
|
fmt.Sprintf("GitTag %s", i.GitTag),
|
||||||
fmt.Sprintf("BuildDate %s", i.BuildDate),
|
fmt.Sprintf("BuildDate %s", i.BuildDate),
|
||||||
fmt.Sprintf("GoVersion %s", i.GoVersion),
|
fmt.Sprintf("GoVersion %s", i.GoVersion),
|
||||||
fmt.Sprintf("Compiler %s", i.Compiler),
|
fmt.Sprintf("Compiler %s", i.Compiler),
|
||||||
fmt.Sprintf("Platform %s", i.Platform),
|
fmt.Sprintf("Platform %s", i.Platform),
|
||||||
)
|
}, "\n") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
type Neko struct {
|
type Neko struct {
|
||||||
|
Reference in New Issue
Block a user