mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
0dd9597519 | |||
334fcef407 | |||
e868ad4061 | |||
b41d0bf956 | |||
b62fa6ab8b | |||
8dba9cff44 | |||
217cc451ea | |||
1f81bd3efc | |||
50e5483661 | |||
d2765c30fd | |||
76fc892823 | |||
ea99ce7753 | |||
646eaced29 | |||
9104953ad9 | |||
c40243635f | |||
8059002475 | |||
9daf83cc52 | |||
0cebe465a2 | |||
d9403d9c14 | |||
8604a30744 | |||
957e893cf6 | |||
55005a6f8d | |||
bc7aae0401 | |||
addb6b6d1b | |||
d2ddae8f45 | |||
724f5fe384 | |||
b0e3e29658 | |||
373dcb4f32 | |||
72c0070a3a | |||
009ceddbd3 | |||
fdf17cfe77 | |||
628c6a1b77 | |||
79a1c41938 | |||
64b79f4579 | |||
8d0468ea62 | |||
89737dd4ce | |||
2649594c2e | |||
f3080713ce | |||
6e62b796fc | |||
4094639ea9 | |||
c45a315d9b | |||
ee13e40d4c | |||
dfe8b8b57d | |||
12623866b3 | |||
161d121e59 | |||
5690a849e2 | |||
cfc6bd417f | |||
32472a70bc |
@ -1,7 +1,7 @@
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.18-bullseye as server
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
@ -32,7 +32,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:14-bullseye-slim as client
|
||||
FROM node:18-bullseye-slim as client
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
@ -61,11 +61,6 @@ ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
RUN set -eux; \
|
||||
#
|
||||
# add non-free repo for intel drivers
|
||||
echo deb http://deb.debian.org/debian bullseye main contrib non-free > /etc/apt/sources.list; \
|
||||
echo deb http://deb.debian.org/debian-security/ bullseye-security main contrib non-free >> /etc/apt/sources.list; \
|
||||
echo deb http://deb.debian.org/debian bullseye-updates main contrib non-free >> /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
#
|
||||
# install dependencies
|
||||
@ -73,13 +68,9 @@ RUN set -eux; \
|
||||
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; \
|
||||
#
|
||||
# intel driver + vaapi
|
||||
apt-get install -y --no-install-recommends intel-media-va-driver-non-free libva2 vainfo; \
|
||||
#
|
||||
# gst + vaapi plugin
|
||||
# gst
|
||||
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio \
|
||||
gstreamer1.0-vaapi ;\
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio; \
|
||||
#
|
||||
# install fonts
|
||||
apt-get install -y --no-install-recommends \
|
||||
@ -88,7 +79,9 @@ RUN set -eux; \
|
||||
# Japanese fonts
|
||||
fonts-takao-mincho \
|
||||
# Chinese fonts
|
||||
fonts-wqy-zenhei; \
|
||||
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||
# Korean fonts
|
||||
fonts-wqy-microhei; \
|
||||
#
|
||||
# create a non-root user
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
@ -122,7 +115,6 @@ 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/add-render-group.sh /usr/bin/add-render-group.sh
|
||||
|
||||
#
|
||||
# set default envs
|
||||
@ -131,7 +123,6 @@ ENV DISPLAY=:99.0
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
ENV RENDER_GID=
|
||||
|
||||
#
|
||||
# copy static files from previous stages
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM arm32v7/golang:1.18-buster as server
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
@ -13,7 +13,7 @@ RUN set -eux; apt-get update; \
|
||||
# install libclipboard
|
||||
set -eux; \
|
||||
cd /tmp; \
|
||||
git clone https://github.com/jtanx/libclipboard; \
|
||||
git clone --depth=1 https://github.com/jtanx/libclipboard; \
|
||||
cd libclipboard; \
|
||||
cmake .; \
|
||||
make -j4; \
|
||||
@ -32,7 +32,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:14-buster-slim as client
|
||||
FROM node:18-bullseye-slim as client
|
||||
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
@ -53,7 +53,7 @@ RUN npm run build
|
||||
#
|
||||
# STAGE 3: RUNTIME
|
||||
#
|
||||
FROM arm32v7/debian:buster-slim
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
#
|
||||
# avoid warnings by switching to noninteractive
|
||||
@ -65,19 +65,29 @@ ARG USERNAME=neko
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
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 libvpx5; \
|
||||
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6; \
|
||||
#
|
||||
# gst
|
||||
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio gstreamer1.0-omx; \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio \
|
||||
gstreamer1.0-omx; \
|
||||
#
|
||||
# fonts
|
||||
apt-get install -y --no-install-recommends fonts-takao-mincho fonts-wqy-zenhei; \
|
||||
# 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; \
|
||||
@ -131,4 +141,3 @@ HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
#
|
||||
# run neko
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]
|
||||
|
||||
|
149
.docker/base/Dockerfile.intel
Normal file
149
.docker/base/Dockerfile.intel
Normal file
@ -0,0 +1,149 @@
|
||||
#
|
||||
# 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 go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:18-bullseye-slim as client
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
COPY client/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
#
|
||||
# build client
|
||||
COPY client/ .
|
||||
RUN npm run build
|
||||
|
||||
#
|
||||
# STAGE 3: RUNTIME
|
||||
#
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
#
|
||||
# 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; \
|
||||
#
|
||||
# add non-free repo for intel drivers
|
||||
echo deb http://deb.debian.org/debian bullseye main contrib non-free > /etc/apt/sources.list; \
|
||||
echo deb http://deb.debian.org/debian-security/ bullseye-security main contrib non-free >> /etc/apt/sources.list; \
|
||||
echo deb http://deb.debian.org/debian bullseye-updates main contrib non-free >> /etc/apt/sources.list; \
|
||||
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; \
|
||||
#
|
||||
# intel driver + vaapi
|
||||
apt-get install -y --no-install-recommends intel-media-va-driver-non-free libva2 vainfo; \
|
||||
#
|
||||
# gst + vaapi plugin
|
||||
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio \
|
||||
gstreamer1.0-vaapi; \
|
||||
#
|
||||
# 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; \
|
||||
#
|
||||
# setup pulseaudio
|
||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
||||
#
|
||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||
mkdir /tmp/.X11-unix; \
|
||||
chmod 1777 /tmp/.X11-unix; \
|
||||
chown $USERNAME /tmp/.X11-unix/; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown $USERNAME /var/log/neko/; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# 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/intel/supervisord.conf /etc/neko/supervisord.conf
|
||||
COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
||||
COPY .docker/base/intel/add-render-group.sh /usr/bin/add-render-group.sh
|
||||
|
||||
#
|
||||
# set default envs
|
||||
ENV USER=$USERNAME
|
||||
ENV DISPLAY=:99.0
|
||||
ENV NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
ENV NEKO_HWENC=VAAPI
|
||||
ENV RENDER_GID=
|
||||
|
||||
#
|
||||
# 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"]
|
197
.docker/base/Dockerfile.nvidia
Normal file
197
.docker/base/Dockerfile.nvidia
Normal file
@ -0,0 +1,197 @@
|
||||
ARG UBUNTU_RELEASE=20.04
|
||||
ARG CUDA_VERSION=11.2.2
|
||||
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.18-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 go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:14-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 nvcr.io/nvidia/cudagl:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_RELEASE} as runtime
|
||||
|
||||
ARG UBUNTU_RELEASE
|
||||
ARG CUDA_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; \
|
||||
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; \
|
||||
#
|
||||
# hardware acclerations utilities
|
||||
apt-get install -y --no-install-recommends libgtk-3-bin mesa-utils mesa-utils-extra mesa-va-drivers mesa-vulkan-drivers libvulkan-dev libvulkan-dev:i386 vdpauinfo; \
|
||||
#
|
||||
# gst
|
||||
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio; \
|
||||
#
|
||||
# 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; \
|
||||
#
|
||||
# setup pulseaudio
|
||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
||||
#
|
||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||
mkdir /tmp/.X11-unix; \
|
||||
chmod 1777 /tmp/.X11-unix; \
|
||||
chown $USERNAME /tmp/.X11-unix/; \
|
||||
#
|
||||
# make directories for neko
|
||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
||||
chmod 1777 /var/log/neko; \
|
||||
chown $USERNAME /var/log/neko/; \
|
||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# install and configure Vulkan manually
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
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; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*; \
|
||||
#
|
||||
# configure vulkan
|
||||
VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)'); \
|
||||
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
|
||||
ARG VIRTUALGL_VERSION=3.1
|
||||
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 NEKO_PASSWORD=neko
|
||||
ENV NEKO_PASSWORD_ADMIN=admin
|
||||
ENV NEKO_BIND=:8080
|
||||
|
||||
#
|
||||
# copy static files from previous stages
|
||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||
COPY --from=client /src/dist/ /var/www
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
|
||||
|
||||
#
|
||||
# run neko
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]
|
@ -1,5 +1,8 @@
|
||||
#!/usr/bin/pulseaudio -nF
|
||||
|
||||
### Create virtual output device sink
|
||||
load-module module-null-sink sink_name=audio_output sink_properties=device.description="Virtual\ Audio\ Output"
|
||||
|
||||
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
|
||||
load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1
|
||||
|
||||
|
69
.docker/base/intel/supervisord.conf
Normal file
69
.docker/base/intel/supervisord.conf
Normal file
@ -0,0 +1,69 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
pidfile=/var/run/supervisord.pid
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
loglevel=debug
|
||||
|
||||
[include]
|
||||
files=/etc/neko/supervisord/*.conf
|
||||
|
||||
[program:rendergroup-init]
|
||||
environment=RENDER_GID="%(ENV_RENDER_GID)s",USER="%(ENV_USER)s"
|
||||
command=/usr/bin/add-render-group.sh
|
||||
startsecs=0
|
||||
startretries=0
|
||||
autorestart=false
|
||||
priority=10
|
||||
user=root
|
||||
stdout_logfile=/var/log/neko/rendergroup.log
|
||||
stdout_logfile_maxbytes=1MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:dbus]
|
||||
environment=HOME="/root",USER="root"
|
||||
command=/usr/bin/dbus
|
||||
autorestart=true
|
||||
priority=100
|
||||
user=root
|
||||
stdout_logfile=/var/log/neko/dbus.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:x-server]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s"
|
||||
command=/usr/bin/X -config /etc/neko/xorg.conf %(ENV_DISPLAY)s
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/xorg.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:pulseaudio]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/pulseaudio --log-level=info --disallow-module-loading --disallow-exit --exit-idle-time=-1
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/pulseaudio.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:neko]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/neko serve --static "/var/www"
|
||||
stopsignal=INT
|
||||
stopwaitsecs=5
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/neko.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
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
|
@ -9,19 +9,6 @@ loglevel=debug
|
||||
[include]
|
||||
files=/etc/neko/supervisord/*.conf
|
||||
|
||||
[program:rendergroup-init]
|
||||
environment=RENDER_GID="%(ENV_RENDER_GID)s",USER="%(ENV_USER)s"
|
||||
command=/usr/bin/add-render-group.sh
|
||||
startsecs=0
|
||||
startretries=0
|
||||
autorestart=false
|
||||
priority=10
|
||||
user=root
|
||||
stdout_logfile=/var/log/neko/rendergroup.log
|
||||
stdout_logfile_maxbytes=1MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:dbus]
|
||||
environment=HOME="/root",USER="root"
|
||||
command=/usr/bin/dbus
|
||||
|
23
.docker/brave/Dockerfile.nvidia
Normal file
23
.docker/brave/Dockerfile.nvidia
Normal file
@ -0,0 +1,23 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends apt-transport-https curl openbox; \
|
||||
#
|
||||
# install brave browser
|
||||
curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg; \
|
||||
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" \
|
||||
| tee /etc/apt/sources.list.d/brave-browser-release.list; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends brave-browser; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/brave.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/brave/Default/Preferences
|
||||
COPY policies.json /etc/brave/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:brave]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/brave-browser --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/brave --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/brave-browser
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/brave
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/brave/supervisord.nvidia.conf
Normal file
36
.docker/brave/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:brave]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/brave-browser
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/brave
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/brave.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
@ -77,6 +77,36 @@ build_arm() {
|
||||
fi
|
||||
}
|
||||
|
||||
build_intel() {
|
||||
if [ "$1" = "base" ]
|
||||
then
|
||||
# build intel base
|
||||
docker build -t "${BUILD_IMAGE}:intel-base" -f base/Dockerfile.intel "${BASE}"
|
||||
elif [ -f "$1/Dockerfile.intel" ]
|
||||
then
|
||||
# build dedicated intel image
|
||||
docker build -t "${BUILD_IMAGE}:intel-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:intel-base" -f "$1/Dockerfile.intel" "$1/"
|
||||
else
|
||||
# try to build intel image with common Dockerfile
|
||||
docker build -t "${BUILD_IMAGE}:intel-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:intel-base" -f "$1/Dockerfile" "$1/"
|
||||
fi
|
||||
}
|
||||
|
||||
build_nvidia() {
|
||||
if [ "$1" = "base" ]
|
||||
then
|
||||
# build nvidia base
|
||||
docker build -t "${BUILD_IMAGE}:nvidia-base" -f base/Dockerfile.nvidia "${BASE}"
|
||||
elif [ -f "$1/Dockerfile.nvidia" ]
|
||||
then
|
||||
# build dedicated nvidia image
|
||||
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile.nvidia" "$1/"
|
||||
else
|
||||
# try to build nvidia image with common Dockerfile
|
||||
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile" "$1/"
|
||||
fi
|
||||
}
|
||||
|
||||
case $1 in
|
||||
client) build_client;;
|
||||
server) build_server;;
|
||||
@ -84,6 +114,12 @@ case $1 in
|
||||
# build arm- images
|
||||
arm-*) build_arm "${1#arm-}";;
|
||||
|
||||
# build intel- images
|
||||
intel-*) build_intel "${1#intel-}";;
|
||||
|
||||
# build nvidia- images
|
||||
nvidia-*) build_nvidia "${1#nvidia-}";;
|
||||
|
||||
# build images
|
||||
*) build "$1";;
|
||||
esac
|
||||
|
@ -4,7 +4,7 @@ FROM $BASE_IMAGE
|
||||
#
|
||||
# install neko chromium
|
||||
RUN set -eux; apt-get update; \
|
||||
# TODO: Bring back DRM support with arm32v7/debian:buster-slim image.
|
||||
# TODO: Bring back DRM support.
|
||||
apt-get install -y --no-install-recommends chromium openbox; \
|
||||
#
|
||||
# clean up
|
||||
|
37
.docker/chromium/Dockerfile.nvidia
Normal file
37
.docker/chromium/Dockerfile.nvidia
Normal file
@ -0,0 +1,37 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
#
|
||||
# install neko chromium
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends software-properties-common; \
|
||||
# chromium-browser from default repo needs snap to be installed
|
||||
# and nvidia base is ubuntu not debian
|
||||
add-apt-repository ppa:system76/pop; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends unzip chromium openbox; \
|
||||
#
|
||||
# install widevine module
|
||||
CHROMIUM_DIR="/usr/lib/chromium"; \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
||||
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
|
||||
mkdir -p "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64"; \
|
||||
unzip -p /tmp/widevine.zip LICENSE.txt > "${CHROMIUM_DIR}/WidevineCdm/LICENSE"; \
|
||||
unzip -p /tmp/widevine.zip manifest.json > "${CHROMIUM_DIR}/WidevineCdm/manifest.json"; \
|
||||
unzip -p /tmp/widevine.zip libwidevinecdm.so > "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64/libwidevinecdm.so"; \
|
||||
find "${CHROMIUM_DIR}/WidevineCdm" -type d -exec chmod 0755 '{}' \;; \
|
||||
find "${CHROMIUM_DIR}/WidevineCdm" -type f -exec chmod 0644 '{}' \;; \
|
||||
rm /tmp/widevine.zip; \
|
||||
#
|
||||
# clean up
|
||||
apt-get --purge autoremove -y unzip; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/chromium.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/chromium/Default/Preferences
|
||||
COPY policies.json /etc/chromium/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:chromium]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/chromium --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/chromium --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/chromium
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/chromium
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/chromium/supervisord.nvidia.conf
Normal file
36
.docker/chromium/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:chromium]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/chromium
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/chromium
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/chromium.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
21
.docker/google-chrome/Dockerfile.nvidia
Normal file
21
.docker/google-chrome/Dockerfile.nvidia
Normal file
@ -0,0 +1,21 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
ARG SRC_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
|
||||
|
||||
#
|
||||
# install google chrome
|
||||
RUN set -eux; apt-get update; \
|
||||
wget -O /tmp/google-chrome.deb "${SRC_URL}"; \
|
||||
apt-get install -y --no-install-recommends openbox /tmp/google-chrome.deb; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/google-chrome.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/google-chrome/Default/Preferences
|
||||
COPY policies.json /etc/opt/chrome/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:google-chrome]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/google-chrome --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/google-chrome --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/google-chrome
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/google-chrome
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/google-chrome/supervisord.nvidia.conf
Normal file
36
.docker/google-chrome/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:google-chrome]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/google-chrome
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/chromium
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/google-chrome.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
26
.docker/kde/Dockerfile
Normal file
26
.docker/kde/Dockerfile
Normal file
@ -0,0 +1,26 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
#
|
||||
# install kde
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends kde-full kwin-x11 sudo; \
|
||||
#
|
||||
# add user to sudoers
|
||||
usermod -aG sudo neko; \
|
||||
echo "neko:neko" | chpasswd; \
|
||||
echo "%sudo ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers; \
|
||||
# clean up
|
||||
apt remove xserver-xorg-legacy -y; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# disable autolock
|
||||
RUN kwriteconfig5 --file /home/neko/.config/kscreenlockerrc --group Daemon --key Autolock false; \
|
||||
chown neko:neko /home/neko/.config/kscreenlockerrc
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.conf /etc/neko/supervisord/kde.conf
|
||||
|
23
.docker/kde/supervisord.conf
Normal file
23
.docker/kde/supervisord.conf
Normal file
@ -0,0 +1,23 @@
|
||||
[program:kde]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/startplasma-x11
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=500
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/kde.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:kwin]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/kwin_x11
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=500
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/kwin.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
24
.docker/microsoft-edge/Dockerfile.nvidia
Normal file
24
.docker/microsoft-edge/Dockerfile.nvidia
Normal file
@ -0,0 +1,24 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
ARG API_URL="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/"
|
||||
|
||||
#
|
||||
# install microsoft edge
|
||||
RUN set -eux; apt-get update; \
|
||||
#
|
||||
# fetch latest release
|
||||
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | tail -1)"; \
|
||||
wget -O /tmp/microsoft-edge.deb "${SRC_URL}"; \
|
||||
apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \
|
||||
#
|
||||
# clean up
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.nvidia.conf /etc/neko/supervisord/microsoft-edge.conf
|
||||
COPY --chown=neko preferences.json /home/neko/.config/microsoft-edge/Default/Preferences
|
||||
COPY policies.json /etc/opt/edge/policies/managed/policies.json
|
||||
COPY openbox.xml /etc/neko/openbox.xml
|
@ -1,6 +1,17 @@
|
||||
[program:microsoft-edge]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/microsoft-edge --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/microsoft-edge --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
|
||||
command=/usr/bin/microsoft-edge
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/microsoft-edge
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--disable-gpu
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
|
36
.docker/microsoft-edge/supervisord.nvidia.conf
Normal file
36
.docker/microsoft-edge/supervisord.nvidia.conf
Normal file
@ -0,0 +1,36 @@
|
||||
[program:microsoft-edge]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/bin/entrypoint.sh /usr/bin/microsoft-edge
|
||||
--window-position=0,0
|
||||
--display=%(ENV_DISPLAY)s
|
||||
--user-data-dir=/home/neko/.config/microsoft-edge
|
||||
--no-first-run
|
||||
--start-maximized
|
||||
--bwsi
|
||||
--force-dark-mode
|
||||
--disable-file-system
|
||||
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
|
||||
--ignore-gpu-blocklist
|
||||
--disable-seccomp-filter-sandbox
|
||||
--use-gl=egl
|
||||
--disable-software-rasterizer
|
||||
--disable-dev-shm-usage
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=800
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/microsoft-edge.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:openbox]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
|
||||
autorestart=true
|
||||
priority=300
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/openbox.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
@ -17,5 +17,9 @@ fi
|
||||
|
||||
docker run --rm -it \
|
||||
-v "${PWD}/../server:/src" \
|
||||
--entrypoint="go" \
|
||||
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"
|
||||
-e GIT_COMMIT=`git rev-parse --short HEAD` \
|
||||
-e GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD` \
|
||||
-e GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"` \
|
||||
--entrypoint="bash" \
|
||||
--workdir="/src" \
|
||||
neko_dev_server ./build
|
||||
|
@ -16,7 +16,7 @@ if [ ! -d "${PWD}/../client/node_modules" ] || [ "$1" == "-i" ]; then
|
||||
-v "${PWD}/../client:/app" \
|
||||
--workdir="/app" \
|
||||
--entrypoint="npm" \
|
||||
node:14-buster-slim install
|
||||
node:18-bullseye-slim install
|
||||
fi
|
||||
|
||||
docker run --rm -it \
|
||||
@ -25,5 +25,5 @@ docker run --rm -it \
|
||||
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
|
||||
--workdir="/app" \
|
||||
--entrypoint="npm" \
|
||||
node:14-buster-slim run serve
|
||||
node:18-bullseye-slim run serve
|
||||
|
@ -17,8 +17,22 @@ if [ ! -f "${BINARY_PATH}" ] || [ "$1" == "-r" ]; then
|
||||
./rebuild-server
|
||||
fi
|
||||
|
||||
# if image starts with nvidia- add --gpus all
|
||||
if [[ "${SERVER_TAG}" == "nvidia-"* ]]; then
|
||||
GPU_FLAG="--gpus all"
|
||||
echo "Nvidia GPU acceleration enabled"
|
||||
fi
|
||||
|
||||
# if image starts with intel- add --device /dev/dri
|
||||
if [[ "${SERVER_TAG}" == "intel-"* ]]; then
|
||||
GPU_FLAG="--device /dev/dri"
|
||||
echo "Intel GPU acceleration enabled"
|
||||
fi
|
||||
|
||||
# use --gpus all to enable GPU acceleration
|
||||
docker run --rm -it \
|
||||
--name "neko_dev" \
|
||||
$GPU_FLAG \
|
||||
-p "${SERVER_PORT}:8080" \
|
||||
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
|
||||
-e "NEKO_SCREEN=1920x1080@60" \
|
||||
|
@ -1,4 +1,4 @@
|
||||
name: "CI for builds"
|
||||
name: "build and push amd64 images to Docker Hub"
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -50,7 +50,7 @@ jobs:
|
||||
# Will build all images even if some fail.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce ]
|
||||
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce, kde ]
|
||||
env:
|
||||
DOCKER_TAG: ${{ matrix.tags }}
|
||||
steps:
|
@ -1,4 +1,4 @@
|
||||
name: "CI for version tags"
|
||||
name: "amd64 images"
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -8,6 +8,9 @@ on:
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: m1k1o/neko
|
||||
TAG_PREFIX: ""
|
||||
BASE_DOCKERFILE: Dockerfile
|
||||
PLATFORMS: linux/amd64
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
@ -31,7 +34,7 @@ jobs:
|
||||
uses: docker/metadata-action@v3
|
||||
id: meta
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/base
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
@ -49,8 +52,8 @@ jobs:
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./
|
||||
file: .docker/base/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: .docker/base/${{ env.BASE_DOCKERFILE }}
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@ -67,32 +70,20 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- tag: firefox
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- tag: chromium
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- tag: google-chrome
|
||||
platforms: linux/amd64
|
||||
- tag: ungoogled-chromium
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- tag: microsoft-edge
|
||||
platforms: linux/amd64
|
||||
- tag: brave
|
||||
platforms: linux/amd64
|
||||
- tag: vivaldi
|
||||
platforms: linux/amd64
|
||||
- tag: opera
|
||||
platforms: linux/amd64
|
||||
- tag: tor-browser
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- tag: remmina
|
||||
platforms: linux/amd64
|
||||
- tag: vlc
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- tag: xfce
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- tag: kde
|
||||
env:
|
||||
TAG_NAME: ${{ matrix.tag }}
|
||||
PLATFORMS: ${{ matrix.platforms }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@ -108,11 +99,12 @@ jobs:
|
||||
uses: docker/metadata-action@v3
|
||||
id: meta
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_NAME }}
|
||||
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
|
||||
@ -130,4 +122,4 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/base:sha-${{ github.sha }}
|
||||
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base:sha-${{ github.sha }}
|
126
.github/workflows/ghcr-arm.yml
vendored
Normal file
126
.github/workflows/ghcr-arm.yml
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
name: "arm64v8 and arm32v7 images"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: m1k1o/neko
|
||||
TAG_PREFIX: arm-
|
||||
BASE_DOCKERFILE: Dockerfile.arm
|
||||
PLATFORMS: linux/arm64,linux/arm/v7
|
||||
|
||||
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.arm
|
||||
- tag: chromium
|
||||
dockerfile: Dockerfile.arm
|
||||
- tag: ungoogled-chromium
|
||||
dockerfile: Dockerfile
|
||||
- tag: tor-browser
|
||||
dockerfile: Dockerfile
|
||||
- tag: vlc
|
||||
dockerfile: Dockerfile
|
||||
- tag: xfce
|
||||
dockerfile: Dockerfile
|
||||
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 }}
|
126
.github/workflows/ghcr-intel.yml
vendored
Normal file
126
.github/workflows/ghcr-intel.yml
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
name: "intel gpu supported images"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: m1k1o/neko
|
||||
TAG_PREFIX: intel-
|
||||
BASE_DOCKERFILE: Dockerfile.intel
|
||||
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
|
||||
- tag: chromium
|
||||
- tag: google-chrome
|
||||
- tag: ungoogled-chromium
|
||||
- tag: microsoft-edge
|
||||
- tag: brave
|
||||
- tag: vivaldi
|
||||
- tag: opera
|
||||
- tag: tor-browser
|
||||
- tag: remmina
|
||||
- tag: vlc
|
||||
- tag: xfce
|
||||
- tag: kde
|
||||
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 }}
|
||||
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 }}
|
122
.github/workflows/ghcr-nvidia.yml
vendored
Normal file
122
.github/workflows/ghcr-nvidia.yml
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
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: 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
.gitignore
vendored
4
.gitignore
vendored
@ -23,10 +23,6 @@ pids
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Lock files
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
# TypeScript incremental compilation cache
|
||||
*.tsbuildinfo
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
|
||||
</a>
|
||||
<a href="https://github.com/m1k1o/neko/actions">
|
||||
<img src="https://github.com/m1k1o/neko/actions/workflows/build.yml/badge.svg" alt="build">
|
||||
<img src="https://github.com/m1k1o/neko/actions/workflows/ghcr-amd.yml/badge.svg" alt="build">
|
||||
</a>
|
||||
</p>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/intro.gif" width="650" height="auto"/>
|
||||
@ -40,13 +40,13 @@ Neko is also a great tool for **hosting watch parties** and interactive presenta
|
||||
|
||||
This app uses WebRTC to stream a desktop inside of a docker container, original author made this because [rabb.it](https://en.wikipedia.org/wiki/Rabb.it) went under and his internet could not handle streaming and discord kept crashing when his friend attempted to. He just wanted to watch anime with his friends ლ(ಠ益ಠლ) so he started digging throughout the internet and found a few *kinda* clones, but none of them had the virtual browser, then he found [Turtus](https://github.com/Khauri/Turtus) and he was able to figure out the rest.
|
||||
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with mutliple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with multiple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||
|
||||
## Use-cases and comparison
|
||||
|
||||
Neko started as a virtual browser that is streamed using WebRTC to multiple users.
|
||||
- It is **not only limited to a browser**; it can run anything that runs on linux (e.g. VLC). Browser only happens to be the most popular and widely used use-case.
|
||||
- In fact, it is not limited to a single program either; you can install a full desktop environment (e.g. XFCE).
|
||||
- In fact, it is not limited to a single program either; you can install a full desktop environment (e.g. XFCE, KDE).
|
||||
- Speaking of limits, it does not need to run in a container; you could install neko on your host, connect to your X server and control your whole VM.
|
||||
- Theoretically it is not limited to only X server, anything that can be controlled and scraped periodically for images could be used instead.
|
||||
- Like implementing RDP or VNC protocol, where neko would only act as WebRTC relay server. This is currently only future.
|
||||
@ -99,6 +99,7 @@ Compared to clientless remote desktop gateway (e.g. [Apache Guacamole](https://g
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/vlc.svg" title="m1k1o/neko:vlc" width="60" height="auto"/>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/xfce.svg" title="m1k1o/neko:xfce" width="60" height="auto"/>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/kde.svg" title="m1k1o/neko:kde" width="60" height="auto"/>
|
||||
|
||||
... others in <a href="https://github.com/m1k1o/neko-apps">m1k1o/neko-apps</a>
|
||||
</div>
|
||||
|
21271
client/package-lock.json
generated
Normal file
21271
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||
"animejs": "^3.2.0",
|
||||
"axios": "^0.24.0",
|
||||
"axios": "^1.2.3",
|
||||
"date-fns": "^2.29.3",
|
||||
"emoji-datasource": "^6.0.1",
|
||||
"eventemitter3": "^4.0.7",
|
||||
@ -42,23 +42,23 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/animejs": "^3.1.6",
|
||||
"@types/node": "^14.18.32",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/vue": "^2.0.0",
|
||||
"@types/vue-clickaway": "^2.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.5.19",
|
||||
"@vue/cli-plugin-eslint": "^4.5.19",
|
||||
"@vue/cli-plugin-typescript": "^4.5.19",
|
||||
"@vue/cli-plugin-vuex": "^4.5.19",
|
||||
"@vue/cli-service": "^4.5.19",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.8",
|
||||
"@typescript-eslint/parser": "^5.0.8",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-plugin-typescript": "^5.0.8",
|
||||
"@vue/cli-plugin-vuex": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"core-js": "^3.26.0",
|
||||
"emojilib": "^3.0.7",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^10.3.1",
|
||||
|
@ -1,17 +1,22 @@
|
||||
<template>
|
||||
<div id="neko" :class="[side ? 'expanded' : '']">
|
||||
<div id="neko" :class="[!videoOnly && side ? 'expanded' : '']">
|
||||
<template v-if="!$client.supported">
|
||||
<neko-unsupported />
|
||||
</template>
|
||||
<template v-else>
|
||||
<main class="neko-main">
|
||||
<div v-if="!hideControls" class="header-container">
|
||||
<div v-if="!videoOnly" class="header-container">
|
||||
<neko-header />
|
||||
</div>
|
||||
<div class="video-container">
|
||||
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
|
||||
<neko-video
|
||||
ref="video"
|
||||
:hideControls="hideControls"
|
||||
:extraControls="isEmbedMode"
|
||||
@control-attempt="controlAttempt"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!hideControls" class="room-container">
|
||||
<div v-if="!videoOnly" class="room-container">
|
||||
<neko-members />
|
||||
<div class="room-menu">
|
||||
<div class="settings">
|
||||
@ -26,11 +31,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<neko-side v-if="!hideControls && side" />
|
||||
<neko-side v-if="!videoOnly && side" />
|
||||
<neko-connect v-if="!connected" />
|
||||
<neko-about v-if="about" />
|
||||
<notifications
|
||||
v-if="!hideControls"
|
||||
v-if="!videoOnly"
|
||||
group="neko"
|
||||
position="top left"
|
||||
style="top: 50px; pointer-events: none"
|
||||
@ -176,10 +181,22 @@
|
||||
|
||||
shakeKbd = false
|
||||
|
||||
get hideControls() {
|
||||
get isCastMode() {
|
||||
return !!new URL(location.href).searchParams.get('cast')
|
||||
}
|
||||
|
||||
get isEmbedMode() {
|
||||
return !!new URL(location.href).searchParams.get('embed')
|
||||
}
|
||||
|
||||
get hideControls() {
|
||||
return this.isCastMode
|
||||
}
|
||||
|
||||
get videoOnly() {
|
||||
return this.isCastMode || this.isEmbedMode
|
||||
}
|
||||
|
||||
@Watch('hideControls', { immediate: true })
|
||||
onHideControls(enabled: boolean) {
|
||||
if (enabled) {
|
||||
|
@ -132,8 +132,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import md, { HtmlOutputRule } from 'simple-markdown'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-about' })
|
||||
export default class extends Vue {
|
||||
|
@ -147,8 +147,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { get, set } from '~/utils/localstorage'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-connect' })
|
||||
export default class extends Vue {
|
||||
|
@ -132,7 +132,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
import { Member } from '~/neko/types'
|
||||
|
||||
// @ts-ignore
|
||||
@ -229,11 +229,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
adminRelease(member: Member) {
|
||||
adminRelease() {
|
||||
this.$accessor.remote.adminRelease()
|
||||
}
|
||||
|
||||
adminControl(member: Member) {
|
||||
adminControl() {
|
||||
this.$accessor.remote.adminControl()
|
||||
}
|
||||
|
||||
|
@ -287,9 +287,9 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
import { directive as onClickaway } from 'vue-clickaway'
|
||||
import { get, set } from '../utils/localstorage'
|
||||
import { get } from '../utils/localstorage'
|
||||
|
||||
@Component({
|
||||
name: 'neko-emoji',
|
||||
@ -356,7 +356,7 @@
|
||||
this.waitingForPaint = false
|
||||
let scrollTop = this._scroll.scrollTop
|
||||
let active = 0
|
||||
for (const [i, group] of this.groups.entries()) {
|
||||
for (const [i] of this.groups.entries()) {
|
||||
let component = this._groups[i]
|
||||
if (component && component.offsetTop > scrollTop) {
|
||||
break
|
||||
@ -368,7 +368,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
onMouseExit(event: MouseEvent, emoji: string) {
|
||||
onMouseExit() {
|
||||
this.hovered = ''
|
||||
}
|
||||
|
||||
@ -382,7 +382,7 @@
|
||||
this.$emit('picked', emoji)
|
||||
}
|
||||
|
||||
onClickAway(event: MouseEvent) {
|
||||
onClickAway() {
|
||||
this.$emit('done')
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,7 @@
|
||||
onDownloadProgress: (x) => {
|
||||
transfer.progress = x.loaded
|
||||
|
||||
if (x.lengthComputable && transfer.size !== x.total) {
|
||||
if (x.total && transfer.size !== x.total) {
|
||||
transfer.size = x.total
|
||||
}
|
||||
if (transfer.progress === transfer.size) {
|
||||
|
@ -157,7 +157,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { AdminLockResource } from '~/neko/messages'
|
||||
|
||||
@Component({ name: 'neko-settings' })
|
||||
|
@ -1,5 +1,5 @@
|
||||
import md, { SingleNodeParserRule, HtmlOutputRule, defaultRules, State, Rules } from 'simple-markdown'
|
||||
import { Component, Watch, Vue, Prop } from 'vue-property-decorator'
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator'
|
||||
|
||||
const { blockQuote, inlineCode, codeBlock, autolink, newline, escape, strong, text, link, url, em, u, br } =
|
||||
defaultRules
|
||||
|
@ -157,8 +157,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Member } from '~/neko/types'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
|
||||
import Content from './context.vue'
|
||||
import Avatar from './avatar.vue'
|
||||
|
@ -60,7 +60,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { messages } from '~/locale'
|
||||
|
||||
@Component({ name: 'neko-menu' })
|
||||
|
@ -97,7 +97,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
import { ScreenResolution } from '~/neko/types'
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -304,7 +304,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Watch, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-settings' })
|
||||
export default class extends Vue {
|
||||
|
@ -68,7 +68,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-unsupported' })
|
||||
export default class extends Vue {}
|
||||
|
@ -22,10 +22,10 @@
|
||||
@mouseenter.stop.prevent="onMouseEnter"
|
||||
@mouseleave.stop.prevent="onMouseLeave"
|
||||
/>
|
||||
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="toggle">
|
||||
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="playAndUnmute">
|
||||
<i class="fas fa-play-circle" />
|
||||
</div>
|
||||
<div v-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
|
||||
<div v-else-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
|
||||
<i class="fas fa-volume-up" />
|
||||
</div>
|
||||
<div ref="aspect" class="player-aspect" />
|
||||
@ -33,7 +33,7 @@
|
||||
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
|
||||
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
|
||||
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
|
||||
<li class="request-control">
|
||||
<li v-if="!implicitHosting" :class="extraControls || 'extra-control'">
|
||||
<i
|
||||
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
||||
@click.stop.prevent="toggleControl"
|
||||
@ -106,12 +106,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.request-control {
|
||||
/* usually extra controls are only shown on mobile */
|
||||
&.extra-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.request-control {
|
||||
&.extra-control {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@ -223,13 +223,15 @@
|
||||
@Ref('resolution') readonly _resolution!: Resolution
|
||||
@Ref('clipboard') readonly _clipboard!: Clipboard
|
||||
|
||||
// all controls are hidden (e.g. for cast mode)
|
||||
@Prop(Boolean) readonly hideControls!: boolean
|
||||
// extra controls are shown (e.g. for embed mode)
|
||||
@Prop(Boolean) readonly extraControls!: boolean
|
||||
|
||||
private keyboard = GuacamoleKeyboard()
|
||||
private observer = new ResizeObserver(this.onResize.bind(this))
|
||||
private focused = false
|
||||
private fullscreen = false
|
||||
private startsMuted = true
|
||||
private mutedOverlay = true
|
||||
|
||||
get admin() {
|
||||
@ -339,12 +341,12 @@
|
||||
}
|
||||
|
||||
@Watch('width')
|
||||
onWidthChanged(width: number) {
|
||||
onWidthChanged() {
|
||||
this.onResize()
|
||||
}
|
||||
|
||||
@Watch('height')
|
||||
onHeightChanged(height: number) {
|
||||
onHeightChanged() {
|
||||
this.onResize()
|
||||
}
|
||||
|
||||
@ -361,7 +363,6 @@
|
||||
onMutedChanged(muted: boolean) {
|
||||
if (this._video && this._video.muted != muted) {
|
||||
this._video.muted = muted
|
||||
this.startsMuted = muted
|
||||
|
||||
if (!muted) {
|
||||
this.mutedOverlay = false
|
||||
@ -384,9 +385,29 @@
|
||||
}
|
||||
|
||||
@Watch('playing')
|
||||
onPlayingChanged(playing: boolean) {
|
||||
async onPlayingChanged(playing: boolean) {
|
||||
if (this._video && this._video.paused && playing) {
|
||||
this.play()
|
||||
// if autoplay is disabled, play() will throw an error
|
||||
// and we need to properly save the state otherwise we
|
||||
// would be thinking we're playing when we're not
|
||||
try {
|
||||
await this._video.play()
|
||||
} catch (err: any) {
|
||||
if (!this._video.muted) {
|
||||
// video.play() can fail if audio is set due restrictive
|
||||
// browsers autoplay policy -> retry with muted audio
|
||||
try {
|
||||
this.$accessor.video.setMuted(true)
|
||||
this._video.muted = true
|
||||
await this._video.play()
|
||||
} catch (err: any) {
|
||||
// if it still fails, we're not playing anything
|
||||
this.$accessor.video.pause()
|
||||
}
|
||||
} else {
|
||||
this.$accessor.video.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._video && !this._video.paused && !playing) {
|
||||
@ -424,11 +445,6 @@
|
||||
this._video.addEventListener('canplaythrough', () => {
|
||||
this.$accessor.video.setPlayable(true)
|
||||
if (this.autoplay) {
|
||||
// start as muted due to restrictive browsers autoplay policy
|
||||
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
|
||||
this.$accessor.video.setMuted(true)
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$accessor.video.play()
|
||||
})
|
||||
@ -444,7 +460,7 @@
|
||||
this.$accessor.video.setPlayable(false)
|
||||
})
|
||||
|
||||
this._video.addEventListener('volumechange', (event) => {
|
||||
this._video.addEventListener('volumechange', () => {
|
||||
this.$accessor.video.setMuted(this._video.muted)
|
||||
this.$accessor.video.setVolume(this._video.volume * 100)
|
||||
})
|
||||
@ -560,6 +576,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
playAndUnmute() {
|
||||
this.$accessor.video.play()
|
||||
this.$accessor.video.setMuted(false)
|
||||
}
|
||||
|
||||
unmute() {
|
||||
this.$accessor.video.setMuted(false)
|
||||
}
|
||||
|
@ -66,8 +66,8 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
this._ws = new WebSocket(`${url}?password=${encodeURIComponent(password)}`)
|
||||
this.emit('debug', `connecting to ${this._ws.url}`)
|
||||
this._ws.onmessage = this.onMessage.bind(this)
|
||||
this._ws.onerror = (event) => this.onError.bind(this)
|
||||
this._ws.onclose = (event) => this.onDisconnected.bind(this, new Error('websocket closed'))
|
||||
this._ws.onerror = () => this.onError.bind(this)
|
||||
this._ws.onclose = () => this.onDisconnected.bind(this, new Error('websocket closed'))
|
||||
this._timeout = window.setTimeout(this.onTimeout.bind(this), 15000)
|
||||
} catch (err: any) {
|
||||
this.onDisconnected(err)
|
||||
@ -203,22 +203,23 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
return
|
||||
}
|
||||
|
||||
this._peer = new RTCPeerConnection()
|
||||
if (lite !== true) {
|
||||
this._peer = new RTCPeerConnection({
|
||||
iceServers: servers,
|
||||
})
|
||||
} else {
|
||||
this._peer = new RTCPeerConnection()
|
||||
}
|
||||
|
||||
this._peer.onconnectionstatechange = (event) => {
|
||||
this._peer.onconnectionstatechange = () => {
|
||||
this.emit('debug', `peer connection state changed`, this._peer ? this._peer.connectionState : undefined)
|
||||
}
|
||||
|
||||
this._peer.onsignalingstatechange = (event) => {
|
||||
this._peer.onsignalingstatechange = () => {
|
||||
this.emit('debug', `peer signaling state changed`, this._peer ? this._peer.signalingState : undefined)
|
||||
}
|
||||
|
||||
this._peer.oniceconnectionstatechange = (event) => {
|
||||
this._peer.oniceconnectionstatechange = () => {
|
||||
this._state = this._peer!.iceConnectionState
|
||||
|
||||
this.emit('debug', `peer ice connection state changed: ${this._peer!.iceConnectionState}`)
|
||||
@ -251,11 +252,28 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
|
||||
this._peer.ontrack = this.onTrack.bind(this)
|
||||
|
||||
this._peer.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||
if (!event.candidate) {
|
||||
this.emit('debug', `sent all local ICE candidates`)
|
||||
return
|
||||
}
|
||||
|
||||
const init = event.candidate.toJSON()
|
||||
this.emit('debug', `sending local ICE candidate`, init)
|
||||
|
||||
this._ws!.send(
|
||||
JSON.stringify({
|
||||
event: EVENT.SIGNAL.CANDIDATE,
|
||||
data: JSON.stringify(init),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
this._peer.onnegotiationneeded = async () => {
|
||||
this.emit('warn', `negotiation is needed`)
|
||||
|
||||
const d = await this._peer!.createOffer()
|
||||
this._peer!.setLocalDescription(d)
|
||||
await this._peer!.setLocalDescription(d)
|
||||
|
||||
this._ws!.send(
|
||||
JSON.stringify({
|
||||
@ -277,10 +295,10 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
return
|
||||
}
|
||||
|
||||
this._peer.setRemoteDescription({ type: 'offer', sdp })
|
||||
await this._peer.setRemoteDescription({ type: 'offer', sdp })
|
||||
|
||||
for (const candidate of this._candidates) {
|
||||
this._peer.addIceCandidate(candidate)
|
||||
await this._peer.addIceCandidate(candidate)
|
||||
}
|
||||
this._candidates = []
|
||||
|
||||
@ -310,7 +328,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
return
|
||||
}
|
||||
|
||||
this._peer.setRemoteDescription({ type: 'answer', sdp })
|
||||
await this._peer.setRemoteDescription({ type: 'answer', sdp })
|
||||
}
|
||||
|
||||
private async onMessage(e: MessageEvent) {
|
||||
|
@ -7,7 +7,6 @@ import { accessor } from '~/store'
|
||||
|
||||
import {
|
||||
SystemMessagePayload,
|
||||
SignalProvidePayload,
|
||||
MemberListPayload,
|
||||
MemberDisconnectPayload,
|
||||
MemberPayload,
|
||||
@ -19,7 +18,6 @@ import {
|
||||
ScreenConfigurationsPayload,
|
||||
ScreenResolutionPayload,
|
||||
BroadcastStatusPayload,
|
||||
AdminPayload,
|
||||
AdminTargetPayload,
|
||||
AdminLockMessage,
|
||||
SystemInitPayload,
|
||||
@ -131,7 +129,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
||||
this.$accessor.video.setStream(0)
|
||||
}
|
||||
|
||||
protected [EVENT.DATA](data: any) {}
|
||||
protected [EVENT.DATA]() {}
|
||||
|
||||
/////////////////////////////
|
||||
// System Events
|
||||
|
@ -10,7 +10,7 @@ declare module 'vue/types/vue' {
|
||||
$swal: VueSwalInstance
|
||||
}
|
||||
|
||||
interface VueConstructor<V extends Vue = Vue> {
|
||||
interface VueConstructor {
|
||||
swal: VueSwalInstance
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ export const actions = actionTree(
|
||||
accessor.chat.addEmote({ id, emote })
|
||||
},
|
||||
|
||||
newMessage({ state }, message: Message) {
|
||||
newMessage(store, message: Message) {
|
||||
if (accessor.settings.chat_sound) {
|
||||
new Audio('chat.mp3').play().catch(console.error)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export const actions = actionTree(
|
||||
accessor.files._removeTransfer(transfer)
|
||||
},
|
||||
|
||||
cancelAllTransfers(store) {
|
||||
cancelAllTransfers() {
|
||||
for (const t of accessor.files.transfers) {
|
||||
if (t.status !== 'completed') {
|
||||
t.abortController?.abort()
|
||||
@ -62,7 +62,7 @@ export const actions = actionTree(
|
||||
}
|
||||
},
|
||||
|
||||
refresh(store) {
|
||||
refresh() {
|
||||
if (!accessor.connected) {
|
||||
return
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export const getters = getterTree(state, {
|
||||
export const actions = actionTree(
|
||||
{ state, getters, mutations },
|
||||
{
|
||||
initialise(store) {
|
||||
initialise() {
|
||||
accessor.emoji.initialise()
|
||||
accessor.settings.initialise()
|
||||
},
|
||||
@ -92,12 +92,12 @@ export const actions = actionTree(
|
||||
}
|
||||
},
|
||||
|
||||
login({ state }, { displayname, password }: { displayname: string; password: string }) {
|
||||
login(store, { displayname, password }: { displayname: string; password: string }) {
|
||||
accessor.setLogin({ displayname, password })
|
||||
$client.login(password, displayname)
|
||||
},
|
||||
|
||||
logout({ state }) {
|
||||
logout() {
|
||||
accessor.setLogin({ displayname: '', password: '' })
|
||||
set('displayname', '')
|
||||
set('password', '')
|
||||
|
@ -21,7 +21,7 @@ export const getters = getterTree(state, {
|
||||
hosting: (state, getters, root) => {
|
||||
return root.user.id === state.id || state.implicitHosting
|
||||
},
|
||||
hosted: (state, getters, root) => {
|
||||
hosted: (state) => {
|
||||
return state.id !== '' || state.implicitHosting
|
||||
},
|
||||
host: (state, getters, root) => {
|
||||
@ -136,7 +136,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.ADMIN.RELEASE)
|
||||
},
|
||||
|
||||
adminGive({ getters }, member: string | Member) {
|
||||
adminGive(store, member: string | Member) {
|
||||
if (!accessor.connected) {
|
||||
return
|
||||
}
|
||||
@ -160,7 +160,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.CONTROL.KEYBOARD, { layout: accessor.settings.keyboard_layout })
|
||||
},
|
||||
|
||||
syncKeyboardModifierState({ state, getters }, { capsLock, numLock, scrollLock }) {
|
||||
syncKeyboardModifierState({ state }, { capsLock, numLock, scrollLock }) {
|
||||
if (state.keyboardModifierState === keyboardModifierState(capsLock, numLock, scrollLock)) {
|
||||
return
|
||||
}
|
||||
|
@ -79,13 +79,13 @@ export const actions = actionTree(
|
||||
}
|
||||
},
|
||||
|
||||
broadcastStatus({ getters }, { url, isActive }) {
|
||||
broadcastStatus(store, { url, isActive }) {
|
||||
accessor.settings.setBroadcastStatus({ url, isActive })
|
||||
},
|
||||
broadcastCreate({ getters }, url: string) {
|
||||
broadcastCreate(store, url: string) {
|
||||
$client.sendMessage(EVENT.BROADCAST.CREATE, { url })
|
||||
},
|
||||
broadcastDestroy({ getters }) {
|
||||
broadcastDestroy() {
|
||||
$client.sendMessage(EVENT.BROADCAST.DESTROY)
|
||||
},
|
||||
},
|
||||
|
@ -169,7 +169,7 @@ export const mutations = mutationTree(state, {
|
||||
export const actions = actionTree(
|
||||
{ state, getters, mutations },
|
||||
{
|
||||
screenConfiguations({ state }) {
|
||||
screenConfiguations() {
|
||||
if (!accessor.connected || !accessor.user.admin) {
|
||||
return
|
||||
}
|
||||
@ -177,7 +177,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.SCREEN.CONFIGURATIONS)
|
||||
},
|
||||
|
||||
screenGet({ state }) {
|
||||
screenGet() {
|
||||
if (!accessor.connected) {
|
||||
return
|
||||
}
|
||||
@ -185,7 +185,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.SCREEN.RESOLUTION)
|
||||
},
|
||||
|
||||
screenSet({ state }, resolution: ScreenResolution) {
|
||||
screenSet(store, resolution: ScreenResolution) {
|
||||
if (!accessor.connected || !accessor.user.admin) {
|
||||
return
|
||||
}
|
||||
|
6
client/src/types/navigator.keyboard.d.ts
vendored
6
client/src/types/navigator.keyboard.d.ts
vendored
@ -3,13 +3,13 @@
|
||||
// Type declarations for Keyboard API
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API
|
||||
interface Keyboard {
|
||||
lock(keyCodes?: array<string>): Promise<void>;
|
||||
unlock(): void;
|
||||
lock(keyCodes?: array<string>): Promise<void>
|
||||
unlock(): void
|
||||
}
|
||||
|
||||
interface NavigatorKeyboard {
|
||||
// Only available in a secure context.
|
||||
readonly keyboard?: Keyboard;
|
||||
readonly keyboard?: Keyboard
|
||||
}
|
||||
|
||||
interface Navigator extends NavigatorKeyboard {}
|
||||
|
@ -10,13 +10,13 @@ export function makeid(length: number) {
|
||||
|
||||
export function lockKeyboard() {
|
||||
if (navigator && navigator.keyboard) {
|
||||
navigator.keyboard.lock();
|
||||
navigator.keyboard.lock()
|
||||
}
|
||||
}
|
||||
|
||||
export function unlockKeyboard() {
|
||||
if (navigator && navigator.keyboard) {
|
||||
navigator.keyboard.unlock();
|
||||
navigator.keyboard.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
devServer: {
|
||||
disableHostCheck: true,
|
||||
allowedHosts: 'all',
|
||||
},
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ Neko is also a great tool for **hosting watch parties** and interactive presenta
|
||||
|
||||
## About
|
||||
|
||||
This app uses WebRTC to stream a desktop inside of a docker container, original author made this because [rabb.it](https://en.wikipedia.org/wiki/Rabb.it) went under and his internet could not handle streaming and discord kept crashing when his friend attempted to. He just wanted to watch anime with his friends ლ(ಠ益ಠლ) so he started digging throughout the internet and found a few *kinda* clones, but none of them had the virtual browser, then he found [Turtus](https://github.com/Khauri/Turtus) and he was able to figure out the rest.
|
||||
This app uses WebRTC to stream a desktop inside a docker container, original author made this because [rabb.it](https://en.wikipedia.org/wiki/Rabb.it) went under, and his internet could not handle streaming and discord kept crashing when his friend attempted to. He just wanted to watch anime with his friends ლ(ಠ益ಠლ) so he started digging throughout the internet and found a few *kinda* clones, but none of them had the virtual browser, then he found [Turtus](https://github.com/Khauri/Turtus), and he was able to figure out the rest.
|
||||
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with mutliple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with multiple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore, and it got eventually archived.
|
||||
|
||||
### Features
|
||||
|
||||
@ -41,4 +41,4 @@ Then I found [this](https://github.com/nurdism/neko) project and started to dig
|
||||
|
||||
I like cats 🐱 (`Neko` is the Japanese word for cat), I'm a weeb/nerd.
|
||||
|
||||
***But why the cat butt?*** Because cats are *assholes*, but you love them anyways.
|
||||
***But why the cat butt?*** Because cats are *assholes*, but you love them anyway.
|
||||
|
8
docs/_media/icons/kde.svg
Normal file
8
docs/_media/icons/kde.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="128" viewBox="0 0 33.8667 33.8667" width="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata/>
|
||||
<g transform="translate(0 -263.13)">
|
||||
<path d="m0 263.13h33.87v33.87h-33.87z" fill="#1d99f3"/>
|
||||
<path d="m18.8061 267.477-4.30242.41187v17.7156l4.25685-.64088v-7.55326l5.72205 8.37759 4.48586-1.41935-5.85934-8.05685 5.90521-7.59912-4.57729-1.05245-5.67649 7.59883zm-9.75254 4.31712c-.04858.005-.09551.0265-.13199.0632l-1.68863 1.68833c-.071.0712-.08437.18161-.03203.26782l1.97702 3.25614c-.35065.5895-.63169 1.22509-.83255 1.89559l-3.6295.75494c-.10098.0209-.17374.11039-.17374.21402v2.38772c0 .10098.06909.18844.16639.21196l3.52278.86107c.18786.77655.47897 1.51268.86372 2.18928l-2.03906 3.10944c-.05689.0869-.045.20138.0285.27458l1.68804 1.68834c.071.0708.18183.0847.26841.0326l3.19528-1.94057c.62765.36219 1.30454.64721 2.01995.8405l.74553 3.58451c.02098.10157.11076.17375.21373.17375h2.38802c.10039 0 .188-.0685.21196-.16699l.87812-3.59186c.73745-.19902 1.43492-.49565 2.07786-.8743l3.14883 2.06463c.08658.057.20109.0456.27458-.0279l1.68863-1.68834c.07143-.0714.08422-.18182.03174-.26781l-1.14947-1.89442-.37189.11759c-.05421.0171-.11333-.003-.14522-.0503 0 0-.73319-1.07332-1.68011-2.45915-1.13197 2.21544-3.43502 3.73297-6.09453 3.73297-3.77847 0-6.84183-3.06343-6.84183-6.84212 0-2.77974 1.65799-5.17032 4.03813-6.24122v-1.76506c-.43318.15154-.85196.33425-1.24972.55092-.00029-.00027-.00058-.001-.0017-.002l-3.22292-2.11384c-.04341-.0284-.09371-.0397-.14229-.0347z" fill="#fcfcfc" stroke-width=".265"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -2,10 +2,27 @@
|
||||
|
||||
## master branch
|
||||
|
||||
### New Features
|
||||
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
|
||||
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
|
||||
- New VirtualGL version 3.1 was released, adding support for Chromium browsers to use Nvidia GPU acceleration!
|
||||
- Added `?embed=1` parameter to the URL, which will hide the sidebar and the top bar, so that it can be embedded in other websites.
|
||||
|
||||
### Bugs
|
||||
- Fixed TCP mux occasional freeze by adding write buffer to it.
|
||||
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
|
||||
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
|
||||
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
|
||||
- Fixed client side webrtc ICE gathering, so that neko can be used without exposed ports, only with STUN and TURN servers.
|
||||
- Fixed play state synchronization, when autoplay is disabled.
|
||||
|
||||
### Misc
|
||||
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
|
||||
- Added adaptive framerate which now streams in the framerate you selected from the dropdown.
|
||||
- Improved chinese and korean characters support.
|
||||
- Disabled autolock for kde, so that it does not lock the screen when you are not using it.
|
||||
- Refactored autoplay, so that it will start playing audio, if it's allowed by the browser (by @urbanekpj).
|
||||
- Renamed pulseaudio sink from `auto_null` to `audio_output`, because it was ignored by KDE.
|
||||
|
||||
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)
|
||||
|
||||
|
@ -12,9 +12,10 @@
|
||||
<img src="../_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/>
|
||||
<img src="../_media/icons/vlc.svg" title="m1k1o/neko:vlc" width="60" height="auto"/>
|
||||
<img src="../_media/icons/xfce.svg" title="m1k1o/neko:xfce" width="60" height="auto"/>
|
||||
<img src="../_media/icons/kde.svg" title="m1k1o/neko:kde" width="60" height="auto"/>
|
||||
</div>
|
||||
|
||||
Use the following docker images:
|
||||
Use the following docker images from [Docker Hub](https://hub.docker.com/r/m1k1o/neko) for x86_64:
|
||||
- `m1k1o/neko:latest` or `m1k1o/neko:firefox` - for Firefox.
|
||||
- `m1k1o/neko:chromium` - for Chromium (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
||||
- `m1k1o/neko:google-chrome` - for Google Chrome (needs `--cap-add=SYS_ADMIN`, see the [security implications](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)).
|
||||
@ -28,15 +29,59 @@ Use the following docker images:
|
||||
- Pass env var `REMMINA_URL=<proto>://[<username>[:<password>]@]server[:port]` (proto being `vnc`, `rdp` or `spice`).
|
||||
- Or create your custom configuration with remmina locally (it's saved in `~/.local/share/remmina/path_to_profile.remmina`) and bind-mount it, then pass env var `REMMINA_PROFILE=<path_to_profile.remmina>`.
|
||||
- `m1k1o/neko:vlc` - for VLC Video player (needs volume mounted to `/media` with local video files, or setting `VLC_MEDIA=/media` path).
|
||||
- `m1k1o/neko:xfce` - for a shared desktop / installing shared software.
|
||||
- `m1k1o/neko:xfce` or `m1k1o/neko:kde` - for a shared desktop / installing shared software.
|
||||
- `m1k1o/neko:base` - for custom base.
|
||||
|
||||
For ARM-based devices (like Raspberry Pi, with GPU hardware acceleration):
|
||||
- `m1k1o/neko:arm-firefox` - for Firefox.
|
||||
- `m1k1o/neko:arm-chromium` - for Chromium.
|
||||
- `m1k1o/neko:arm-base` - for custom arm based.
|
||||
Dockerhub images are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date.
|
||||
|
||||
Images (except `arm-`) are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date,
|
||||
All images are also available on [GitHub Container Registry](https://github.com/m1k1o?tab=packages&repo_name=neko) for faster pulls:
|
||||
|
||||
- `ghcr.io/m1k1o/neko/firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/google-chrome:latest`
|
||||
- `ghcr.io/m1k1o/neko/ungoogled-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/microsoft-edge:latest`
|
||||
- `ghcr.io/m1k1o/neko/brave:latest`
|
||||
- `ghcr.io/m1k1o/neko/vivaldi:latest`
|
||||
- `ghcr.io/m1k1o/neko/opera:latest`
|
||||
- `ghcr.io/m1k1o/neko/tor-browser:latest`
|
||||
- `ghcr.io/m1k1o/neko/remmina:latest`
|
||||
- `ghcr.io/m1k1o/neko/vlc:latest`
|
||||
- `ghcr.io/m1k1o/neko/xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/kde:latest`
|
||||
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently, not all images are available for ARM, because not all applications are available for ARM.
|
||||
|
||||
- `ghcr.io/m1k1o/neko/arm-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-ungoogled-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-vlc:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-xfce:latest`
|
||||
|
||||
For images with VAAPI GPU hardware acceleration using intel drivers use:
|
||||
|
||||
- `ghcr.io/m1k1o/neko/intel-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-google-chrome:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-ungoogled-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-microsoft-edge:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-brave:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-vivaldi:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-opera:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-tor-browser:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-remmina:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-vlc:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-kde:latest`
|
||||
|
||||
For images with Nvidia GPU hardware acceleration using EGL use:
|
||||
|
||||
- `ghcr.io/m1k1o/neko/nvidia-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-google-chrome:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-microsoft-edge:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-brave:latest`
|
||||
|
||||
GHCR images are built using GitHub actions for every tag.
|
||||
|
||||
### Networking:
|
||||
- If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP.
|
||||
@ -93,7 +138,7 @@ services:
|
||||
- For others, see where existing `policies.json` is placed in their `Dockerfile`.
|
||||
|
||||
#### Allow file uploading & downloading
|
||||
- From security perespective, browser is not enabled to access local file data.
|
||||
- From security perspective, browser is not enabled to access local file data.
|
||||
- If you want to enable this, you need to modify following policies:
|
||||
```json
|
||||
"DownloadRestrictions": 0,
|
||||
@ -110,13 +155,64 @@ services:
|
||||
- For other chromium based browsers, see in `supervisord.conf` folder that is specified in `--user-data-dir`.
|
||||
|
||||
#### Allow persistent data in policies
|
||||
- From security perespective, browser is set up to forget all cookies and brwosing history when its closed.
|
||||
- From security perspective, browser is set up to forget all cookies and browsing history when its closed.
|
||||
- If you want to enable this, you need to modify following policies:
|
||||
```json
|
||||
"DefaultCookiesSetting": 1,
|
||||
"RestoreOnStartup": 1,
|
||||
```
|
||||
|
||||
### Nvidia GPU acceleration
|
||||
|
||||
You need to have 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 \
|
||||
--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
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
Note, currently only browser GPU acceleration is supported, not encoding.
|
||||
|
||||
### Want to use VPN for your n.eko browsing?
|
||||
- Check this out: https://github.com/m1k1o/neko-vpn
|
||||
|
||||
@ -134,6 +230,7 @@ services:
|
||||
- Adding `?pwd=<password>` will prefill password.
|
||||
- Adding `?usr=<display-name>` will prefill username.
|
||||
- Adding `?cast=1` will hide all control and show only video.
|
||||
- Adding `?embed=1` will hide most additional components and show only video.
|
||||
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
|
||||
|
||||
### Screen size
|
||||
|
@ -25,7 +25,7 @@ nat1to1: <ip>
|
||||
- Control protection means, users can gain control only if at least one admin is in the room.
|
||||
- e.g. `false`
|
||||
#### `NEKO_IMPLICIT_CONTROL`:
|
||||
- If enabled members can gain control implicitly, they don't needd to request control.
|
||||
- If enabled members can gain control implicitly, they don't need to request control.
|
||||
- e.g. `false`
|
||||
#### `NEKO_LOCKS`:
|
||||
- Resources, that will be locked when starting, separated by whitespace.
|
||||
@ -93,7 +93,7 @@ nat1to1: <ip>
|
||||
- opus *(default encoder)*
|
||||
- g722
|
||||
- pcmu
|
||||
- pcma
|
||||
- pcma
|
||||
#### `NEKO_AUDIO_BITRATE`:
|
||||
- Bitrate of the audio stream in kb/s.
|
||||
- e.g. `196`
|
||||
@ -136,7 +136,7 @@ nat1to1: <ip>
|
||||
- Enable file transfer feature.
|
||||
- e.g. `true`
|
||||
#### `NEKO_FILE_TRANSFER_PATH`:
|
||||
- Path where files will be transferred between the host and users. By default this is
|
||||
- Path where files will be transferred between the host and users. By default, this is
|
||||
`/home/neko/Downloads`. If the path doesn't exist, it will be created.
|
||||
- e.g. `/home/neko/Desktop`
|
||||
|
||||
@ -167,7 +167,7 @@ Flags:
|
||||
--cert string path to the SSL cert used to secure the neko server
|
||||
--control_protection control protection means, users can gain control only if at least one admin is in the room
|
||||
--cors strings list of allowed origins for CORS (default [*])
|
||||
--device string audio device to capture (default "auto_null.monitor")
|
||||
--device string audio device to capture (default "audio_output.monitor")
|
||||
--display string XDisplay to capture (default ":99.0")
|
||||
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
|
||||
--file_transfer_enabled enable file transfer feature (default false)
|
||||
|
@ -42,9 +42,9 @@ server {
|
||||
|
||||
After successfully installing and running neko, you might want to get rid of the port in the url, use DNS instead of IP address and also having SSL.
|
||||
This will remove the port from the URL and also enables HTTPS.
|
||||
To do this, you have to get running apache server. Now you can go into the `/etc/apache2/sites-available` folder and create new config file for example `neko.conf`
|
||||
After creating new config file, you can use this example config and paste it in. Some thing might vary on your machine so read through and modify if needed.
|
||||
Bear in mind that your neko server doesn't have to run on the same computer as apache. They just have to be on the same network and then you replace localhost with correct internal IP.
|
||||
To do this, you have to get running Apache server. Now you can go into the `/etc/apache2/sites-available` folder and create new config file for example `neko.conf`
|
||||
After creating new config file, you can use this example config and paste it in. Some things may vary on your machine so read through and modify if needed.
|
||||
Bear in mind that your neko server doesn't have to run on the same computer as Apache. They just have to be on the same network, and then you replace localhost with correct internal IP.
|
||||
|
||||
```xml
|
||||
<VirtualHost *:80>
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Troubleshooting
|
||||
|
||||
Neko UI loads but you don't see the screen and it gives you `connection timeout` or `disconnected` error?
|
||||
Neko UI loads, but you don't see the screen, and it gives you `connection timeout` or `disconnected` error?
|
||||
|
||||
## Test your client
|
||||
|
||||
@ -42,7 +42,7 @@ services:
|
||||
|
||||
Ensure, that your ports are reachable through your external IP.
|
||||
|
||||
To validate UDP connection the simpliest way, run this on your server:
|
||||
To validate UDP connection the simplest way, run this on your server:
|
||||
|
||||
```shell
|
||||
nc -ul 52101
|
||||
@ -61,7 +61,7 @@ If it does not work for you, then most likely your port forwarding is not workin
|
||||
|
||||
### Check if your external IP was determined correctly
|
||||
|
||||
One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to conenct to.
|
||||
One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to connect to.
|
||||
|
||||
```shell
|
||||
docker-compose logs neko | grep nat_ips
|
||||
@ -73,7 +73,7 @@ You should see this:
|
||||
11:11AM INF webrtc starting ephemeral_port_range=52000-52100 ice_lite=true ice_servers="[{URLs:[stun:stun.l.google.com:19302] Username: Credential:<nil> CredentialType:password}]" module=webrtc nat_ips=<your-IP>
|
||||
```
|
||||
|
||||
If your IP is not correct, you can specify own IP resover using `NEKO_IPFETCH`. It needs to return IP address that will be used.
|
||||
If your IP is not correct, you can specify own IP resolver using `NEKO_IPFETCH`. It needs to return IP address that will be used.
|
||||
|
||||
```diff
|
||||
version: "3.4"
|
||||
@ -119,13 +119,13 @@ If you want to use n.eko only locally, you must put here your local IP address,
|
||||
|
||||
### Neko works externally, but not locally
|
||||
|
||||
You are probabbly missing NAT Loopback (NAT Hairpinning) setting on your router.
|
||||
You are probably missing NAT Loopback (NAT Hairpinning) setting on your router.
|
||||
|
||||
Example for pfsense with truecharts docker container:
|
||||
- First, port forward the relevant ports 8080 and 52000-52100/udp for the container.
|
||||
- Then turn on `Pure NAT` pfsense (under system > advanced > firewall and nat).
|
||||
- Make sure to check the two boxes so it works.
|
||||
- Make sure `NEKO_NAT1TO1` is blank and `NEKO_IPFETCH` address is working correclty (if unset default value is chosen).
|
||||
- Make sure `NEKO_NAT1TO1` is blank and `NEKO_IPFETCH` address is working correctly (if unset default value is chosen).
|
||||
- Test externally to confirm it works.
|
||||
- Internally you have to access it using `<your-public-ip>:port`
|
||||
|
||||
@ -201,7 +201,7 @@ Check if your TCP port is exposed correctly and your reverse proxy is correctly
|
||||
Getting black screen with a cursor, but no browser.
|
||||
```
|
||||
|
||||
Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based brwosers.
|
||||
Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based browsers.
|
||||
|
||||
### Unrelated server errors
|
||||
|
||||
|
22
server/build
22
server/build
@ -3,8 +3,22 @@
|
||||
set -ex
|
||||
|
||||
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_DIRTY ];
|
||||
then
|
||||
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 "✗-"`
|
||||
fi
|
||||
|
||||
go build \
|
||||
-o bin/neko \
|
||||
-ldflags "
|
||||
-s -w
|
||||
-X 'm1k1o/neko.buildDate=${BUILD_TIME}'
|
||||
-X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}'
|
||||
-X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'
|
||||
" \
|
||||
cmd/neko/main.go;
|
||||
|
@ -1,58 +1,55 @@
|
||||
module m1k1o/neko
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/kataras/go-events v0.0.3
|
||||
github.com/pion/ice/v2 v2.2.11 // indirect
|
||||
github.com/pion/ice/v2 v2.3.0
|
||||
github.com/pion/interceptor v0.1.12
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.10 // indirect
|
||||
github.com/pion/webrtc/v3 v3.1.47
|
||||
github.com/pion/srtp/v2 v2.0.12 // indirect
|
||||
github.com/pion/webrtc/v3 v3.1.55
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/rs/zerolog v1.29.0
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/spf13/viper v1.13.0
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pion/datachannel v1.5.2 // indirect
|
||||
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.6 // indirect
|
||||
github.com/pion/mdns v0.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.10 // indirect
|
||||
github.com/pion/sctp v1.8.3 // indirect
|
||||
github.com/pion/sctp v1.8.6 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.13.1 // indirect
|
||||
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/pion/stun v0.4.0 // indirect
|
||||
github.com/pion/transport/v2 v2.0.2 // indirect
|
||||
github.com/pion/turn/v2 v2.1.0 // indirect
|
||||
github.com/pion/udp/v2 v2.0.1 // indirect
|
||||
github.com/spf13/afero v1.9.4 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
148
server/go.sum
148
server/go.sum
@ -111,7 +111,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@ -141,12 +141,11 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
||||
github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQomXoVEMsj4=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@ -157,14 +156,15 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@ -176,51 +176,49 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||
github.com/pion/ice/v2 v2.2.11 h1:wiAy7TSrVZ4KdyjC0CcNTkwltz9ywetbe4wbHLKUbIg=
|
||||
github.com/pion/ice/v2 v2.2.11/go.mod h1:NqUDUao6SjSs1+4jrqpexDmFlptlVhGxQjcymXLaVvE=
|
||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw=
|
||||
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
|
||||
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY=
|
||||
github.com/pion/ice/v2 v2.3.0 h1:G+ysriabk1p9wbySDpdsnlD+6ZspLlDLagRduRfzJPk=
|
||||
github.com/pion/ice/v2 v2.3.0/go.mod h1:+xO/cXVnnVUr6D2ZJcCT5g9LngucUkkTvfnTMqUxKRM=
|
||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.3 h1:LWcciN2ptLkw9Ugp/Ks2E76fiWy7yk3Wm79D6oFbFNo=
|
||||
github.com/pion/sctp v1.8.3/go.mod h1:OHbDjdk7kg+L+7TJim9q/qGVefdEJohuA2SZyihccgI=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.6 h1:CUex11Vkt9YS++VhLf8b55O3VqKrWL6W3SDwX4jAqsI=
|
||||
github.com/pion/sctp v1.8.6/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
||||
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.47 h1:2dFEKRI1rzFvehXDq43hK9OGGyTGJSusUi3j6QKHC5s=
|
||||
github.com/pion/webrtc/v3 v3.1.47/go.mod h1:8U39MYZCLVV4sIBn01htASVNkWQN2zDa/rx5xisEXWs=
|
||||
github.com/pion/srtp/v2 v2.0.12 h1:WrmiVCubGMOAObBU1vwWjG0H3VSyQHawKeer2PVA5rY=
|
||||
github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y=
|
||||
github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk=
|
||||
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/transport/v2 v2.0.1/go.mod h1:93OYg91+mrGxKW+Jrgzmqr80kgXqD7J0yybOrdr7w0Y=
|
||||
github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg=
|
||||
github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0=
|
||||
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
|
||||
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
||||
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
|
||||
github.com/pion/webrtc/v3 v3.1.55 h1:jQt98hZ8DUi/l/s/rtogthBdsKKvKekFgZCX9hMEqRo=
|
||||
github.com/pion/webrtc/v3 v3.1.55/go.mod h1:M1gU5mnvvo4e1nnLvF23esYz0nZAFOtbU/wq44MSfbc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -232,38 +230,40 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
||||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs=
|
||||
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
|
||||
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
|
||||
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
|
||||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -276,11 +276,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -314,6 +314,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -344,20 +345,16 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -377,6 +374,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -419,18 +417,20 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -439,8 +439,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -492,6 +494,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -599,7 +602,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@ -51,6 +51,7 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
||||
|
||||
if gstError != nil {
|
||||
defer C.g_error_free(gstError)
|
||||
fmt.Printf("(pipeline error) %s", C.GoString(gstError.message))
|
||||
return nil, fmt.Errorf("(pipeline error) %s", C.GoString(gstError.message))
|
||||
}
|
||||
|
||||
@ -60,19 +61,20 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
||||
Str("module", "capture").
|
||||
Str("submodule", "gstreamer").
|
||||
Int("pipeline_id", int(id)).Logger(),
|
||||
Src: pipelineStr,
|
||||
Ctx: ctx,
|
||||
Sample: make(chan types.Sample),
|
||||
Src: pipelineStr,
|
||||
Ctx: ctx,
|
||||
}
|
||||
|
||||
pipelines[p.id] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Pipeline) AttachAppsink(sinkName string) {
|
||||
func (p *Pipeline) AttachAppsink(sinkName string, sampleChannel chan types.Sample) {
|
||||
sinkNameUnsafe := C.CString(sinkName)
|
||||
defer C.free(unsafe.Pointer(sinkNameUnsafe))
|
||||
|
||||
p.Sample = sampleChannel
|
||||
|
||||
C.gstreamer_pipeline_attach_appsink(p.Ctx, sinkNameUnsafe)
|
||||
}
|
||||
|
||||
@ -98,7 +100,6 @@ func (p *Pipeline) Destroy() {
|
||||
delete(pipelines, p.id)
|
||||
pipelinesLock.Unlock()
|
||||
|
||||
close(p.Sample)
|
||||
C.free(unsafe.Pointer(p.Ctx))
|
||||
p = nil
|
||||
}
|
||||
@ -176,8 +177,9 @@ func goHandlePipelineBuffer(buffer unsafe.Pointer, bufferLen C.int, duration C.i
|
||||
|
||||
if ok {
|
||||
pipeline.Sample <- types.Sample{
|
||||
Data: C.GoBytes(buffer, bufferLen),
|
||||
Duration: time.Duration(duration),
|
||||
Data: C.GoBytes(buffer, bufferLen),
|
||||
Timestamp: time.Now(),
|
||||
Duration: time.Duration(duration),
|
||||
}
|
||||
} else {
|
||||
log.Warn().
|
||||
|
@ -35,7 +35,13 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
||||
return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate)
|
||||
}, "audio"),
|
||||
video: streamSinkNew(config.VideoCodec, func() (string, error) {
|
||||
return NewVideoPipeline(config.VideoCodec, config.Display, config.VideoPipeline, config.VideoMaxFPS, config.VideoBitrate, config.VideoHWEnc)
|
||||
// use screen fps as default
|
||||
fps := desktop.GetScreenSize().Rate
|
||||
// if max fps is set, cap it to that value
|
||||
if config.VideoMaxFPS > 0 && config.VideoMaxFPS < fps {
|
||||
fps = config.VideoMaxFPS
|
||||
}
|
||||
return NewVideoPipeline(config.VideoCodec, config.Display, config.VideoPipeline, fps, config.VideoBitrate, config.VideoHWEnc)
|
||||
}, "video"),
|
||||
}
|
||||
}
|
||||
@ -47,31 +53,43 @@ func (manager *CaptureManagerCtx) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
manager.desktop.OnBeforeScreenSizeChange(func() {
|
||||
if manager.video.Started() {
|
||||
manager.video.destroyPipeline()
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
before, ok := <-manager.desktop.GetScreenSizeChangeChannel()
|
||||
if !ok {
|
||||
manager.logger.Info().Msg("screen size change channel was closed")
|
||||
return
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
manager.broadcast.destroyPipeline()
|
||||
}
|
||||
})
|
||||
if before {
|
||||
// before screen size change, we need to destroy all pipelines
|
||||
|
||||
manager.desktop.OnAfterScreenSizeChange(func() {
|
||||
if manager.video.Started() {
|
||||
err := manager.video.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline")
|
||||
if manager.video.Started() {
|
||||
manager.video.destroyPipeline()
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
manager.broadcast.destroyPipeline()
|
||||
}
|
||||
} else {
|
||||
// after screen size change, we need to recreate all pipelines
|
||||
|
||||
if manager.video.Started() {
|
||||
err := manager.video.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline")
|
||||
}
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
err := manager.broadcast.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
err := manager.broadcast.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline")
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func (manager *CaptureManagerCtx) Shutdown() error {
|
||||
|
@ -53,7 +53,7 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url
|
||||
}
|
||||
|
||||
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc string) (string, error) {
|
||||
pipelineStr := " ! appsink name=appsink"
|
||||
pipelineStr := " ! appsink name=appsinkvideo"
|
||||
|
||||
// if using custom pipeline
|
||||
if pipelineSrc != "" {
|
||||
@ -61,6 +61,11 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
return pipelineStr, nil
|
||||
}
|
||||
|
||||
// use default fps if not set
|
||||
if fps == 0 {
|
||||
fps = 25
|
||||
}
|
||||
|
||||
switch rtpCodec.Name {
|
||||
case codec.VP8().Name:
|
||||
if hwenc == "VAAPI" {
|
||||
@ -106,6 +111,28 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"vp9enc target-bitrate=%d cpu-used=-5 threads=4 deadline=1 keyframe-max-dist=30 auto-alt-ref=true"+pipelineStr, display, fps, bitrate*1000)
|
||||
case codec.AV1().Name:
|
||||
// https://gstreamer.freedesktop.org/documentation/aom/av1enc.html?gi-language=c
|
||||
// gstreamer1.0-plugins-bad
|
||||
// av1enc usage-profile=1
|
||||
// TODO: check for plugin.
|
||||
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pipelineStr = strings.Join([]string{
|
||||
fmt.Sprintf(videoSrc, display, fps),
|
||||
"av1enc",
|
||||
fmt.Sprintf("target-bitrate=%d", bitrate*650),
|
||||
"cpu-used=4",
|
||||
"end-usage=cbr",
|
||||
// "usage-profile=realtime",
|
||||
"undershoot=95",
|
||||
"keyframe-max-dist=25",
|
||||
"min-quantizer=4",
|
||||
"max-quantizer=20",
|
||||
pipelineStr,
|
||||
}, " ")
|
||||
case codec.H264().Name:
|
||||
if err := gst.CheckPlugins([]string{"ximagesrc"}); err != nil {
|
||||
return "", err
|
||||
@ -149,7 +176,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
}
|
||||
|
||||
func NewAudioPipeline(rtpCodec codec.RTPCodec, device string, pipelineSrc string, bitrate uint) (string, error) {
|
||||
pipelineStr := " ! appsink name=appsink"
|
||||
pipelineStr := " ! appsink name=appsinkaudio"
|
||||
|
||||
// if using custom pipeline
|
||||
if pipelineSrc != "" {
|
||||
|
@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
type StreamSinkManagerCtx struct {
|
||||
logger zerolog.Logger
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
logger zerolog.Logger
|
||||
mu sync.Mutex
|
||||
sampleChannel chan types.Sample
|
||||
|
||||
codec codec.RTPCodec
|
||||
pipeline *gst.Pipeline
|
||||
@ -24,8 +24,6 @@ type StreamSinkManagerCtx struct {
|
||||
|
||||
listeners int
|
||||
listenersMu sync.Mutex
|
||||
|
||||
sampleFn func(sample types.Sample)
|
||||
}
|
||||
|
||||
func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), video_id string) *StreamSinkManagerCtx {
|
||||
@ -35,9 +33,10 @@ func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), vide
|
||||
Str("video_id", video_id).Logger()
|
||||
|
||||
manager := &StreamSinkManagerCtx{
|
||||
logger: logger,
|
||||
codec: codec,
|
||||
pipelineFn: pipelineFn,
|
||||
logger: logger,
|
||||
codec: codec,
|
||||
pipelineFn: pipelineFn,
|
||||
sampleChannel: make(chan types.Sample),
|
||||
}
|
||||
|
||||
return manager
|
||||
@ -47,11 +46,6 @@ func (manager *StreamSinkManagerCtx) shutdown() {
|
||||
manager.logger.Info().Msgf("shutdown")
|
||||
|
||||
manager.destroyPipeline()
|
||||
manager.wg.Wait()
|
||||
}
|
||||
|
||||
func (manager *StreamSinkManagerCtx) OnSample(listener func(sample types.Sample)) {
|
||||
manager.sampleFn = listener
|
||||
}
|
||||
|
||||
func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec {
|
||||
@ -152,27 +146,14 @@ func (manager *StreamSinkManagerCtx) createPipeline() error {
|
||||
return err
|
||||
}
|
||||
|
||||
manager.pipeline.AttachAppsink("appsink")
|
||||
appsinkSubfix := "audio"
|
||||
if manager.codec.IsVideo() {
|
||||
appsinkSubfix = "video"
|
||||
}
|
||||
|
||||
manager.pipeline.AttachAppsink("appsink"+appsinkSubfix, manager.sampleChannel)
|
||||
manager.pipeline.Play()
|
||||
|
||||
manager.wg.Add(1)
|
||||
pipeline := manager.pipeline
|
||||
|
||||
go func() {
|
||||
manager.logger.Debug().Msg("started emitting samples")
|
||||
defer manager.wg.Done()
|
||||
|
||||
for {
|
||||
sample, ok := <-pipeline.Sample
|
||||
if !ok {
|
||||
manager.logger.Debug().Msg("stopped emitting samples")
|
||||
return
|
||||
}
|
||||
|
||||
manager.sampleFn(sample)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -188,3 +169,7 @@ func (manager *StreamSinkManagerCtx) destroyPipeline() {
|
||||
manager.logger.Info().Msgf("destroying pipeline")
|
||||
manager.pipeline = nil
|
||||
}
|
||||
|
||||
func (manager *StreamSinkManagerCtx) GetSampleChannel() chan types.Sample {
|
||||
return manager.sampleChannel
|
||||
}
|
||||
|
@ -56,6 +56,12 @@ func (Capture) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DEPRECATED: video codec
|
||||
cmd.PersistentFlags().Bool("av1", false, "DEPRECATED: use video_codec")
|
||||
if err := viper.BindPFlag("av1", cmd.PersistentFlags().Lookup("av1")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// DEPRECATED: video codec
|
||||
cmd.PersistentFlags().Bool("h264", false, "DEPRECATED: use video_codec")
|
||||
if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil {
|
||||
@ -86,7 +92,7 @@ func (Capture) Init(cmd *cobra.Command) error {
|
||||
// audio
|
||||
//
|
||||
|
||||
cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture")
|
||||
cmd.PersistentFlags().String("device", "audio_output.monitor", "audio device to capture")
|
||||
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -173,6 +179,9 @@ func (s *Capture) Set() {
|
||||
} else if viper.GetBool("h264") {
|
||||
s.VideoCodec = codec.H264()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_VIDEO_CODEC=h264' instead")
|
||||
} else if viper.GetBool("av1") {
|
||||
s.VideoCodec = codec.AV1()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_VIDEO_CODEC=av1' instead")
|
||||
}
|
||||
|
||||
videoHWEnc := ""
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"m1k1o/neko/internal/desktop/xevent"
|
||||
"m1k1o/neko/internal/desktop/xorg"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@ -20,16 +19,18 @@ type DesktopManagerCtx struct {
|
||||
logger zerolog.Logger
|
||||
wg sync.WaitGroup
|
||||
shutdown chan struct{}
|
||||
emmiter events.EventEmmiter
|
||||
config *config.Desktop
|
||||
|
||||
screenSizeChangeChannel chan bool
|
||||
}
|
||||
|
||||
func New(config *config.Desktop) *DesktopManagerCtx {
|
||||
return &DesktopManagerCtx{
|
||||
logger: log.With().Str("module", "desktop").Logger(),
|
||||
shutdown: make(chan struct{}),
|
||||
emmiter: events.New(),
|
||||
config: config,
|
||||
|
||||
screenSizeChangeChannel: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,14 +48,22 @@ func (manager *DesktopManagerCtx) Start() {
|
||||
|
||||
go xevent.EventLoop(manager.config.Display)
|
||||
|
||||
manager.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
|
||||
manager.logger.Warn().
|
||||
Uint8("error_code", error_code).
|
||||
Str("message", message).
|
||||
Uint8("request_code", request_code).
|
||||
Uint8("minor_code", minor_code).
|
||||
Msg("X event error occurred")
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
msg, ok := <-xevent.EventErrorChannel
|
||||
if !ok {
|
||||
manager.logger.Info().Msg("xevent error channel was closed")
|
||||
return
|
||||
}
|
||||
|
||||
manager.logger.Warn().
|
||||
Uint8("error_code", msg.Error_code).
|
||||
Str("message", msg.Message).
|
||||
Uint8("request_code", msg.Request_code).
|
||||
Uint8("minor_code", msg.Minor_code).
|
||||
Msg("X event error occurred")
|
||||
}
|
||||
}()
|
||||
|
||||
manager.wg.Add(1)
|
||||
|
||||
@ -75,22 +84,15 @@ func (manager *DesktopManagerCtx) Start() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnBeforeScreenSizeChange(listener func()) {
|
||||
manager.emmiter.On("before_screen_size_change", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnAfterScreenSizeChange(listener func()) {
|
||||
manager.emmiter.On("after_screen_size_change", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetScreenSizeChangeChannel() chan bool {
|
||||
return manager.screenSizeChangeChannel
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) Shutdown() error {
|
||||
manager.logger.Info().Msgf("desktop shutting down")
|
||||
|
||||
close(manager.shutdown)
|
||||
close(manager.screenSizeChangeChannel)
|
||||
manager.wg.Wait()
|
||||
|
||||
xorg.DisplayClose()
|
||||
|
@ -1,33 +1,18 @@
|
||||
package desktop
|
||||
|
||||
import "m1k1o/neko/internal/desktop/xevent"
|
||||
import (
|
||||
"m1k1o/neko/internal/desktop/xevent"
|
||||
"m1k1o/neko/internal/types"
|
||||
)
|
||||
|
||||
func (manager *DesktopManagerCtx) OnCursorChanged(listener func(serial uint64)) {
|
||||
xevent.Emmiter.On("cursor-changed", func(payload ...any) {
|
||||
listener(payload[0].(uint64))
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetCursorChangedChannel() chan uint64 {
|
||||
return xevent.CursorChangedChannel
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnClipboardUpdated(listener func()) {
|
||||
xevent.Emmiter.On("clipboard-updated", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetClipboardUpdatedChannel() chan struct{} {
|
||||
return xevent.ClipboardUpdatedChannel
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnFileChooserDialogOpened(listener func()) {
|
||||
xevent.Emmiter.On("file-chooser-dialog-opened", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnFileChooserDialogClosed(listener func()) {
|
||||
xevent.Emmiter.On("file-chooser-dialog-closed", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8)) {
|
||||
xevent.Emmiter.On("event-error", func(payload ...any) {
|
||||
listener(payload[0].(uint8), payload[1].(string), payload[2].(uint8), payload[3].(uint8))
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetEventErrorChannel() chan types.DesktopErrorMessage {
|
||||
return xevent.EventErrorChannel
|
||||
}
|
||||
|
@ -10,13 +10,24 @@ import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"m1k1o/neko/internal/types"
|
||||
)
|
||||
|
||||
var Emmiter events.EventEmmiter
|
||||
var CursorChangedChannel chan uint64
|
||||
var ClipboardUpdatedChannel chan struct{}
|
||||
var EventErrorChannel chan types.DesktopErrorMessage
|
||||
|
||||
func init() {
|
||||
Emmiter = events.New()
|
||||
CursorChangedChannel = make(chan uint64)
|
||||
ClipboardUpdatedChannel = make(chan struct{})
|
||||
EventErrorChannel = make(chan types.DesktopErrorMessage)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
// TODO: Reserved for future use.
|
||||
<-CursorChangedChannel
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func EventLoop(display string) {
|
||||
@ -26,14 +37,19 @@ func EventLoop(display string) {
|
||||
C.XEventLoop(displayUnsafe)
|
||||
}
|
||||
|
||||
// TODO: Shutdown function.
|
||||
//close(CursorChangedChannel)
|
||||
//close(ClipboardUpdatedChannel)
|
||||
//close(EventErrorChannel)
|
||||
|
||||
//export goXEventCursorChanged
|
||||
func goXEventCursorChanged(event C.XFixesCursorNotifyEvent) {
|
||||
Emmiter.Emit("cursor-changed", uint64(event.cursor_serial))
|
||||
CursorChangedChannel <- uint64(event.cursor_serial)
|
||||
}
|
||||
|
||||
//export goXEventClipboardUpdated
|
||||
func goXEventClipboardUpdated() {
|
||||
Emmiter.Emit("clipboard-updated")
|
||||
ClipboardUpdatedChannel <- struct{}{}
|
||||
}
|
||||
|
||||
//export goXEventConfigureNotify
|
||||
@ -48,7 +64,12 @@ func goXEventUnmapNotify(window C.Window) {
|
||||
|
||||
//export goXEventError
|
||||
func goXEventError(event *C.XErrorEvent, message *C.char) {
|
||||
Emmiter.Emit("event-error", uint8(event.error_code), C.GoString(message), uint8(event.request_code), uint8(event.minor_code))
|
||||
EventErrorChannel <- types.DesktopErrorMessage{
|
||||
Error_code: uint8(event.error_code),
|
||||
Message: C.GoString(message),
|
||||
Request_code: uint8(event.request_code),
|
||||
Minor_code: uint8(event.minor_code),
|
||||
}
|
||||
}
|
||||
|
||||
//export goXEventActive
|
||||
|
@ -72,10 +72,10 @@ func (manager *DesktopManagerCtx) ScreenConfigurations() map[int]types.ScreenCon
|
||||
|
||||
func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error {
|
||||
mu.Lock()
|
||||
manager.emmiter.Emit("before_screen_size_change")
|
||||
manager.GetScreenSizeChangeChannel() <- true
|
||||
|
||||
defer func() {
|
||||
manager.emmiter.Emit("after_screen_size_change")
|
||||
manager.GetScreenSizeChangeChannel() <- false
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
@ -14,21 +13,21 @@ import (
|
||||
|
||||
func New(capture types.CaptureManager) *SessionManager {
|
||||
return &SessionManager{
|
||||
logger: log.With().Str("module", "session").Logger(),
|
||||
host: "",
|
||||
capture: capture,
|
||||
members: make(map[string]*Session),
|
||||
emmiter: events.New(),
|
||||
logger: log.With().Str("module", "session").Logger(),
|
||||
host: "",
|
||||
capture: capture,
|
||||
eventsChannel: make(chan types.SessionEvent, 10),
|
||||
members: make(map[string]*Session),
|
||||
}
|
||||
}
|
||||
|
||||
type SessionManager struct {
|
||||
mu sync.Mutex
|
||||
logger zerolog.Logger
|
||||
host string
|
||||
capture types.CaptureManager
|
||||
members map[string]*Session
|
||||
emmiter events.EventEmmiter
|
||||
mu sync.Mutex
|
||||
logger zerolog.Logger
|
||||
host string
|
||||
capture types.CaptureManager
|
||||
members map[string]*Session
|
||||
eventsChannel chan types.SessionEvent
|
||||
// TODO: Handle locks in sessions as flags.
|
||||
controlLocked bool
|
||||
}
|
||||
@ -49,7 +48,12 @@ func (manager *SessionManager) New(id string, admin bool, socket types.WebSocket
|
||||
manager.capture.Video().AddListener()
|
||||
manager.mu.Unlock()
|
||||
|
||||
manager.emmiter.Emit("created", id, session)
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_CREATED,
|
||||
Id: id,
|
||||
Session: session,
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
@ -68,7 +72,12 @@ func (manager *SessionManager) SetHost(id string) error {
|
||||
|
||||
if ok {
|
||||
manager.host = id
|
||||
manager.emmiter.Emit("host", id)
|
||||
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_HOST_SET,
|
||||
Id: id,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -86,7 +95,11 @@ func (manager *SessionManager) GetHost() (types.Session, bool) {
|
||||
func (manager *SessionManager) ClearHost() {
|
||||
id := manager.host
|
||||
manager.host = ""
|
||||
manager.emmiter.Emit("host_cleared", id)
|
||||
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_HOST_CLEARED,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SessionManager) Has(id string) bool {
|
||||
@ -163,7 +176,11 @@ func (manager *SessionManager) Destroy(id string) {
|
||||
manager.capture.Video().RemoveListener()
|
||||
manager.mu.Unlock()
|
||||
|
||||
manager.emmiter.Emit("destroyed", id, session)
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_DESTROYED,
|
||||
Id: id,
|
||||
Session: session,
|
||||
}
|
||||
manager.logger.Err(err).Str("session_id", id).Msg("destroying session")
|
||||
return
|
||||
}
|
||||
@ -221,32 +238,6 @@ func (manager *SessionManager) AdminBroadcast(v interface{}, exclude interface{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnHost(listener func(id string)) {
|
||||
manager.emmiter.On("host", func(payload ...interface{}) {
|
||||
listener(payload[0].(string))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnHostCleared(listener func(id string)) {
|
||||
manager.emmiter.On("host_cleared", func(payload ...interface{}) {
|
||||
listener(payload[0].(string))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnDestroy(listener func(id string, session types.Session)) {
|
||||
manager.emmiter.On("destroyed", func(payload ...interface{}) {
|
||||
listener(payload[0].(string), payload[1].(*Session))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnCreated(listener func(id string, session types.Session)) {
|
||||
manager.emmiter.On("created", func(payload ...interface{}) {
|
||||
listener(payload[0].(string), payload[1].(*Session))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnConnected(listener func(id string, session types.Session)) {
|
||||
manager.emmiter.On("connected", func(payload ...interface{}) {
|
||||
listener(payload[0].(string), payload[1].(*Session))
|
||||
})
|
||||
func (manager *SessionManager) GetEventsChannel() chan types.SessionEvent {
|
||||
return manager.eventsChannel
|
||||
}
|
||||
|
@ -78,7 +78,11 @@ func (session *Session) SetPeer(peer types.Peer) error {
|
||||
func (session *Session) SetConnected(connected bool) error {
|
||||
session.connected = connected
|
||||
if connected {
|
||||
session.manager.emmiter.Emit("connected", session.id, session)
|
||||
session.manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_CONNECTED,
|
||||
Id: session.id,
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -127,6 +131,17 @@ func (session *Session) SignalLocalAnswer(sdp string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (session *Session) SignalLocalCandidate(data string) error {
|
||||
if session.socket == nil {
|
||||
return nil
|
||||
}
|
||||
session.logger.Info().Msg("signal update - LocalCandidate")
|
||||
return session.socket.Send(&message.SignalCandidate{
|
||||
Event: event.SIGNAL_CANDIDATE,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func (session *Session) SignalRemoteOffer(sdp string) error {
|
||||
if session.peer == nil {
|
||||
return nil
|
||||
@ -150,14 +165,12 @@ func (session *Session) SignalRemoteAnswer(sdp string) error {
|
||||
return session.peer.SetAnswer(sdp)
|
||||
}
|
||||
|
||||
func (session *Session) SignalCandidate(data string) error {
|
||||
func (session *Session) SignalRemoteCandidate(data string) error {
|
||||
if session.socket == nil {
|
||||
return nil
|
||||
}
|
||||
return session.socket.Send(&message.SignalCandidate{
|
||||
Event: event.SIGNAL_CANDIDATE,
|
||||
Data: data,
|
||||
})
|
||||
session.logger.Info().Msg("signal update - RemoteCandidate")
|
||||
return session.peer.SetCandidate(data)
|
||||
}
|
||||
|
||||
func (session *Session) destroy() error {
|
||||
|
@ -19,13 +19,13 @@ type BroadcastManager interface {
|
||||
|
||||
type StreamSinkManager interface {
|
||||
Codec() codec.RTPCodec
|
||||
OnSample(listener func(sample Sample))
|
||||
|
||||
AddListener() error
|
||||
RemoveListener() error
|
||||
|
||||
ListenersCount() int
|
||||
Started() bool
|
||||
GetSampleChannel() chan Sample
|
||||
}
|
||||
|
||||
type CaptureManager interface {
|
||||
|
@ -31,6 +31,8 @@ func ParseStr(codecName string) (codec RTPCodec, ok bool) {
|
||||
codec = VP8()
|
||||
case VP9().Name:
|
||||
codec = VP9()
|
||||
case AV1().Name:
|
||||
codec = AV1()
|
||||
case H264().Name:
|
||||
codec = H264()
|
||||
case Opus().Name:
|
||||
@ -62,6 +64,14 @@ func (codec RTPCodec) Register(engine *webrtc.MediaEngine) error {
|
||||
}, codec.Type)
|
||||
}
|
||||
|
||||
func (codec RTPCodec) IsVideo() bool {
|
||||
return codec.Type == webrtc.RTPCodecTypeVideo
|
||||
}
|
||||
|
||||
func (codec RTPCodec) IsAudio() bool {
|
||||
return codec.Type == webrtc.RTPCodecTypeAudio
|
||||
}
|
||||
|
||||
func VP8() RTPCodec {
|
||||
return RTPCodec{
|
||||
Name: "vp8",
|
||||
@ -109,6 +119,22 @@ func H264() RTPCodec {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Profile ID.
|
||||
func AV1() RTPCodec {
|
||||
return RTPCodec{
|
||||
Name: "av1",
|
||||
PayloadType: 96,
|
||||
Type: webrtc.RTPCodecTypeVideo,
|
||||
Capability: webrtc.RTPCodecCapability{
|
||||
MimeType: webrtc.MimeTypeAV1,
|
||||
ClockRate: 90000,
|
||||
Channels: 0,
|
||||
SDPFmtpLine: "",
|
||||
RTCPFeedback: RTCPFeedback,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Opus() RTPCodec {
|
||||
return RTPCodec{
|
||||
Name: "opus",
|
||||
|
@ -33,11 +33,17 @@ type KeyboardMap struct {
|
||||
Variant string
|
||||
}
|
||||
|
||||
type DesktopErrorMessage struct {
|
||||
Error_code uint8
|
||||
Message string
|
||||
Request_code uint8
|
||||
Minor_code uint8
|
||||
}
|
||||
|
||||
type DesktopManager interface {
|
||||
Start()
|
||||
Shutdown() error
|
||||
OnBeforeScreenSizeChange(listener func())
|
||||
OnAfterScreenSizeChange(listener func())
|
||||
GetScreenSizeChangeChannel() (before chan bool) // true - before, false - after
|
||||
|
||||
// clipboard
|
||||
ReadClipboard() string
|
||||
@ -65,9 +71,7 @@ type DesktopManager interface {
|
||||
GetScreenshotImage() *image.RGBA
|
||||
|
||||
// xevent
|
||||
OnCursorChanged(listener func(serial uint64))
|
||||
OnClipboardUpdated(listener func())
|
||||
OnFileChooserDialogOpened(listener func())
|
||||
OnFileChooserDialogClosed(listener func())
|
||||
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
|
||||
GetCursorChangedChannel() chan uint64
|
||||
GetClipboardUpdatedChannel() chan struct{}
|
||||
GetEventErrorChannel() chan DesktopErrorMessage
|
||||
}
|
||||
|
@ -7,6 +7,22 @@ type Member struct {
|
||||
Muted bool `json:"muted"`
|
||||
}
|
||||
|
||||
type SessionEventType int
|
||||
|
||||
const (
|
||||
SESSION_CREATED SessionEventType = iota
|
||||
SESSION_CONNECTED
|
||||
SESSION_DESTROYED
|
||||
SESSION_HOST_SET
|
||||
SESSION_HOST_CLEARED
|
||||
)
|
||||
|
||||
type SessionEvent struct {
|
||||
Type SessionEventType
|
||||
Id string
|
||||
Session Session
|
||||
}
|
||||
|
||||
type Session interface {
|
||||
ID() string
|
||||
Name() string
|
||||
@ -24,9 +40,10 @@ type Session interface {
|
||||
Send(v interface{}) error
|
||||
SignalLocalOffer(sdp string) error
|
||||
SignalLocalAnswer(sdp string) error
|
||||
SignalLocalCandidate(data string) error
|
||||
SignalRemoteOffer(sdp string) error
|
||||
SignalRemoteAnswer(sdp string) error
|
||||
SignalCandidate(data string) error
|
||||
SignalRemoteCandidate(data string) error
|
||||
}
|
||||
|
||||
type SessionManager interface {
|
||||
@ -46,9 +63,5 @@ type SessionManager interface {
|
||||
Clear() error
|
||||
Broadcast(v interface{}, exclude interface{}) error
|
||||
AdminBroadcast(v interface{}, exclude interface{}) error
|
||||
OnHost(listener func(id string))
|
||||
OnHostCleared(listener func(id string))
|
||||
OnDestroy(listener func(id string, session Session))
|
||||
OnCreated(listener func(id string, session Session))
|
||||
OnConnected(listener func(id string, session Session))
|
||||
GetEventsChannel() chan SessionEvent
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ type Peer interface {
|
||||
CreateAnswer() (string, error)
|
||||
SetOffer(sdp string) error
|
||||
SetAnswer(sdp string) error
|
||||
SetCandidate(candidateString string) error
|
||||
WriteData(v interface{}) error
|
||||
Destroy() error
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
@ -49,6 +50,16 @@ func (peer *Peer) SetAnswer(sdp string) error {
|
||||
return peer.connection.SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
|
||||
}
|
||||
|
||||
func (peer *Peer) SetCandidate(candidateString string) error {
|
||||
var candidate webrtc.ICECandidateInit
|
||||
err := json.Unmarshal([]byte(candidateString), &candidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return peer.connection.AddICECandidate(candidate)
|
||||
}
|
||||
|
||||
func (peer *Peer) WriteData(v interface{}) error {
|
||||
peer.mu.Lock()
|
||||
defer peer.mu.Unlock()
|
||||
|
@ -55,12 +55,20 @@ func (manager *WebRTCManager) Start() {
|
||||
manager.logger.Panic().Err(err).Msg("unable to create audio track")
|
||||
}
|
||||
|
||||
manager.capture.Audio().OnSample(func(sample types.Sample) {
|
||||
err := manager.audioTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("audio pipeline failed to write")
|
||||
go func() {
|
||||
for {
|
||||
sample, ok := <-manager.capture.Audio().GetSampleChannel()
|
||||
if !ok {
|
||||
manager.logger.Debug().Msg("audio capture channel is closed")
|
||||
continue
|
||||
}
|
||||
|
||||
err := manager.audioTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("audio pipeline failed to write")
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
//
|
||||
// video
|
||||
@ -72,12 +80,20 @@ func (manager *WebRTCManager) Start() {
|
||||
manager.logger.Panic().Err(err).Msg("unable to create video track")
|
||||
}
|
||||
|
||||
manager.capture.Video().OnSample(func(sample types.Sample) {
|
||||
err := manager.videoTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("video pipeline failed to write")
|
||||
go func() {
|
||||
for {
|
||||
sample, ok := <-manager.capture.Video().GetSampleChannel()
|
||||
if !ok {
|
||||
manager.logger.Debug().Msg("video capture channel is closed")
|
||||
continue
|
||||
}
|
||||
|
||||
err := manager.videoTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("video pipeline failed to write")
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
//
|
||||
// api
|
||||
@ -107,7 +123,6 @@ func (manager *WebRTCManager) initAPI() error {
|
||||
LoggerFactory: logger,
|
||||
}
|
||||
|
||||
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
||||
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
|
||||
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
|
||||
settings.SetSRTPReplayProtectionWindow(512)
|
||||
@ -152,12 +167,15 @@ func (manager *WebRTCManager) initAPI() error {
|
||||
|
||||
networkType = append(networkType, webrtc.NetworkTypeUDP4)
|
||||
manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX")
|
||||
} else if manager.config.EphemeralMax != 0 {
|
||||
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
||||
networkType = append(networkType,
|
||||
webrtc.NetworkTypeUDP4,
|
||||
webrtc.NetworkTypeUDP6,
|
||||
)
|
||||
}
|
||||
|
||||
// Enable support for TCP and UDP ICE candidates
|
||||
if len(networkType) > 0 {
|
||||
settings.SetNetworkTypes(networkType)
|
||||
}
|
||||
settings.SetNetworkTypes(networkType)
|
||||
|
||||
// Create MediaEngine with selected codecs
|
||||
engine := webrtc.MediaEngine{}
|
||||
@ -283,7 +301,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (type
|
||||
return
|
||||
}
|
||||
|
||||
if err := session.SignalCandidate(string(candidateString)); err != nil {
|
||||
if err := session.SignalLocalCandidate(string(candidateString)); err != nil {
|
||||
manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed")
|
||||
return
|
||||
}
|
||||
|
@ -87,6 +87,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
||||
utils.Unmarshal(payload, raw, func() error {
|
||||
return h.signalRemoteAnswer(id, session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
case event.SIGNAL_CANDIDATE:
|
||||
payload := &message.SignalCandidate{}
|
||||
return errors.Wrapf(
|
||||
utils.Unmarshal(payload, raw, func() error {
|
||||
return h.signalRemoteCandidate(id, session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
|
||||
// Control Events
|
||||
case event.CONTROL_RELEASE:
|
||||
|
@ -45,3 +45,7 @@ func (h *MessageHandler) signalRemoteAnswer(id string, session types.Session, pa
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MessageHandler) signalRemoteCandidate(id string, session types.Session, payload *message.SignalCandidate) error {
|
||||
return session.SignalRemoteCandidate(payload.Data)
|
||||
}
|
||||
|
@ -101,102 +101,121 @@ type WebSocketHandler struct {
|
||||
}
|
||||
|
||||
func (ws *WebSocketHandler) Start() {
|
||||
ws.sessions.OnCreated(func(id string, session types.Session) {
|
||||
if err := ws.handler.SessionCreated(id, session); err != nil {
|
||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session created with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", id).Msg("session created")
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
e, ok := <-ws.sessions.GetEventsChannel()
|
||||
if !ok {
|
||||
ws.logger.Info().Msg("session channel was closed")
|
||||
return
|
||||
}
|
||||
|
||||
ws.sessions.OnConnected(func(id string, session types.Session) {
|
||||
if err := ws.handler.SessionConnected(id, session); err != nil {
|
||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session connected with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", id).Msg("session connected")
|
||||
}
|
||||
switch e.Type {
|
||||
case types.SESSION_CREATED:
|
||||
if err := ws.handler.SessionCreated(e.Id, e.Session); err != nil {
|
||||
ws.logger.Warn().Str("id", e.Id).Err(err).Msg("session created with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", e.Id).Msg("session created")
|
||||
}
|
||||
case types.SESSION_CONNECTED:
|
||||
if err := ws.handler.SessionConnected(e.Id, e.Session); err != nil {
|
||||
ws.logger.Warn().Str("id", e.Id).Err(err).Msg("session connected with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", e.Id).Msg("session connected")
|
||||
}
|
||||
|
||||
// if control protection is enabled and at least one admin
|
||||
// and if room was locked on behalf control protection, unlock
|
||||
sess, ok := ws.state.GetLocked("control")
|
||||
if ok && ws.conf.ControlProtection && sess == CONTROL_PROTECTION_SESSION && len(ws.sessions.Admins()) > 0 {
|
||||
ws.state.Unlock("control")
|
||||
ws.sessions.SetControlLocked(false) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control unlocked on behalf of control protection")
|
||||
// if control protection is enabled and at least one admin
|
||||
// and if room was locked on behalf control protection, unlock
|
||||
sess, ok := ws.state.GetLocked("control")
|
||||
if ok && ws.conf.ControlProtection && sess == CONTROL_PROTECTION_SESSION && len(ws.sessions.Admins()) > 0 {
|
||||
ws.state.Unlock("control")
|
||||
ws.sessions.SetControlLocked(false) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control unlocked on behalf of control protection")
|
||||
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_UNLOCK,
|
||||
ID: id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNLOCK)
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_UNLOCK,
|
||||
ID: e.Id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
// remove outdated stats
|
||||
if e.Session.Admin() {
|
||||
ws.lastAdminLeftAt = nil
|
||||
} else {
|
||||
ws.lastUserLeftAt = nil
|
||||
}
|
||||
case types.SESSION_DESTROYED:
|
||||
if err := ws.handler.SessionDestroyed(e.Id); err != nil {
|
||||
ws.logger.Warn().Str("id", e.Id).Err(err).Msg("session destroyed with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", e.Id).Msg("session destroyed")
|
||||
}
|
||||
|
||||
membersCount := len(ws.sessions.Members())
|
||||
adminCount := len(ws.sessions.Admins())
|
||||
|
||||
// if control protection is enabled and no admin
|
||||
// and room is not locked, lock
|
||||
ok := ws.state.IsLocked("control")
|
||||
if !ok && ws.conf.ControlProtection && adminCount == 0 {
|
||||
ws.state.Lock("control", CONTROL_PROTECTION_SESSION)
|
||||
ws.sessions.SetControlLocked(true) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control locked and released on behalf of control protection")
|
||||
ws.handler.AdminRelease(e.Id, e.Session)
|
||||
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_LOCK,
|
||||
ID: e.Id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_LOCK)
|
||||
}
|
||||
}
|
||||
|
||||
// if this was the last admin
|
||||
if e.Session.Admin() && adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastAdminLeftAt = &now
|
||||
}
|
||||
|
||||
// if this was the last user
|
||||
if !e.Session.Admin() && membersCount-adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastUserLeftAt = &now
|
||||
}
|
||||
case types.SESSION_HOST_SET:
|
||||
// TODO: Unused.
|
||||
case types.SESSION_HOST_CLEARED:
|
||||
// TODO: Unused.
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// remove outdated stats
|
||||
if session.Admin() {
|
||||
ws.lastAdminLeftAt = nil
|
||||
} else {
|
||||
ws.lastUserLeftAt = nil
|
||||
}
|
||||
})
|
||||
|
||||
ws.sessions.OnDestroy(func(id string, session types.Session) {
|
||||
if err := ws.handler.SessionDestroyed(id); err != nil {
|
||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session destroyed with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", id).Msg("session destroyed")
|
||||
}
|
||||
|
||||
membersCount := len(ws.sessions.Members())
|
||||
adminCount := len(ws.sessions.Admins())
|
||||
|
||||
// if control protection is enabled and no admin
|
||||
// and room is not locked, lock
|
||||
ok := ws.state.IsLocked("control")
|
||||
if !ok && ws.conf.ControlProtection && adminCount == 0 {
|
||||
ws.state.Lock("control", CONTROL_PROTECTION_SESSION)
|
||||
ws.sessions.SetControlLocked(true) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control locked and released on behalf of control protection")
|
||||
ws.handler.AdminRelease(id, session)
|
||||
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_LOCK,
|
||||
ID: id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_LOCK)
|
||||
go func() {
|
||||
for {
|
||||
_, ok := <-ws.desktop.GetClipboardUpdatedChannel()
|
||||
if !ok {
|
||||
ws.logger.Info().Msg("clipboard update channel closed")
|
||||
return
|
||||
}
|
||||
|
||||
session, ok := ws.sessions.GetHost()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := session.Send(message.Clipboard{
|
||||
Event: event.CONTROL_CLIPBOARD,
|
||||
Text: ws.desktop.ReadClipboard(),
|
||||
})
|
||||
|
||||
ws.logger.Err(err).Msg("sync clipboard")
|
||||
}
|
||||
|
||||
// if this was the last admin
|
||||
if session.Admin() && adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastAdminLeftAt = &now
|
||||
}
|
||||
|
||||
// if this was the last user
|
||||
if !session.Admin() && membersCount-adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastUserLeftAt = &now
|
||||
}
|
||||
})
|
||||
|
||||
ws.desktop.OnClipboardUpdated(func() {
|
||||
session, ok := ws.sessions.GetHost()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := session.Send(message.Clipboard{
|
||||
Event: event.CONTROL_CLIPBOARD,
|
||||
Text: ws.desktop.ReadClipboard(),
|
||||
})
|
||||
|
||||
ws.logger.Err(err).Msg("sync clipboard")
|
||||
})
|
||||
}()
|
||||
|
||||
// watch for file changes and send file list if file transfer is enabled
|
||||
if ws.conf.FileTransferEnabled {
|
||||
|
Reference in New Issue
Block a user