Compare commits

..

11 Commits

Author SHA1 Message Date
Miroslav Šedivý
6a2ebc0433 add to docs. 2023-01-15 20:38:00 +01:00
Miroslav Šedivý
93b9ab1732 WIP. 2023-01-15 20:26:06 +01:00
Miroslav Šedivý
56109df85f WIP. 2023-01-15 20:16:54 +01:00
Miroslav Šedivý
1c8679b61e WIP. 2023-01-15 20:09:27 +01:00
Miroslav Šedivý
3906e1007c WIP. 2023-01-15 19:29:45 +01:00
Miroslav Šedivý
d7b64be87b WIP. 2023-01-15 19:27:46 +01:00
Miroslav Šedivý
ab7666ec8a build intel images. 2023-01-15 19:23:43 +01:00
Miroslav Šedivý
300a4f713b new workflows. 2023-01-15 19:21:35 +01:00
Miroslav Šedivý
ee943591a3 update arm support. 2023-01-15 18:55:30 +01:00
Miroslav Šedivý
be480311c7 extract intel gpu support from dockerfile. 2023-01-15 18:55:16 +01:00
Miroslav Šedivý
2801acf0b9 include package-lock. 2023-01-15 18:42:50 +01:00
119 changed files with 9669 additions and 17993 deletions

View File

@ -1,7 +1,7 @@
# #
# STAGE 1: SERVER # STAGE 1: SERVER
# #
FROM golang:1.20-bullseye as server FROM golang:1.18-bullseye as server
WORKDIR /src WORKDIR /src
# #
@ -27,12 +27,12 @@ RUN set -eux; apt-get update; \
# #
# build server # build server
COPY server/ . COPY server/ .
RUN ./build RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
# #
# STAGE 2: CLIENT # STAGE 2: CLIENT
# #
FROM node:18-bullseye-slim as client FROM node:14-bullseye-slim as client
WORKDIR /src WORKDIR /src
# #
@ -79,9 +79,7 @@ RUN set -eux; \
# Japanese fonts # Japanese fonts
fonts-takao-mincho \ fonts-takao-mincho \
# Chinese fonts # Chinese fonts
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \ fonts-wqy-zenhei; \
# Korean fonts
fonts-wqy-microhei; \
# #
# create a non-root user # create a non-root user
groupadd --gid $USER_GID $USERNAME; \ groupadd --gid $USER_GID $USERNAME; \
@ -90,18 +88,19 @@ RUN set -eux; \
adduser $USERNAME video; \ adduser $USERNAME video; \
adduser $USERNAME pulse; \ adduser $USERNAME pulse; \
# #
# setup pulseaudio
mkdir -p /home/$USERNAME/.config/pulse/; \
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
#
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476 # workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
mkdir /tmp/.X11-unix; \ mkdir /tmp/.X11-unix; \
chmod 1777 /tmp/.X11-unix; \ chmod 1777 /tmp/.X11-unix; \
chown $USERNAME /tmp/.X11-unix/; \ chown $USERNAME /tmp/.X11-unix/; \
# #
# make directories for neko # make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko \ mkdir -p /etc/neko /var/www /var/log/neko; \
/tmp/runtime-$USERNAME \
/home/$USERNAME/.config/pulse \
/home/$USERNAME/.local/share/xorg; \
chmod 1777 /var/log/neko; \ chmod 1777 /var/log/neko; \
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \ chown $USERNAME /var/log/neko/; \
chown -R $USERNAME:$USERNAME /home/$USERNAME; \ chown -R $USERNAME:$USERNAME /home/$USERNAME; \
# #
# clean up # clean up
@ -119,8 +118,6 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
# set default envs # set default envs
ENV USER=$USERNAME ENV USER=$USERNAME
ENV DISPLAY=:99.0 ENV DISPLAY=:99.0
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
ENV NEKO_PASSWORD=neko ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080 ENV NEKO_BIND=:8080

View File

@ -1,7 +1,7 @@
# #
# STAGE 1: SERVER # STAGE 1: SERVER
# #
FROM golang:1.20-bullseye as server FROM golang:1.18-bullseye as server
WORKDIR /src WORKDIR /src
# #
@ -27,32 +27,29 @@ RUN set -eux; apt-get update; \
# #
# build server # build server
COPY server/ . COPY server/ .
RUN ./build RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
# #
# STAGE 2: CLIENT # STAGE 2: CLIENT
# #
FROM node:14-bullseye-slim as client
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends python2 build-essential
WORKDIR /src
# #
# Because client builds fail in Github Actions, therefor we build it outside of Docker. # install dependencies
# COPY client/package*.json ./
# FROM node:18-bullseye-slim as client RUN npm install
#
# # install dependencies
# RUN set -eux; apt-get update; \
# apt-get install -y --no-install-recommends python2 build-essential
#
# WORKDIR /src
#
# #
# # install dependencies
# COPY client/package*.json ./
# RUN npm install
#
# #
# # build client
# COPY client/ .
# RUN npm run build
# #
# build client
COPY client/ .
RUN npm run build
# #
# STAGE 3: RUNTIME # STAGE 3: RUNTIME
# #
@ -88,9 +85,7 @@ RUN set -eux; \
# Japanese fonts # Japanese fonts
fonts-takao-mincho \ fonts-takao-mincho \
# Chinese fonts # Chinese fonts
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \ fonts-wqy-zenhei; \
# Korean fonts
fonts-wqy-microhei; \
# #
# create a non-root user # create a non-root user
groupadd --gid $USER_GID $USERNAME; \ groupadd --gid $USER_GID $USERNAME; \
@ -99,18 +94,19 @@ RUN set -eux; \
adduser $USERNAME video; \ adduser $USERNAME video; \
adduser $USERNAME pulse; \ adduser $USERNAME pulse; \
# #
# setup pulseaudio
mkdir -p /home/$USERNAME/.config/pulse/; \
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
#
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476 # workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
mkdir /tmp/.X11-unix; \ mkdir /tmp/.X11-unix; \
chmod 1777 /tmp/.X11-unix; \ chmod 1777 /tmp/.X11-unix; \
chown $USERNAME /tmp/.X11-unix/; \ chown $USERNAME /tmp/.X11-unix/; \
# #
# make directories for neko # make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko \ mkdir -p /etc/neko /var/www /var/log/neko; \
/tmp/runtime-$USERNAME \
/home/$USERNAME/.config/pulse \
/home/$USERNAME/.local/share/xorg; \
chmod 1777 /var/log/neko; \ chmod 1777 /var/log/neko; \
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \ chown $USERNAME /var/log/neko/; \
chown -R $USERNAME:$USERNAME /home/$USERNAME; \ chown -R $USERNAME:$USERNAME /home/$USERNAME; \
# #
# clean up # clean up
@ -128,8 +124,6 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
# set default envs # set default envs
ENV USER=$USERNAME ENV USER=$USERNAME
ENV DISPLAY=:99.0 ENV DISPLAY=:99.0
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
ENV NEKO_PASSWORD=neko ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080 ENV NEKO_BIND=:8080
@ -137,8 +131,7 @@ ENV NEKO_BIND=:8080
# #
# copy static files from previous stages # copy static files from previous stages
COPY --from=server /src/bin/neko /usr/bin/neko COPY --from=server /src/bin/neko /usr/bin/neko
# COPY --from=client /src/dist/ /var/www COPY --from=client /src/dist/ /var/www
COPY client/dist/ /var/www
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \ HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1 CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1

View File

@ -1,7 +1,7 @@
# #
# STAGE 1: SERVER # STAGE 1: SERVER
# #
FROM golang:1.20-bullseye as server FROM golang:1.18-bullseye as server
WORKDIR /src WORKDIR /src
# #
@ -27,12 +27,12 @@ RUN set -eux; apt-get update; \
# #
# build server # build server
COPY server/ . COPY server/ .
RUN ./build RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
# #
# STAGE 2: CLIENT # STAGE 2: CLIENT
# #
FROM node:18-bullseye-slim as client FROM node:14-bullseye-slim as client
WORKDIR /src WORKDIR /src
# #
@ -88,9 +88,7 @@ RUN set -eux; \
# Japanese fonts # Japanese fonts
fonts-takao-mincho \ fonts-takao-mincho \
# Chinese fonts # Chinese fonts
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \ fonts-wqy-zenhei; \
# Korean fonts
fonts-wqy-microhei; \
# #
# create a non-root user # create a non-root user
groupadd --gid $USER_GID $USERNAME; \ groupadd --gid $USER_GID $USERNAME; \
@ -99,18 +97,19 @@ RUN set -eux; \
adduser $USERNAME video; \ adduser $USERNAME video; \
adduser $USERNAME pulse; \ adduser $USERNAME pulse; \
# #
# setup pulseaudio
mkdir -p /home/$USERNAME/.config/pulse/; \
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
#
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476 # workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
mkdir /tmp/.X11-unix; \ mkdir /tmp/.X11-unix; \
chmod 1777 /tmp/.X11-unix; \ chmod 1777 /tmp/.X11-unix; \
chown $USERNAME /tmp/.X11-unix/; \ chown $USERNAME /tmp/.X11-unix/; \
# #
# make directories for neko # make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko \ mkdir -p /etc/neko /var/www /var/log/neko; \
/tmp/runtime-$USERNAME \
/home/$USERNAME/.config/pulse \
/home/$USERNAME/.local/share/xorg; \
chmod 1777 /var/log/neko; \ chmod 1777 /var/log/neko; \
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \ chown $USERNAME /var/log/neko/; \
chown -R $USERNAME:$USERNAME /home/$USERNAME; \ chown -R $USERNAME:$USERNAME /home/$USERNAME; \
# #
# clean up # clean up
@ -129,12 +128,9 @@ COPY .docker/base/intel/add-render-group.sh /usr/bin/add-render-group.sh
# set default envs # set default envs
ENV USER=$USERNAME ENV USER=$USERNAME
ENV DISPLAY=:99.0 ENV DISPLAY=:99.0
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
ENV NEKO_PASSWORD=neko ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080 ENV NEKO_BIND=:8080
ENV NEKO_HWENC=VAAPI
ENV RENDER_GID= ENV RENDER_GID=
# #

View File

@ -1,294 +0,0 @@
ARG UBUNTU_RELEASE=20.04
ARG CUDA_VERSION=11.4.3
ARG VIRTUALGL_VERSION=3.1
ARG GSTREAMER_VERSION=1.20
#
# STAGE 0: Build gstreamer with nvidia plugins.
#
FROM ubuntu:${UBUNTU_RELEASE} AS gstreamer
ARG GSTREAMER_VERSION
#
# install dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
# Install essentials
curl build-essential ca-certificates git \
# Install pip and ninja
python3-pip python-gi-dev ninja-build \
# Install build deps
autopoint autoconf automake autotools-dev libtool gettext bison flex gtk-doc-tools \
# Install libraries
librtmp-dev \
libvo-aacenc-dev \
libtool-bin \
libgtk2.0-dev \
libgl1-mesa-dev \
libopus-dev \
libpulse-dev \
libssl-dev \
libx264-dev \
libvpx-dev; \
# Install meson
pip3 install meson; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# build gstreamer
RUN set -eux; \
git clone --depth 1 --branch $GSTREAMER_VERSION https://gitlab.freedesktop.org/gstreamer/gstreamer.git /gstreamer/src; \
cd /gstreamer/src; \
mkdir -p /opt/gstreamer; \
meson --prefix /opt/gstreamer \
-Dgpl=enabled \
-Dugly=enabled \
-Dgst-plugins-ugly:x264=enabled \
build; \
ninja -C build; \
meson install -C build;
#
# STAGE 1: SERVER
#
FROM golang:1.20-bullseye as server
WORKDIR /src
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends git cmake make libx11-dev libxrandr-dev libxtst-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly; \
#
# install libclipboard
set -eux; \
cd /tmp; \
git clone --depth=1 https://github.com/jtanx/libclipboard; \
cd libclipboard; \
cmake .; \
make -j4; \
make install; \
rm -rf /tmp/libclipboard; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# build server
COPY server/ .
RUN ./build
#
# STAGE 2: CLIENT
#
FROM node:18-bullseye-slim as client
WORKDIR /src
#
# install dependencies
COPY client/package*.json ./
RUN npm install
#
# build client
COPY client/ .
RUN npm run build
#
# STAGE 3: RUNTIME
#
FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_RELEASE} as runtime
ARG UBUNTU_RELEASE
ARG VIRTUALGL_VERSION
# Make all NVIDIA GPUs visible by default
ENV NVIDIA_VISIBLE_DEVICES all
# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work
ENV NVIDIA_DRIVER_CAPABILITIES all
#
# set vgl-display to headless 3d gpu card/// correct values are egl[n] or /dev/dri/card0:if this is passed into container
ENV VGL_DISPLAY egl
#
# avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
#
# set custom user
ARG USERNAME=neko
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN set -eux; \
dpkg --add-architecture i386; \
apt-get update; \
apt-get install -y --no-install-recommends \
# opengl base: https://gitlab.com/nvidia/container-images/opengl/-/blob/ubuntu20.04/base/Dockerfile
libxau6 libxau6:i386 \
libxdmcp6 libxdmcp6:i386 \
libxcb1 libxcb1:i386 \
libxext6 libxext6:i386 \
libx11-6 libx11-6:i386 \
# opengl runtime: https://gitlab.com/nvidia/container-images/opengl/-/blob/ubuntu20.04/glvnd/runtime/Dockerfile
libglvnd0 libglvnd0:i386 \
libgl1 libgl1:i386 \
libglx0 libglx0:i386 \
libegl1 libegl1:i386 \
libgles2 libgles2:i386 \
# hardware accleration utilities
libglu1 libglu1:i386 \
libvulkan-dev libvulkan-dev:i386 \
mesa-utils mesa-utils-extra \
mesa-va-drivers mesa-vulkan-drivers \
vainfo vdpauinfo; \
#
# install vulkan-utils or vulkan-tools depending on ubuntu release
if [ "${UBUNTU_RELEASE}" = "18.04" ]; then \
apt-get install -y --no-install-recommends vulkan-utils; \
else \
apt-get install -y --no-install-recommends vulkan-tools; \
fi; \
#
# create symlink for libnvrtc.so (needed for cudaconvert)
find /usr/local/cuda/lib64/ -maxdepth 1 -type l -name "*libnvrtc.so.*" -exec sh -c 'ln -sf {} /usr/local/cuda/lib64/libnvrtc.so' \;; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# add cuda to ld path, for gstreamer cuda plugins
ENV LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}:/usr/local/cuda/lib:/usr/local/cuda/lib64"
RUN set -eux; \
apt-get update; \
#
# install dependencies
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6 libx264-155 libvo-aacenc0 librtmp1; \
apt-get install -y --no-install-recommends libgtk-3-bin software-properties-common cabextract aptitude vim curl; \
#
# install fonts
apt-get install -y --no-install-recommends \
# Google emojis
fonts-noto-color-emoji \
# Japanese fonts
fonts-takao-mincho \
# Chinese fonts
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
# Korean fonts
fonts-wqy-microhei; \
#
# create a non-root user
groupadd --gid $USER_GID $USERNAME; \
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
adduser $USERNAME audio; \
adduser $USERNAME video; \
adduser $USERNAME pulse; \
#
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
mkdir /tmp/.X11-unix; \
chmod 1777 /tmp/.X11-unix; \
chown $USERNAME /tmp/.X11-unix/; \
#
# make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko \
/tmp/runtime-$USERNAME \
/home/$USERNAME/.config/pulse \
/home/$USERNAME/.local/share/xorg; \
chmod 1777 /var/log/neko; \
chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# configure EGL and Vulkan manually
RUN VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)') && \
# Configure EGL manually
mkdir -p /usr/share/glvnd/egl_vendor.d/ && \
echo "{\n\
\"file_format_version\" : \"1.0.0\",\n\
\"ICD\": {\n\
\"library_path\": \"libEGL_nvidia.so.0\"\n\
}\n\
}" > /usr/share/glvnd/egl_vendor.d/10_nvidia.json && \
# Configure Vulkan manually
mkdir -p /etc/vulkan/icd.d/ && \
echo "{\n\
\"file_format_version\" : \"1.0.0\",\n\
\"ICD\": {\n\
\"library_path\": \"libGLX_nvidia.so.0\",\n\
\"api_version\" : \"${VULKAN_API_VERSION}\"\n\
}\n\
}" > /etc/vulkan/icd.d/nvidia_icd.json
#
# install VirtualGL and make libraries available for preload
RUN set -eux; \
apt-get update; \
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \
apt-get install -y --no-install-recommends ./virtualgl_${VIRTUALGL_VERSION}_amd64.deb ./virtualgl32_${VIRTUALGL_VERSION}_amd64.deb; \
rm -f "virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \
chmod u+s /usr/lib/libvglfaker.so; \
chmod u+s /usr/lib/libdlfaker.so; \
chmod u+s /usr/lib32/libvglfaker.so; \
chmod u+s /usr/lib32/libdlfaker.so; \
chmod u+s /usr/lib/i386-linux-gnu/libvglfaker.so; \
chmod u+s /usr/lib/i386-linux-gnu/libdlfaker.so; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*;
#
# copy config files
COPY .docker/base/dbus /usr/bin/dbus
COPY .docker/base/default.pa /etc/pulse/default.pa
COPY .docker/base/supervisord.conf /etc/neko/supervisord.conf
COPY .docker/base/xorg.conf /etc/neko/xorg.conf
COPY .docker/base/nvidia/entrypoint.sh /bin/entrypoint.sh
#
# set default envs
ENV USER=$USERNAME
ENV DISPLAY=:99.0
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
ENV XDG_RUNTIME_DIR=/tmp/runtime-$USERNAME
ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080
#
# set gstreamer envs
ENV PATH="/opt/gstreamer/bin:${PATH}"
ENV LD_LIBRARY_PATH="/opt/gstreamer/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
ENV PKG_CONFIG_PATH="/opt/gstreamer/lib/x86_64-linux-gnu/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
#
# copy gstreamer from previous stage
COPY --from=gstreamer /opt/gstreamer /opt/gstreamer
#
# copy static files from previous stages
COPY --from=server /src/bin/neko /usr/bin/neko
COPY --from=client /src/dist/ /var/www
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
#
# run neko
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]

View File

@ -1,8 +1,5 @@
#!/usr/bin/pulseaudio -nF #!/usr/bin/pulseaudio -nF
### Create virtual output device sink
load-module module-null-sink sink_name=audio_output sink_properties=device.description="Virtual\ Audio\ Output"
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices # Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1 load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1

View File

@ -1,12 +0,0 @@
#!/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

View File

@ -54,14 +54,3 @@ stdout_logfile=/var/log/neko/neko.log
stdout_logfile_maxbytes=100MB stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10 stdout_logfile_backups=10
redirect_stderr=true redirect_stderr=true
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
chown=root:root
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

View File

@ -1,23 +0,0 @@
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

View File

@ -1,17 +1,6 @@
[program:brave] [program:brave]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/brave-browser 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
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/brave
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -1,36 +0,0 @@
[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

View File

@ -92,21 +92,6 @@ build_intel() {
fi fi
} }
build_nvidia() {
if [ "$1" = "base" ]
then
# build nvidia base
docker build -t "${BUILD_IMAGE}:nvidia-base" -f base/Dockerfile.nvidia "${BASE}"
elif [ -f "$1/Dockerfile.nvidia" ]
then
# build dedicated nvidia image
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile.nvidia" "$1/"
else
# try to build nvidia image with common Dockerfile
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile" "$1/"
fi
}
case $1 in case $1 in
client) build_client;; client) build_client;;
server) build_server;; server) build_server;;
@ -117,9 +102,6 @@ case $1 in
# build intel- images # build intel- images
intel-*) build_intel "${1#intel-}";; intel-*) build_intel "${1#intel-}";;
# build nvidia- images
nvidia-*) build_nvidia "${1#nvidia-}";;
# build images # build images
*) build "$1";; *) build "$1";;
esac esac

View File

@ -10,7 +10,7 @@ RUN set -eux; \
# #
# install widevine module # install widevine module
CHROMIUM_DIR="/usr/lib/chromium"; \ CHROMIUM_DIR="/usr/lib/chromium"; \
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | sort --version-sort | tail -n 1); \ 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"; \ 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"; \ 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 LICENSE.txt > "${CHROMIUM_DIR}/WidevineCdm/LICENSE"; \

View File

@ -1,37 +0,0 @@
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 | sort --version-sort | 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

View File

@ -1,17 +1,6 @@
[program:chromium] [program:chromium]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/chromium 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
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/chromium
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -1,36 +0,0 @@
[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

View File

@ -1,35 +0,0 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US"
#
# install firefox
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox \
xz-utils bzip2 libgtk-3-0 libdbus-glib-1-2; \
#
# fetch latest release
wget -O /tmp/firefox-setup.tar.bz2 "${SRC_URL}"; \
mkdir /usr/lib/firefox; \
tar -xjf /tmp/firefox-setup.tar.bz2 -C /usr/lib; \
rm -f /tmp/firefox-setup.tar.bz2; \
ln -s /usr/lib/firefox/firefox /usr/bin/firefox; \
#
# create a profile directory
mkdir -p /home/neko/.mozilla/firefox/profile.default/extensions; \
chown -R neko:neko /home/neko/.mozilla/firefox/profile.default; \
#
# clean up
apt-get --purge autoremove -y xz-utils bzip2; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.nvidia.conf /etc/neko/supervisord/firefox.conf
COPY neko.js /usr/lib/firefox/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox/distribution/policies.json
COPY --chown=neko profiles.ini /home/neko/.mozilla/firefox/profiles.ini
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -1,28 +0,0 @@
[program:firefox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/bin/entrypoint.sh /usr/bin/firefox
--no-remote
-P default
--display=%(ENV_DISPLAY)s
-setDefaultBrowser
-width 1280
-height 720
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/firefox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -1,25 +0,0 @@
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
FROM $BASE_IMAGE
# latest working version with EGL: 111.0.5563.146, revert when resolved
# 112.0.5615.49 fails: https://github.com/VirtualGL/virtualgl/issues/229
# google does not provide a direct link to the deb file anymore
# ARG SRC_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_111.0.5563.146-1_amd64.deb"
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

View File

@ -1,17 +1,6 @@
[program:google-chrome] [program:google-chrome]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/google-chrome 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
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/google-chrome
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -1,36 +0,0 @@
[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

View File

@ -1,26 +0,0 @@
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

View File

@ -1,23 +0,0 @@
[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

View File

@ -8,7 +8,7 @@ ARG API_URL="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edg
RUN set -eux; apt-get update; \ RUN set -eux; apt-get update; \
# #
# fetch latest release # fetch latest release
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | sort --version-sort | tail -1)"; \ 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}"; \ wget -O /tmp/microsoft-edge.deb "${SRC_URL}"; \
apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \ apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \
# #

View File

@ -1,24 +0,0 @@
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' | sort --version-sort | 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

View File

@ -1,17 +1,6 @@
[program:microsoft-edge] [program:microsoft-edge]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/microsoft-edge 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
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/microsoft-edge
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -1,36 +0,0 @@
[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

View File

@ -9,7 +9,7 @@ ARG LIBFFMPEG_API_URL="https://api.github.com/repos/nwjs-ffmpeg-prebuilt/nwjs-ff
RUN set -eux; apt-get update; \ RUN set -eux; apt-get update; \
# #
# fetch latest release # fetch latest release
VERSION="$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"/]*\).*/\1/p' | sort --version-sort | tail -1)"; \ VERSION="$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"/]*\).*/\1/p' | tail -1)"; \
wget -O /tmp/opera.deb "${API_URL}${VERSION}/linux/opera-stable_${VERSION}_amd64.deb"; \ wget -O /tmp/opera.deb "${API_URL}${VERSION}/linux/opera-stable_${VERSION}_amd64.deb"; \
apt-get install -y --no-install-recommends openbox jq unzip /tmp/opera.deb; \ apt-get install -y --no-install-recommends openbox jq unzip /tmp/opera.deb; \
# #

View File

@ -17,9 +17,5 @@ fi
docker run --rm -it \ docker run --rm -it \
-v "${PWD}/../server:/src" \ -v "${PWD}/../server:/src" \
-e GIT_COMMIT=`git rev-parse --short HEAD` \ --entrypoint="go" \
-e GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD` \ neko_dev_server build -o "bin/neko" "cmd/neko/main.go"
-e GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"` \
--entrypoint="bash" \
--workdir="/src" \
neko_dev_server ./build

View File

@ -16,7 +16,7 @@ if [ ! -d "${PWD}/../client/node_modules" ] || [ "$1" == "-i" ]; then
-v "${PWD}/../client:/app" \ -v "${PWD}/../client:/app" \
--workdir="/app" \ --workdir="/app" \
--entrypoint="npm" \ --entrypoint="npm" \
node:18-bullseye-slim install node:14-buster-slim install
fi fi
docker run --rm -it \ docker run --rm -it \
@ -25,5 +25,5 @@ docker run --rm -it \
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \ -e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
--workdir="/app" \ --workdir="/app" \
--entrypoint="npm" \ --entrypoint="npm" \
node:18-bullseye-slim run serve node:14-buster-slim run serve

View File

@ -17,22 +17,8 @@ if [ ! -f "${BINARY_PATH}" ] || [ "$1" == "-r" ]; then
./rebuild-server ./rebuild-server
fi fi
# if image starts with nvidia- add --gpus all
if [[ "${SERVER_TAG}" == "nvidia-"* ]]; then
GPU_FLAG="--gpus all"
echo "Nvidia GPU acceleration enabled"
fi
# if image starts with intel- add --device /dev/dri
if [[ "${SERVER_TAG}" == "intel-"* ]]; then
GPU_FLAG="--device /dev/dri"
echo "Intel GPU acceleration enabled"
fi
# use --gpus all to enable GPU acceleration
docker run --rm -it \ docker run --rm -it \
--name "neko_dev" \ --name "neko_dev" \
$GPU_FLAG \
-p "${SERVER_PORT}:8080" \ -p "${SERVER_PORT}:8080" \
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \ -p "${SERVER_EPR}:${SERVER_EPR}/udp" \
-e "NEKO_SCREEN=1920x1080@60" \ -e "NEKO_SCREEN=1920x1080@60" \

View File

@ -21,7 +21,7 @@ RUN set -eux; apt-get update; \
chmod 4755 /usr/lib/chromium/chrome-sandbox; \ chmod 4755 /usr/lib/chromium/chrome-sandbox; \
# #
# install widevine module # install widevine module
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | sort --version-sort | tail -n 1); \ 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"; \ wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \ unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \
chmod 644 /usr/lib/chromium/libwidevinecdm.so; \ chmod 644 /usr/lib/chromium/libwidevinecdm.so; \

View File

@ -50,7 +50,7 @@ jobs:
# Will build all images even if some fail. # Will build all images even if some fail.
fail-fast: false fail-fast: false
matrix: matrix:
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce, kde ] tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce ]
env: env:
DOCKER_TAG: ${{ matrix.tags }} DOCKER_TAG: ${{ matrix.tags }}
steps: steps:

View File

@ -81,7 +81,6 @@ jobs:
- tag: remmina - tag: remmina
- tag: vlc - tag: vlc
- tag: xfce - tag: xfce
- tag: kde
env: env:
TAG_NAME: ${{ matrix.tag }} TAG_NAME: ${{ matrix.tag }}
steps: steps:

View File

@ -13,51 +13,16 @@ env:
PLATFORMS: linux/arm64,linux/arm/v7 PLATFORMS: linux/arm64,linux/arm/v7
jobs: jobs:
build-client:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up node
uses: actions/setup-node@v3
with:
node-version: 18.x
-
name: Build client
run: |
cd client
npm install
npm run build
-
name: Upload client dist
uses: actions/upload-artifact@v3
with:
name: client-dist
path: client/dist
build-base: build-base:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# #
# do not run on forks # do not run on forks
# #
if: github.repository_owner == 'm1k1o' if: github.repository_owner == 'm1k1o'
needs: [ build-client ]
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
-
name: Download client dist
uses: actions/download-artifact@v3
with:
name: client-dist
path: client/dist
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1

View File

@ -81,7 +81,6 @@ jobs:
- tag: remmina - tag: remmina
- tag: vlc - tag: vlc
- tag: xfce - tag: xfce
- tag: kde
env: env:
TAG_NAME: ${{ matrix.tag }} TAG_NAME: ${{ matrix.tag }}
DOCKERFILE: ${{ matrix.dockerfile }} DOCKERFILE: ${{ matrix.dockerfile }}

View File

@ -1,124 +0,0 @@
name: "nvidia gpu supported images"
on:
push:
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: m1k1o/neko
TAG_PREFIX: nvidia-
BASE_DOCKERFILE: Dockerfile.nvidia
PLATFORMS: linux/amd64
jobs:
build-base:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Extract metadata (tags, labels) for Docker
uses: docker/metadata-action@v3
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,format=long
-
name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GHCR_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: ./
file: .docker/base/${{ env.BASE_DOCKERFILE }}
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
needs: [ build-base ]
strategy:
# Will build all images even if some fail.
matrix:
include:
- tag: firefox
dockerfile: Dockerfile.nvidia
- tag: brave
dockerfile: Dockerfile.nvidia
- tag: chromium
dockerfile: Dockerfile.nvidia
- tag: google-chrome
dockerfile: Dockerfile.nvidia
- tag: microsoft-edge
dockerfile: Dockerfile.nvidia
env:
TAG_NAME: ${{ matrix.tag }}
DOCKERFILE: ${{ matrix.dockerfile }}
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Extract metadata (tags, labels) for Docker
uses: docker/metadata-action@v3
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}${{ env.TAG_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,format=long
-
name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GHCR_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .docker/${{ env.TAG_NAME }}
file: .docker/${{ env.TAG_NAME }}/${{ env.DOCKERFILE }}
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base:sha-${{ github.sha }}

View File

@ -186,9 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright (C) 2020 Nurdism <nurdism.io@gmail.com> Copyright 2020 Nurdism <nurdism.io@gmail.com>, 2020-2021 m1k1o
Copyright (C) 2020-2023 m1k1o
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -22,7 +22,7 @@
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord"> <img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
</a> </a>
<a href="https://github.com/m1k1o/neko/actions"> <a href="https://github.com/m1k1o/neko/actions">
<img src="https://github.com/m1k1o/neko/actions/workflows/ghcr-amd.yml/badge.svg" alt="build"> <img src="https://github.com/m1k1o/neko/actions/workflows/build.yml/badge.svg" alt="build">
</a> </a>
</p> </p>
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/intro.gif" width="650" height="auto"/> <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. 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 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. 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.
## Use-cases and comparison ## Use-cases and comparison
Neko started as a virtual browser that is streamed using WebRTC to multiple users. 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. - 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, KDE). - In fact, it is not limited to a single program either; you can install a full desktop environment (e.g. XFCE).
- 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. - 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. - 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. - Like implementing RDP or VNC protocol, where neko would only act as WebRTC relay server. This is currently only future.
@ -99,7 +99,6 @@ 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/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/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/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> ... others in <a href="https://github.com/m1k1o/neko-apps">m1k1o/neko-apps</a>
</div> </div>

24598
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.2.0", "@fortawesome/fontawesome-free": "^6.2.0",
"animejs": "^3.2.0", "animejs": "^3.2.0",
"axios": "^1.2.3", "axios": "^0.24.0",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"emoji-datasource": "^6.0.1", "emoji-datasource": "^6.0.1",
"eventemitter3": "^4.0.7", "eventemitter3": "^4.0.7",
@ -42,23 +42,23 @@
}, },
"devDependencies": { "devDependencies": {
"@types/animejs": "^3.1.6", "@types/animejs": "^3.1.6",
"@types/node": "^18.11.18", "@types/node": "^14.18.32",
"@types/vue": "^2.0.0", "@types/vue": "^2.0.0",
"@types/vue-clickaway": "^2.2.0", "@types/vue-clickaway": "^2.2.0",
"@typescript-eslint/eslint-plugin": "^5.0.8", "@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^5.0.8", "@typescript-eslint/parser": "^4.33.0",
"@vue/cli-plugin-babel": "^5.0.8", "@vue/cli-plugin-babel": "^4.5.19",
"@vue/cli-plugin-eslint": "^5.0.8", "@vue/cli-plugin-eslint": "^4.5.19",
"@vue/cli-plugin-typescript": "^5.0.8", "@vue/cli-plugin-typescript": "^4.5.19",
"@vue/cli-plugin-vuex": "^5.0.8", "@vue/cli-plugin-vuex": "^4.5.19",
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^4.5.19",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^11.0.2", "@vue/eslint-config-typescript": "^7.0.0",
"core-js": "^3.26.0", "core-js": "^3.26.0",
"emojilib": "^3.0.7", "emojilib": "^3.0.7",
"eslint": "^8.32.0", "eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.4.1", "eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^9.0.0", "eslint-plugin-vue": "^7.20.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"sass": "^1.55.0", "sass": "^1.55.0",
"sass-loader": "^10.3.1", "sass-loader": "^10.3.1",

View File

@ -1,22 +1,17 @@
<template> <template>
<div id="neko" :class="[!videoOnly && side ? 'expanded' : '']"> <div id="neko" :class="[side ? 'expanded' : '']">
<template v-if="!$client.supported"> <template v-if="!$client.supported">
<neko-unsupported /> <neko-unsupported />
</template> </template>
<template v-else> <template v-else>
<main class="neko-main"> <main class="neko-main">
<div v-if="!videoOnly" class="header-container"> <div v-if="!hideControls" class="header-container">
<neko-header /> <neko-header />
</div> </div>
<div class="video-container"> <div class="video-container">
<neko-video <neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
ref="video"
:hideControls="hideControls"
:extraControls="isEmbedMode"
@control-attempt="controlAttempt"
/>
</div> </div>
<div v-if="!videoOnly" class="room-container"> <div v-if="!hideControls" class="room-container">
<neko-members /> <neko-members />
<div class="room-menu"> <div class="room-menu">
<div class="settings"> <div class="settings">
@ -31,11 +26,11 @@
</div> </div>
</div> </div>
</main> </main>
<neko-side v-if="!videoOnly && side" /> <neko-side v-if="!hideControls && side" />
<neko-connect v-if="!connected" /> <neko-connect v-if="!connected" />
<neko-about v-if="about" /> <neko-about v-if="about" />
<notifications <notifications
v-if="!videoOnly" v-if="!hideControls"
group="neko" group="neko"
position="top left" position="top left"
style="top: 50px; pointer-events: none" style="top: 50px; pointer-events: none"
@ -181,32 +176,8 @@
shakeKbd = false shakeKbd = false
get volume() {
const numberParam = parseFloat(new URL(location.href).searchParams.get('volume') || '1.0')
return Math.max(0.0, Math.min(!isNaN(numberParam) ? numberParam * 100 : 100, 100))
}
get isCastMode() {
return !!new URL(location.href).searchParams.get('cast')
}
get isEmbedMode() {
return !!new URL(location.href).searchParams.get('embed')
}
get hideControls() { get hideControls() {
return this.isCastMode return !!new URL(location.href).searchParams.get('cast')
}
get videoOnly() {
return this.isCastMode || this.isEmbedMode
}
@Watch('volume', { immediate: true })
onVolume(volume: number) {
if (new URL(location.href).searchParams.has('volume')) {
this.$accessor.video.setVolume(volume)
}
} }
@Watch('hideControls', { immediate: true }) @Watch('hideControls', { immediate: true })

View File

@ -132,7 +132,8 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import md, { HtmlOutputRule } from 'simple-markdown'
@Component({ name: 'neko-about' }) @Component({ name: 'neko-about' })
export default class extends Vue { export default class extends Vue {

View File

@ -147,7 +147,8 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { get, set } from '~/utils/localstorage'
@Component({ name: 'neko-connect' }) @Component({ name: 'neko-connect' })
export default class extends Vue { export default class extends Vue {

View File

@ -132,7 +132,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Ref, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { Member } from '~/neko/types' import { Member } from '~/neko/types'
// @ts-ignore // @ts-ignore
@ -229,11 +229,11 @@
} }
} }
adminRelease() { adminRelease(member: Member) {
this.$accessor.remote.adminRelease() this.$accessor.remote.adminRelease()
} }
adminControl() { adminControl(member: Member) {
this.$accessor.remote.adminControl() this.$accessor.remote.adminControl()
} }

View File

@ -287,9 +287,9 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Ref, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { directive as onClickaway } from 'vue-clickaway' import { directive as onClickaway } from 'vue-clickaway'
import { get } from '../utils/localstorage' import { get, set } from '../utils/localstorage'
@Component({ @Component({
name: 'neko-emoji', name: 'neko-emoji',
@ -356,7 +356,7 @@
this.waitingForPaint = false this.waitingForPaint = false
let scrollTop = this._scroll.scrollTop let scrollTop = this._scroll.scrollTop
let active = 0 let active = 0
for (const [i] of this.groups.entries()) { for (const [i, group] of this.groups.entries()) {
let component = this._groups[i] let component = this._groups[i]
if (component && component.offsetTop > scrollTop) { if (component && component.offsetTop > scrollTop) {
break break
@ -368,7 +368,7 @@
} }
} }
onMouseExit() { onMouseExit(event: MouseEvent, emoji: string) {
this.hovered = '' this.hovered = ''
} }
@ -382,7 +382,7 @@
this.$emit('picked', emoji) this.$emit('picked', emoji)
} }
onClickAway() { onClickAway(event: MouseEvent) {
this.$emit('done') this.$emit('done')
} }
} }

View File

@ -334,7 +334,7 @@
onDownloadProgress: (x) => { onDownloadProgress: (x) => {
transfer.progress = x.loaded transfer.progress = x.loaded
if (x.total && transfer.size !== x.total) { if (x.lengthComputable && transfer.size !== x.total) {
transfer.size = x.total transfer.size = x.total
} }
if (transfer.progress === transfer.size) { if (transfer.progress === transfer.size) {

View File

@ -157,7 +157,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { AdminLockResource } from '~/neko/messages' import { AdminLockResource } from '~/neko/messages'
@Component({ name: 'neko-settings' }) @Component({ name: 'neko-settings' })

View File

@ -1,5 +1,5 @@
import md, { SingleNodeParserRule, HtmlOutputRule, defaultRules, State, Rules } from 'simple-markdown' import md, { SingleNodeParserRule, HtmlOutputRule, defaultRules, State, Rules } from 'simple-markdown'
import { Component, Vue, Prop } from 'vue-property-decorator' import { Component, Watch, Vue, Prop } from 'vue-property-decorator'
const { blockQuote, inlineCode, codeBlock, autolink, newline, escape, strong, text, link, url, em, u, br } = const { blockQuote, inlineCode, codeBlock, autolink, newline, escape, strong, text, link, url, em, u, br } =
defaultRules defaultRules

View File

@ -157,7 +157,8 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Ref, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { Member } from '~/neko/types'
import Content from './context.vue' import Content from './context.vue'
import Avatar from './avatar.vue' import Avatar from './avatar.vue'

View File

@ -60,7 +60,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { messages } from '~/locale' import { messages } from '~/locale'
@Component({ name: 'neko-menu' }) @Component({ name: 'neko-menu' })
@ -76,20 +76,5 @@
about() { about() {
this.$accessor.client.toggleAbout() this.$accessor.client.toggleAbout()
} }
mounted() {
const default_lang = new URL(location.href).searchParams.get('lang')
if (default_lang && this.langs.includes(default_lang)) {
this.$i18n.locale = default_lang
}
const show_side = new URL(location.href).searchParams.get('show_side')
if (show_side !== null) {
this.$accessor.client.setSide(show_side === '1')
}
const mute_chat = new URL(location.href).searchParams.get('mute_chat')
if (mute_chat !== null) {
this.$accessor.settings.setSound(mute_chat !== '1')
}
}
} }
</script> </script>

View File

@ -97,7 +97,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Ref, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { ScreenResolution } from '~/neko/types' import { ScreenResolution } from '~/neko/types'
// @ts-ignore // @ts-ignore

View File

@ -304,7 +304,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Watch, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
@Component({ name: 'neko-settings' }) @Component({ name: 'neko-settings' })
export default class extends Vue { export default class extends Vue {

View File

@ -68,7 +68,7 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator' import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
@Component({ name: 'neko-unsupported' }) @Component({ name: 'neko-unsupported' })
export default class extends Vue {} export default class extends Vue {}

View File

@ -13,7 +13,6 @@
class="overlay" class="overlay"
tabindex="0" tabindex="0"
data-gramm="false" data-gramm="false"
:style="{ pointerEvents: hosting ? 'auto' : 'none' }"
@click.stop.prevent @click.stop.prevent
@contextmenu.stop.prevent @contextmenu.stop.prevent
@wheel.stop.prevent="onWheel" @wheel.stop.prevent="onWheel"
@ -22,14 +21,11 @@
@mouseup.stop.prevent="onMouseUp" @mouseup.stop.prevent="onMouseUp"
@mouseenter.stop.prevent="onMouseEnter" @mouseenter.stop.prevent="onMouseEnter"
@mouseleave.stop.prevent="onMouseLeave" @mouseleave.stop.prevent="onMouseLeave"
@touchmove.stop.prevent="onTouchHandler"
@touchstart.stop.prevent="onTouchHandler"
@touchend.stop.prevent="onTouchHandler"
/> />
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="playAndUnmute"> <div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="toggle">
<i class="fas fa-play-circle" /> <i class="fas fa-play-circle" />
</div> </div>
<div v-else-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute"> <div v-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
<i class="fas fa-volume-up" /> <i class="fas fa-volume-up" />
</div> </div>
<div ref="aspect" class="player-aspect" /> <div ref="aspect" class="player-aspect" />
@ -37,7 +33,7 @@
<ul v-if="!fullscreen && !hideControls" class="video-menu top"> <ul v-if="!fullscreen && !hideControls" class="video-menu top">
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li> <li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li> <li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
<li v-if="!controlLocked && !implicitHosting" :class="extraControls || 'extra-control'"> <li class="request-control">
<i <i
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']" :class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
@click.stop.prevent="toggleControl" @click.stop.prevent="toggleControl"
@ -110,12 +106,12 @@
} }
} }
/* usually extra controls are only shown on mobile */ &.request-control {
&.extra-control {
display: none; display: none;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
&.extra-control { &.request-control {
display: inline-block; display: inline-block;
} }
} }
@ -227,15 +223,13 @@
@Ref('resolution') readonly _resolution!: Resolution @Ref('resolution') readonly _resolution!: Resolution
@Ref('clipboard') readonly _clipboard!: Clipboard @Ref('clipboard') readonly _clipboard!: Clipboard
// all controls are hidden (e.g. for cast mode)
@Prop(Boolean) readonly hideControls!: boolean @Prop(Boolean) readonly hideControls!: boolean
// extra controls are shown (e.g. for embed mode)
@Prop(Boolean) readonly extraControls!: boolean
private keyboard = GuacamoleKeyboard() private keyboard = GuacamoleKeyboard()
private observer = new ResizeObserver(this.onResize.bind(this)) private observer = new ResizeObserver(this.onResize.bind(this))
private focused = false private focused = false
private fullscreen = false private fullscreen = false
private startsMuted = true
private mutedOverlay = true private mutedOverlay = true
get admin() { get admin() {
@ -313,15 +307,7 @@
} }
get clipboard_read_available() { get clipboard_read_available() {
return ( return 'clipboard' in navigator && typeof navigator.clipboard.readText === 'function'
'clipboard' in navigator &&
typeof navigator.clipboard.readText === 'function' &&
// Firefox 122+ incorrectly reports that it can read the clipboard but it can't
// instead it hangs when reading clipboard, until user clicks on the page
// and the click itself is not handled by the page at all, also the clipboard
// reads always fail with "Clipboard read operation is not allowed."
navigator.userAgent.indexOf('Firefox') == -1
)
} }
get clipboard_write_available() { get clipboard_write_available() {
@ -353,12 +339,12 @@
} }
@Watch('width') @Watch('width')
onWidthChanged() { onWidthChanged(width: number) {
this.onResize() this.onResize()
} }
@Watch('height') @Watch('height')
onHeightChanged() { onHeightChanged(height: number) {
this.onResize() this.onResize()
} }
@ -375,6 +361,7 @@
onMutedChanged(muted: boolean) { onMutedChanged(muted: boolean) {
if (this._video && this._video.muted != muted) { if (this._video && this._video.muted != muted) {
this._video.muted = muted this._video.muted = muted
this.startsMuted = muted
if (!muted) { if (!muted) {
this.mutedOverlay = false this.mutedOverlay = false
@ -397,29 +384,9 @@
} }
@Watch('playing') @Watch('playing')
async onPlayingChanged(playing: boolean) { onPlayingChanged(playing: boolean) {
if (this._video && this._video.paused && playing) { if (this._video && this._video.paused && playing) {
// if autoplay is disabled, play() will throw an error this.play()
// and we need to properly save the state otherwise we
// would be thinking we're playing when we're not
try {
await this._video.play()
} catch (err: any) {
if (!this._video.muted) {
// video.play() can fail if audio is set due restrictive
// browsers autoplay policy -> retry with muted audio
try {
this.$accessor.video.setMuted(true)
this._video.muted = true
await this._video.play()
} catch (err: any) {
// if it still fails, we're not playing anything
this.$accessor.video.pause()
}
} else {
this.$accessor.video.pause()
}
}
} }
if (this._video && !this._video.paused && !playing) { if (this._video && !this._video.paused && !playing) {
@ -457,6 +424,11 @@
this._video.addEventListener('canplaythrough', () => { this._video.addEventListener('canplaythrough', () => {
this.$accessor.video.setPlayable(true) this.$accessor.video.setPlayable(true)
if (this.autoplay) { if (this.autoplay) {
// start as muted due to restrictive browsers autoplay policy
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
this.$accessor.video.setMuted(true)
}
this.$nextTick(() => { this.$nextTick(() => {
this.$accessor.video.play() this.$accessor.video.play()
}) })
@ -472,7 +444,7 @@
this.$accessor.video.setPlayable(false) this.$accessor.video.setPlayable(false)
}) })
this._video.addEventListener('volumechange', () => { this._video.addEventListener('volumechange', (event) => {
this.$accessor.video.setMuted(this._video.muted) this.$accessor.video.setMuted(this._video.muted)
this.$accessor.video.setVolume(this._video.volume * 100) this.$accessor.video.setVolume(this._video.volume * 100)
}) })
@ -588,11 +560,6 @@
} }
} }
playAndUnmute() {
this.$accessor.video.play()
this.$accessor.video.setMuted(false)
}
unmute() { unmute() {
this.$accessor.video.setMuted(false) this.$accessor.video.setMuted(false)
} }
@ -638,7 +605,7 @@
} }
async syncClipboard() { async syncClipboard() {
if (this.clipboard_read_available && window.document.hasFocus()) { if (this.clipboard_read_available) {
try { try {
const text = await navigator.clipboard.readText() const text = await navigator.clipboard.readText()
if (this.clipboard !== text) { if (this.clipboard !== text) {
@ -700,35 +667,6 @@
} }
} }
onTouchHandler(e: TouchEvent) {
let first = e.changedTouches[0]
let type = ''
switch (e.type) {
case 'touchstart':
type = 'mousedown'
break
case 'touchmove':
type = 'mousemove'
break
case 'touchend':
type = 'mouseup'
break
default:
return
}
const simulatedEvent = new MouseEvent(type, {
bubbles: true,
cancelable: true,
view: window,
screenX: first.screenX,
screenY: first.screenY,
clientX: first.clientX,
clientY: first.clientY,
})
first.target.dispatchEvent(simulatedEvent)
}
onMouseDown(e: MouseEvent) { onMouseDown(e: MouseEvent) {
if (!this.hosting) { if (!this.hosting) {
this.$emit('control-attempt', e) this.$emit('control-attempt', e)

View File

@ -9,7 +9,6 @@ import * as ko from './ko-kr'
import * as fi from './fi-fi' import * as fi from './fi-fi'
import * as ru from './ru-ru' import * as ru from './ru-ru'
import * as cn from './zh-cn' import * as cn from './zh-cn'
import * as tw from './zh-tw'
export const messages = { export const messages = {
en, en,
@ -23,5 +22,4 @@ export const messages = {
fi, fi,
ru, ru,
cn, cn,
tw,
} }

View File

@ -1,127 +0,0 @@
export const logout = '登出'
export const unsupported = '您的網頁瀏覽器不支援 WebRTC'
export const admin_loggedin = '您已經以管理員身份登入'
export const you = '您'
export const somebody = '某人'
export const send_a_message = '傳送訊息'
export const side = {
chat: '聊天',
files: '檔案',
settings: '設定',
}
export const connect = {
login_title: '請登入',
invitation_title: '您已被邀請進入此房間',
displayname: '輸入您的顯示名稱',
password: '密碼',
connect: '連線',
error: '登入錯誤',
empty_displayname: '顯示名稱不能為空。',
}
export const context = {
ignore: '忽略',
unignore: '取消忽略',
mute: '靜音',
unmute: '解除靜音',
release: '強制釋放控制',
take: '強制接管控制',
give: '移交控制',
kick: '踢出',
ban: '封鎖 IP',
confirm: {
kick_title: '踢出 {name}',
kick_text: '您確定要踢出 {name} 嗎?',
ban_title: '封鎖 {name}',
ban_text: '您是否要封鎖 {name}?封鎖後需要重新啟動伺服器才能取消封鎖。',
mute_title: '靜音 {name}',
mute_text: '您確定要將 {name} 設為靜音嗎?',
unmute_title: '解除靜音 {name}',
unmute_text: '您是否要解除 {name} 的靜音?',
button_yes: '是',
button_cancel: '取消',
},
}
export const controls = {
release: '釋放控制',
request: '請求控制',
lock: '鎖定控制',
unlock: '解鎖控制',
has: '您擁有控制權',
hasnot: '您沒有控制權',
}
export const locks = {
control: {
lock: '鎖定控制(對使用者)',
unlock: '解鎖控制(對使用者)',
locked: '已鎖定控制(對使用者)',
unlocked: '已解鎖控制(對使用者)',
notif_locked: '已鎖定使用者控制',
notif_unlocked: '已解鎖使用者控制',
},
login: {
lock: '鎖定房間(對使用者)',
unlock: '解鎖房間(對使用者)',
locked: '房間已鎖定(對使用者)',
unlocked: '房間已解鎖(對使用者)',
notif_locked: '已鎖定房間',
notif_unlocked: '已解鎖房間',
},
file_transfer: {
lock: '鎖定檔案傳輸(對使用者)',
unlock: '解鎖檔案傳輸(對使用者)',
locked: '檔案傳輸已鎖定(對使用者)',
unlocked: '檔案傳輸已解鎖(對使用者)',
notif_locked: '已鎖定檔案傳輸',
notif_unlocked: '已解鎖檔案傳輸',
},
}
export const setting = {
scroll: '滾動靈敏度',
scroll_invert: '反向滾動',
autoplay: '自動播放影片',
ignore_emotes: '忽略表情符號',
chat_sound: '播放聊天音效',
keyboard_layout: '鍵盤配置',
broadcast_title: '直播',
}
export const connection = {
logged_out: '您已登出。',
reconnecting: '正在重新連線…',
connected: '已連線',
disconnected: '已斷線',
kicked: '您已被移出此房間。',
button_confirm: '確定',
}
export const notifications = {
connected: '{name} 已連線',
disconnected: '{name} 已斷線',
controls_taken: '{name} 接管了控制權',
controls_taken_force: '強制接管控制權',
controls_taken_steal: '從 {name} 奪取了控制權',
controls_released: '{name} 釋放了控制權',
controls_released_force: '強制釋放控制權',
controls_released_steal: '從 {name} 強制釋放控制權',
controls_given: '將控制權交給 {name}',
controls_has: '{name} 擁有控制權',
controls_has_alt: '但我已通知對方您有意接管',
controls_requesting: '{name} 正在請求控制權',
resolution: '將解析度改為 {width}x{height}@{rate}',
banned: '已封鎖 {name}',
kicked: '已踢出 {name}',
muted: '已將 {name} 靜音',
unmuted: '已解除 {name} 的靜音',
}
export const files = {
downloads: '下載',
uploads: '上傳',
upload_here: '點選或拖曳檔案至此上傳',
}

View File

@ -66,8 +66,8 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._ws = new WebSocket(`${url}?password=${encodeURIComponent(password)}`) this._ws = new WebSocket(`${url}?password=${encodeURIComponent(password)}`)
this.emit('debug', `connecting to ${this._ws.url}`) this.emit('debug', `connecting to ${this._ws.url}`)
this._ws.onmessage = this.onMessage.bind(this) this._ws.onmessage = this.onMessage.bind(this)
this._ws.onerror = () => this.onError.bind(this) this._ws.onerror = (event) => this.onError.bind(this)
this._ws.onclose = () => this.onDisconnected.bind(this, new Error('websocket closed')) this._ws.onclose = (event) => this.onDisconnected.bind(this, new Error('websocket closed'))
this._timeout = window.setTimeout(this.onTimeout.bind(this), 15000) this._timeout = window.setTimeout(this.onTimeout.bind(this), 15000)
} catch (err: any) { } catch (err: any) {
this.onDisconnected(err) this.onDisconnected(err)
@ -203,23 +203,22 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return return
} }
this._peer = new RTCPeerConnection()
if (lite !== true) { if (lite !== true) {
this._peer = new RTCPeerConnection({ this._peer = new RTCPeerConnection({
iceServers: servers, iceServers: servers,
}) })
} else {
this._peer = new RTCPeerConnection()
} }
this._peer.onconnectionstatechange = () => { this._peer.onconnectionstatechange = (event) => {
this.emit('debug', `peer connection state changed`, this._peer ? this._peer.connectionState : undefined) this.emit('debug', `peer connection state changed`, this._peer ? this._peer.connectionState : undefined)
} }
this._peer.onsignalingstatechange = () => { this._peer.onsignalingstatechange = (event) => {
this.emit('debug', `peer signaling state changed`, this._peer ? this._peer.signalingState : undefined) this.emit('debug', `peer signaling state changed`, this._peer ? this._peer.signalingState : undefined)
} }
this._peer.oniceconnectionstatechange = () => { this._peer.oniceconnectionstatechange = (event) => {
this._state = this._peer!.iceConnectionState this._state = this._peer!.iceConnectionState
this.emit('debug', `peer ice connection state changed: ${this._peer!.iceConnectionState}`) this.emit('debug', `peer ice connection state changed: ${this._peer!.iceConnectionState}`)
@ -252,28 +251,11 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._peer.ontrack = this.onTrack.bind(this) this._peer.ontrack = this.onTrack.bind(this)
this._peer.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
if (!event.candidate) {
this.emit('debug', `sent all local ICE candidates`)
return
}
const init = event.candidate.toJSON()
this.emit('debug', `sending local ICE candidate`, init)
this._ws!.send(
JSON.stringify({
event: EVENT.SIGNAL.CANDIDATE,
data: JSON.stringify(init),
}),
)
}
this._peer.onnegotiationneeded = async () => { this._peer.onnegotiationneeded = async () => {
this.emit('warn', `negotiation is needed`) this.emit('warn', `negotiation is needed`)
const d = await this._peer!.createOffer() const d = await this._peer!.createOffer()
await this._peer!.setLocalDescription(d) this._peer!.setLocalDescription(d)
this._ws!.send( this._ws!.send(
JSON.stringify({ JSON.stringify({
@ -295,10 +277,10 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return return
} }
await this._peer.setRemoteDescription({ type: 'offer', sdp }) this._peer.setRemoteDescription({ type: 'offer', sdp })
for (const candidate of this._candidates) { for (const candidate of this._candidates) {
await this._peer.addIceCandidate(candidate) this._peer.addIceCandidate(candidate)
} }
this._candidates = [] this._candidates = []
@ -328,7 +310,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return return
} }
await this._peer.setRemoteDescription({ type: 'answer', sdp }) this._peer.setRemoteDescription({ type: 'answer', sdp })
} }
private async onMessage(e: MessageEvent) { private async onMessage(e: MessageEvent) {

View File

@ -7,6 +7,7 @@ import { accessor } from '~/store'
import { import {
SystemMessagePayload, SystemMessagePayload,
SignalProvidePayload,
MemberListPayload, MemberListPayload,
MemberDisconnectPayload, MemberDisconnectPayload,
MemberPayload, MemberPayload,
@ -18,6 +19,7 @@ import {
ScreenConfigurationsPayload, ScreenConfigurationsPayload,
ScreenResolutionPayload, ScreenResolutionPayload,
BroadcastStatusPayload, BroadcastStatusPayload,
AdminPayload,
AdminTargetPayload, AdminTargetPayload,
AdminLockMessage, AdminLockMessage,
SystemInitPayload, SystemInitPayload,
@ -129,7 +131,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.video.setStream(0) this.$accessor.video.setStream(0)
} }
protected [EVENT.DATA]() {} protected [EVENT.DATA](data: any) {}
///////////////////////////// /////////////////////////////
// System Events // System Events

View File

@ -6,6 +6,5 @@ Vue.use(VueI18n)
export const i18n = new VueI18n({ export const i18n = new VueI18n({
locale: 'en', locale: 'en',
fallbackLocale: 'en',
messages, messages,
}) })

View File

@ -10,7 +10,7 @@ declare module 'vue/types/vue' {
$swal: VueSwalInstance $swal: VueSwalInstance
} }
interface VueConstructor { interface VueConstructor<V extends Vue = Vue> {
swal: VueSwalInstance swal: VueSwalInstance
} }
} }

View File

@ -73,7 +73,7 @@ export const actions = actionTree(
accessor.chat.addEmote({ id, emote }) accessor.chat.addEmote({ id, emote })
}, },
newMessage(store, message: Message) { newMessage({ state }, message: Message) {
if (accessor.settings.chat_sound) { if (accessor.settings.chat_sound) {
new Audio('chat.mp3').play().catch(console.error) new Audio('chat.mp3').play().catch(console.error)
} }

View File

@ -27,10 +27,6 @@ export const mutations = mutationTree(state, {
state.side = !state.side state.side = !state.side
set('side', state.side) set('side', state.side)
}, },
setSide(state, side: boolean) {
state.side = side
set('side', side)
},
}) })
export const actions = actionTree({ state, getters, mutations }, {}) export const actions = actionTree({ state, getters, mutations }, {})

View File

@ -53,7 +53,7 @@ export const actions = actionTree(
accessor.files._removeTransfer(transfer) accessor.files._removeTransfer(transfer)
}, },
cancelAllTransfers() { cancelAllTransfers(store) {
for (const t of accessor.files.transfers) { for (const t of accessor.files.transfers) {
if (t.status !== 'completed') { if (t.status !== 'completed') {
t.abortController?.abort() t.abortController?.abort()
@ -62,7 +62,7 @@ export const actions = actionTree(
} }
}, },
refresh() { refresh(store) {
if (!accessor.connected) { if (!accessor.connected) {
return return
} }

View File

@ -63,7 +63,7 @@ export const getters = getterTree(state, {
export const actions = actionTree( export const actions = actionTree(
{ state, getters, mutations }, { state, getters, mutations },
{ {
initialise() { initialise(store) {
accessor.emoji.initialise() accessor.emoji.initialise()
accessor.settings.initialise() accessor.settings.initialise()
}, },
@ -92,12 +92,12 @@ export const actions = actionTree(
} }
}, },
login(store, { displayname, password }: { displayname: string; password: string }) { login({ state }, { displayname, password }: { displayname: string; password: string }) {
accessor.setLogin({ displayname, password }) accessor.setLogin({ displayname, password })
$client.login(password, displayname) $client.login(password, displayname)
}, },
logout() { logout({ state }) {
accessor.setLogin({ displayname: '', password: '' }) accessor.setLogin({ displayname: '', password: '' })
set('displayname', '') set('displayname', '')
set('password', '') set('password', '')

View File

@ -21,7 +21,7 @@ export const getters = getterTree(state, {
hosting: (state, getters, root) => { hosting: (state, getters, root) => {
return root.user.id === state.id || state.implicitHosting return root.user.id === state.id || state.implicitHosting
}, },
hosted: (state) => { hosted: (state, getters, root) => {
return state.id !== '' || state.implicitHosting return state.id !== '' || state.implicitHosting
}, },
host: (state, getters, root) => { host: (state, getters, root) => {
@ -136,7 +136,7 @@ export const actions = actionTree(
$client.sendMessage(EVENT.ADMIN.RELEASE) $client.sendMessage(EVENT.ADMIN.RELEASE)
}, },
adminGive(store, member: string | Member) { adminGive({ getters }, member: string | Member) {
if (!accessor.connected) { if (!accessor.connected) {
return return
} }
@ -160,7 +160,7 @@ export const actions = actionTree(
$client.sendMessage(EVENT.CONTROL.KEYBOARD, { layout: accessor.settings.keyboard_layout }) $client.sendMessage(EVENT.CONTROL.KEYBOARD, { layout: accessor.settings.keyboard_layout })
}, },
syncKeyboardModifierState({ state }, { capsLock, numLock, scrollLock }) { syncKeyboardModifierState({ state, getters }, { capsLock, numLock, scrollLock }) {
if (state.keyboardModifierState === keyboardModifierState(capsLock, numLock, scrollLock)) { if (state.keyboardModifierState === keyboardModifierState(capsLock, numLock, scrollLock)) {
return return
} }

View File

@ -79,13 +79,13 @@ export const actions = actionTree(
} }
}, },
broadcastStatus(store, { url, isActive }) { broadcastStatus({ getters }, { url, isActive }) {
accessor.settings.setBroadcastStatus({ url, isActive }) accessor.settings.setBroadcastStatus({ url, isActive })
}, },
broadcastCreate(store, url: string) { broadcastCreate({ getters }, url: string) {
$client.sendMessage(EVENT.BROADCAST.CREATE, { url }) $client.sendMessage(EVENT.BROADCAST.CREATE, { url })
}, },
broadcastDestroy() { broadcastDestroy({ getters }) {
$client.sendMessage(EVENT.BROADCAST.DESTROY) $client.sendMessage(EVENT.BROADCAST.DESTROY)
}, },
}, },

View File

@ -169,7 +169,7 @@ export const mutations = mutationTree(state, {
export const actions = actionTree( export const actions = actionTree(
{ state, getters, mutations }, { state, getters, mutations },
{ {
screenConfiguations() { screenConfiguations({ state }) {
if (!accessor.connected || !accessor.user.admin) { if (!accessor.connected || !accessor.user.admin) {
return return
} }
@ -177,7 +177,7 @@ export const actions = actionTree(
$client.sendMessage(EVENT.SCREEN.CONFIGURATIONS) $client.sendMessage(EVENT.SCREEN.CONFIGURATIONS)
}, },
screenGet() { screenGet({ state }) {
if (!accessor.connected) { if (!accessor.connected) {
return return
} }
@ -185,7 +185,7 @@ export const actions = actionTree(
$client.sendMessage(EVENT.SCREEN.RESOLUTION) $client.sendMessage(EVENT.SCREEN.RESOLUTION)
}, },
screenSet(store, resolution: ScreenResolution) { screenSet({ state }, resolution: ScreenResolution) {
if (!accessor.connected || !accessor.user.admin) { if (!accessor.connected || !accessor.user.admin) {
return return
} }

View File

@ -3,13 +3,13 @@
// Type declarations for Keyboard API // Type declarations for Keyboard API
// https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API // https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API
interface Keyboard { interface Keyboard {
lock(keyCodes?: array<string>): Promise<void> lock(keyCodes?: array<string>): Promise<void>;
unlock(): void unlock(): void;
} }
interface NavigatorKeyboard { interface NavigatorKeyboard {
// Only available in a secure context. // Only available in a secure context.
readonly keyboard?: Keyboard readonly keyboard?: Keyboard;
} }
interface Navigator extends NavigatorKeyboard {} interface Navigator extends NavigatorKeyboard {}

View File

@ -10,13 +10,13 @@ export function makeid(length: number) {
export function lockKeyboard() { export function lockKeyboard() {
if (navigator && navigator.keyboard) { if (navigator && navigator.keyboard) {
navigator.keyboard.lock() navigator.keyboard.lock();
} }
} }
export function unlockKeyboard() { export function unlockKeyboard() {
if (navigator && navigator.keyboard) { if (navigator && navigator.keyboard) {
navigator.keyboard.unlock() navigator.keyboard.unlock();
} }
} }

View File

@ -22,6 +22,6 @@ module.exports = {
}, },
}, },
devServer: { devServer: {
allowedHosts: 'all', disableHostCheck: true,
}, },
} }

View File

@ -21,9 +21,9 @@ Neko is also a great tool for **hosting watch parties** and interactive presenta
## About ## About
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. 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 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. 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.
### Features ### 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. 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 anyway. ***But why the cat butt?*** Because cats are *assholes*, but you love them anyways.

View File

@ -1,8 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -6,7 +6,6 @@
* [Reverse Proxy](/getting-started/reverse-proxy) * [Reverse Proxy](/getting-started/reverse-proxy)
* [Configuration](/getting-started/configuration) * [Configuration](/getting-started/configuration)
* [Troubleshooting](/getting-started/troubleshooting) * [Troubleshooting](/getting-started/troubleshooting)
* [Frequently Asked Questions](/getting-started/faq)
* [Mobile Support](/mobile-support) * [Mobile Support](/mobile-support)
* [Contributing](/contributing) * [Contributing](/contributing)
* [Non Goals](/non-goals) * [Non Goals](/non-goals)

View File

@ -2,52 +2,10 @@
## master branch ## master branch
### New Features
- Added nvidia support for firefox.
- Added `?lang=<lang>` parameter to the URL, which will set the language of the interface (by @mbattista).
- Added `?show_side=1` and `?mute_chat=1` parameter to the URL, for chat mute and show side (by @mbattista).
- Added `NEKO_BROADCAST_AUTOSTART` to automatically start or do not start broadcasting when the room is created. By default, it is set to `true` because it was the previous behavior.
### Bugs
- Fix incorrect version sorting for chromium, microsoft-edge, opera and ungoogledchromium.
- Fix buffer overflow in Gstreamer log function [#382](https://github.com/m1k1o/neko/pull/382) (by @@tt2468).
### Misc
- Added RTMP broadcast support to nvidia docker image [#274](https://github.com/m1k1o/neko/issues/274).
- Ensured that paths are writable by neko user [#277](https://github.com/m1k1o/neko/issues/277).
- Git commit and tag are now included in the build when creating a docker image.
- Remove any temporary files associated with a Form after file upload, that would be otherwise never removed.
- Add check for volume parameter in URL before setting volume (by @FapFapDragon).
- Add glib main loop to capture manager [#383](https://github.com/m1k1o/neko/pull/383) (by @tt2468).
## [n.eko v2.8.0](https://github.com/m1k1o/neko/releases/tag/v2.8.0)
### New Features
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
- New VirtualGL version 3.1 was released, adding support for Chromium browsers to use Nvidia GPU acceleration!
- Added `?embed=1` parameter to the URL, which will hide the sidebar and the top bar, so that it can be embedded in other websites.
- Added `?volume=<0-1>` parameter to the URL, which will set the inital volume of the player (by @urbanekpj).
- Touch events are now supported on mobile devices (by @urbanekpj).
- Added NVENC support, hardware h264 encoding for Nvidia GPUs!
- Fixed an issue where `nvh264enc` did not send SPS and PPS NAL units (by @mbattista).
### Bugs ### Bugs
- Fixed TCP mux occasional freeze by adding write buffer to it. - Fixed TCP mux occasional freeze by adding write buffer to it.
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer. - Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards. - Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
- Fixed client side webrtc ICE gathering, so that neko can be used without exposed ports, only with STUN and TURN servers.
- Fixed play state synchronization, when autoplay is disabled.
### Misc
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
- Added adaptive framerate which now streams in the framerate you selected from the dropdown.
- Improved chinese and korean characters support.
- Disabled autolock for kde, so that it does not lock the screen when you are not using it.
- Refactored autoplay, so that it will start playing audio, if it's allowed by the browser (by @urbanekpj).
- Renamed pulseaudio sink from `auto_null` to `audio_output`, because it was ignored by KDE.
- Pulseaudio is now configured using environment variables, so that users can mount `/home/neko` without losing audio configuration.
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7) ## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)

View File

@ -10,7 +10,7 @@
## Server build dependencies ## Server build dependencies
If you want to compile Golang code locally, you must install additional dependencies in order for it to compile. If you want to compile goalng code locally, you must install additional dependencies in order for it to compile.
```shell ```shell
apt-get install -y --no-install-recommends libx11-dev libxrandr-dev libxtst-dev libgstreamer1.0-dev apt-get install -y --no-install-recommends libx11-dev libxrandr-dev libxtst-dev libgstreamer1.0-dev

View File

@ -12,7 +12,6 @@
<img src="../_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/> <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/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/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> </div>
Use the following docker images from [Docker Hub](https://hub.docker.com/r/m1k1o/neko) for x86_64: Use the following docker images from [Docker Hub](https://hub.docker.com/r/m1k1o/neko) for x86_64:
@ -29,7 +28,7 @@ Use the following docker images from [Docker Hub](https://hub.docker.com/r/m1k1o
- Pass env var `REMMINA_URL=<proto>://[<username>[:<password>]@]server[:port]` (proto being `vnc`, `rdp` or `spice`). - 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>`. - 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:vlc` - for VLC Video player (needs volume mounted to `/media` with local video files, or setting `VLC_MEDIA=/media` path).
- `m1k1o/neko:xfce` or `m1k1o/neko:kde` - for a shared desktop / installing shared software. - `m1k1o/neko:xfce` - for a shared desktop / installing shared software.
- `m1k1o/neko:base` - for custom base. - `m1k1o/neko:base` - for custom base.
Dockerhub images are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date. Dockerhub images are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date.
@ -48,9 +47,8 @@ All images are also available on [GitHub Container Registry](https://github.com/
- `ghcr.io/m1k1o/neko/remmina:latest` - `ghcr.io/m1k1o/neko/remmina:latest`
- `ghcr.io/m1k1o/neko/vlc:latest` - `ghcr.io/m1k1o/neko/vlc:latest`
- `ghcr.io/m1k1o/neko/xfce: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. Please note, that `m1k1o/neko:arm-*` images from dockerhub are currently not maintained and they can contain outdated software. Please use images below: 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-firefox:latest`
- `ghcr.io/m1k1o/neko/arm-chromium:latest` - `ghcr.io/m1k1o/neko/arm-chromium:latest`
@ -72,63 +70,13 @@ For images with VAAPI GPU hardware acceleration using intel drivers use:
- `ghcr.io/m1k1o/neko/intel-remmina:latest` - `ghcr.io/m1k1o/neko/intel-remmina:latest`
- `ghcr.io/m1k1o/neko/intel-vlc:latest` - `ghcr.io/m1k1o/neko/intel-vlc:latest`
- `ghcr.io/m1k1o/neko/intel-xfce:latest` - `ghcr.io/m1k1o/neko/intel-xfce:latest`
- `ghcr.io/m1k1o/neko/intel-kde:latest`
For images with Nvidia GPU hardware acceleration using EGL (see example below) use (please note, there is a known issue with EGL and Chromium-based browsers, see [here](https://github.com/m1k1o/neko/issues/279)):
- `ghcr.io/m1k1o/neko/nvidia-firefox:latest`
- `ghcr.io/m1k1o/neko/nvidia-chromium:latest`
- `ghcr.io/m1k1o/neko/nvidia-google-chrome:latest`
- `ghcr.io/m1k1o/neko/nvidia-microsoft-edge:latest`
- `ghcr.io/m1k1o/neko/nvidia-brave:latest`
GHCR images are built using GitHub actions for every tag. GHCR images are built using GitHub actions for every tag.
### Networking: ### Networking:
- If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP. - If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP.
- If you want to use n.eko in **internal** network, set `NEKO_NAT1TO1` to your local IP address (e.g. `NEKO_NAT1TO1: 192.168.1.20`)- - If you want to use n.eko in **internal** network, set `NEKO_NAT1TO1` to your local IP address (e.g. `NEKO_NAT1TO1: 192.168.1.20`)-
- Currently, it is not supported to supply multiple NAT addresses (see https://github.com/m1k1o/neko/issues/47).
Currently, it is not supported to supply multiple NAT addresses directly to neko (see https://github.com/m1k1o/neko/issues/47).
But it can be acheived by deploying own turn server alongside neko that is accessible from your LAN:
```yaml
version: "3.4"
services:
neko:
image: "m1k1o/neko:firefox"
restart: "unless-stopped"
shm_size: "2gb"
ports:
- "8080:8080"
- "52000-52100:52000-52100/udp"
environment:
NEKO_SCREEN: 1920x1080@30
NEKO_PASSWORD: neko
NEKO_PASSWORD_ADMIN: admin
NEKO_EPR: 52000-52100
NEKO_ICESERVERS: '[{ "urls": [ "turn:192.168.1.60:3478" ], "username":"neko", "credential":"neko" }, { "urls": [ "stun:stun.nextcloud.com:3478" ] }]'
coturn:
image: 'coturn/coturn:latest'
network_mode: "host"
command: |
-n
--realm=localhost
--fingerprint
--listening-ip=0.0.0.0
--external-ip=192.168.1.60
--listening-port=3478
--min-port=49160
--max-port=49200
--log-file=stdout
--user=neko:neko
--lt-cred-mech
```
- Replace `192.168.1.60` with your LAN IP address, and allow ports `49160-49200/udp` and `3478/tcp` in your LAN.
- Make sure you don't use `NEKO_ICELITE: true` because ICE LITE does not support TURN servers.
This setup adds local turn server to neko. It won't be reachable by your remote clients and your own IP won't be reachable from your lan. So it effectively just adds local candidate and allows connections from LAN.
### Why so many ports? ### Why so many ports?
- WebRTC needs UDP ports in order to transfer Audio/Video towards user and Mouse/Keyboard events to the server in real time. - WebRTC needs UDP ports in order to transfer Audio/Video towards user and Mouse/Keyboard events to the server in real time.
@ -173,17 +121,6 @@ services:
- UDP is generally better for latency. But some networks block UDP so it is good to have TCP available as fallback. - UDP is generally better for latency. But some networks block UDP so it is good to have TCP available as fallback.
- Still, using `NEKO_ICELITE=true` is recommended. - Still, using `NEKO_ICELITE=true` is recommended.
### Using turn servers instead of port forwarding
- If you don't want to use port forwarding, you can use turn servers.
- But you need to have your own turn server (e.g. [cotrun](https://github.com/coturn/coturn)) or have access to one.
- They are generally not free, because they require a lot of bandwidth.
- Please make sure that you correctly escape your turn server credentials in the environment variable or use aphostrophes.
```yaml
NEKO_ICESERVERS: '[{"urls": ["turn:<MY-COTURN-SERVER>:443?transport=udp", "turn:<MY-COTURN-SERVER>:443?transport=tcp", "turns:<MY-COTURN-SERVER>:443?transport=udp", "turns:<MY-COTURN-SERVER>:443?transport=tcp"], "credential": "<MY-COTURN-CREDENTIAL"}, {"urls": ["stun:stun.nextcloud.com:443"]}]'
```
### Want to customize and install own add-ons, set custom bookmarks? ### Want to customize and install own add-ons, set custom bookmarks?
- You would need to modify the existing policy file and mount it to your container. - You would need to modify the existing policy file and mount it to your container.
- For Firefox, copy [this](https://github.com/m1k1o/neko/blob/master/.docker/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/lib/firefox/distribution/policies.json'` - For Firefox, copy [this](https://github.com/m1k1o/neko/blob/master/.docker/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/lib/firefox/distribution/policies.json'`
@ -191,7 +128,7 @@ NEKO_ICESERVERS: '[{"urls": ["turn:<MY-COTURN-SERVER>:443?transport=udp", "turn:
- For others, see where existing `policies.json` is placed in their `Dockerfile`. - For others, see where existing `policies.json` is placed in their `Dockerfile`.
#### Allow file uploading & downloading #### Allow file uploading & downloading
- From security perspective, browser is not enabled to access local file data. - From security perespective, browser is not enabled to access local file data.
- If you want to enable this, you need to modify following policies: - If you want to enable this, you need to modify following policies:
```json ```json
"DownloadRestrictions": 0, "DownloadRestrictions": 0,
@ -208,76 +145,13 @@ NEKO_ICESERVERS: '[{"urls": ["turn:<MY-COTURN-SERVER>:443?transport=udp", "turn:
- For other chromium based browsers, see in `supervisord.conf` folder that is specified in `--user-data-dir`. - For other chromium based browsers, see in `supervisord.conf` folder that is specified in `--user-data-dir`.
#### Allow persistent data in policies #### Allow persistent data in policies
- From security perspective, browser is set up to forget all cookies and browsing history when its closed. - From security perespective, browser is set up to forget all cookies and brwosing history when its closed.
- If you want to enable this, you need to modify following policies: - If you want to enable this, you need to modify following policies:
```json ```json
"DefaultCookiesSetting": 1, "DefaultCookiesSetting": 1,
"RestoreOnStartup": 1, "RestoreOnStartup": 1,
``` ```
### Nvidia GPU acceleration
You need to have [NVIDIA Container Toolkit](https://github.com/NVIDIA/nvidia-container-toolkit) installed, start the container with `--gpus all` flag and use images built for nvidia (see above).
```bash
docker run -d --gpus all \
-p 8080:8080 \
-p 56000-56100:56000-56100/udp \
-e NEKO_SCREEN=1920x1080@30 \
-e NEKO_PASSWORD=neko \
-e NEKO_PASSWORD_ADMIN=admin \
-e NEKO_EPR=56000-56100 \
-e NEKO_NAT1TO1=192.168.1.10 \
-e NEKO_ICELITE=1 \
-e NEKO_VIDEO_CODEC=h264 \
-e NEKO_HWENC=nvenc \
--shm-size=2gb \
--cap-add=SYS_ADMIN \
--name neko \
ghcr.io/m1k1o/neko/nvidia-google-chrome:latest
```
If you want to use docker-compose, you can use this example:
```yaml
version: "3.4"
services:
neko:
image: "ghcr.io/m1k1o/neko/nvidia-google-chrome:latest"
restart: "unless-stopped"
shm_size: "2gb"
ports:
- "8080:8080"
- "56000-56100:56000-56100/udp"
cap_add:
- SYS_ADMIN
environment:
NEKO_SCREEN: '1920x1080@30'
NEKO_PASSWORD: neko
NEKO_PASSWORD_ADMIN: admin
NEKO_EPR: 56000-56100
NEKO_NAT1TO1: 192.168.1.10
NEKO_VIDEO_CODEC: h264
NEKO_HWENC: nvenc
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```
- You can verify that GPU is available inside the container by running `docker exec -it neko nvidia-smi` command.
- You can verify that GPU is used for encoding by searching for `nvh264enc` in `docker logs neko` output.
- If you don'ŧ specify `NEKO_HWENC: nvenc` environment variable, CPU encoding will be used but GPU will still be available for browser rendering.
Broadcast pipeline is not hardware accelerated by default. You can use this pipeline created by [@evilalmus](https://github.com/m1k1o/neko/issues/276#issuecomment-1498362533).
```yaml
NEKO_BROADCAST_PIPELINE: "flvmux name=mux ! rtmpsink location={url} pulsesrc device={device} ! audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. ximagesrc display-name={display} show-pointer=false use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! nvh264enc name=encoder preset=low-latency-hq gop-size=25 spatial-aq=true temporal-aq=true bitrate=2800 vbv-buffer-size=2800 rc-mode=6 ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,profile=high ! h264parse ! mux."
```
### Want to use VPN for your n.eko browsing? ### Want to use VPN for your n.eko browsing?
- Check this out: https://github.com/m1k1o/neko-vpn - Check this out: https://github.com/m1k1o/neko-vpn
@ -295,11 +169,6 @@ NEKO_BROADCAST_PIPELINE: "flvmux name=mux ! rtmpsink location={url} pulsesrc dev
- Adding `?pwd=<password>` will prefill password. - Adding `?pwd=<password>` will prefill password.
- Adding `?usr=<display-name>` will prefill username. - Adding `?usr=<display-name>` will prefill username.
- Adding `?cast=1` will hide all control and show only video. - Adding `?cast=1` will hide all control and show only video.
- Adding `?embed=1` will hide most additional components and show only video.
- Adding `?volume=<0-1>` will set volume to given value.
- Adding `?lang=<language>` will set language to given value.
- Adding `?show_side=1` will show the sidebar on startup.
- Adding `?mute_chat=1` will mute the chat on startup.
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1` - e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
### Screen size ### Screen size

View File

@ -25,7 +25,7 @@ nat1to1: <ip>
- Control protection means, users can gain control only if at least one admin is in the room. - Control protection means, users can gain control only if at least one admin is in the room.
- e.g. `false` - e.g. `false`
#### `NEKO_IMPLICIT_CONTROL`: #### `NEKO_IMPLICIT_CONTROL`:
- If enabled members can gain control implicitly, they don't need to request control. - If enabled members can gain control implicitly, they don't needd to request control.
- e.g. `false` - e.g. `false`
#### `NEKO_LOCKS`: #### `NEKO_LOCKS`:
- Resources, that will be locked when starting, separated by whitespace. - Resources, that will be locked when starting, separated by whitespace.
@ -79,14 +79,13 @@ nat1to1: <ip>
- `gstreamer1.0-plugins-good` - `gstreamer1.0-plugins-good`
- `gstreamer1.0-plugins-bad` - `gstreamer1.0-plugins-bad`
- `gstreamer1.0-plugins-ugly` - `gstreamer1.0-plugins-ugly`
- e.g. `ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline` - e.g. `ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream`
#### `NEKO_MAX_FPS`: #### `NEKO_MAX_FPS`:
- The resulting stream frames per seconds should be capped *(0 for uncapped)*. - The resulting stream frames per seconds should be capped *(0 for uncapped)*.
- e.g. `0` - e.g. `0`
#### `NEKO_HWENC`: #### `NEKO_HWENC`:
- none *(default CPU encoding)* - Use hardware accelerated encoding, for now supported only `VAAPI`.
- vaapi - e.g. `VAAPI`
- nvenc
### Audio ### Audio
@ -107,11 +106,9 @@ nat1to1: <ip>
#### `NEKO_BROADCAST_PIPELINE`: #### `NEKO_BROADCAST_PIPELINE`:
- Makes it possible to create custom gstreamer pipeline used for broadcasting, strings `{url}`, `{device}` and `{display}` will be replaced. - Makes it possible to create custom gstreamer pipeline used for broadcasting, strings `{url}`, `{device}` and `{display}` will be replaced.
#### `NEKO_BROADCAST_URL`: #### `NEKO_BROADCAST_URL`:
- Set a default URL for broadcast streams. It can be disabled/changed later by admins in the GUI. - Set a default URL for broadcast streams. Setting this value will automatically enable broadcasting when n.eko starts. It can be disabled/changed later by admins in the GUI.
- e.g. `rtmp://<your-server>:1935/ingest/<stream-key>` - e.g. `rtmp://<your-server>:1935/ingest/<stream-key>`
#### `NEKO_BROADCAST_AUTOSTART`:
- Automatically start broadcasting when neko starts and broadcast_url is set.
- e.g. `true`
### Server ### Server
#### `NEKO_BIND`: #### `NEKO_BIND`:
@ -139,7 +136,7 @@ nat1to1: <ip>
- Enable file transfer feature. - Enable file transfer feature.
- e.g. `true` - e.g. `true`
#### `NEKO_FILE_TRANSFER_PATH`: #### `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. `/home/neko/Downloads`. If the path doesn't exist, it will be created.
- e.g. `/home/neko/Desktop` - e.g. `/home/neko/Desktop`
@ -170,7 +167,7 @@ Flags:
--cert string path to the SSL cert used to secure the neko server --cert string path to the SSL cert used to secure the neko server
--control_protection control protection means, users can gain control only if at least one admin is in the room --control_protection control protection means, users can gain control only if at least one admin is in the room
--cors strings list of allowed origins for CORS (default [*]) --cors strings list of allowed origins for CORS (default [*])
--device string audio device to capture (default "audio_output.monitor") --device string audio device to capture (default "auto_null.monitor")
--display string XDisplay to capture (default ":99.0") --display string XDisplay to capture (default ":99.0")
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100") --epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
--file_transfer_enabled enable file transfer feature (default false) --file_transfer_enabled enable file transfer feature (default false)

View File

@ -72,8 +72,7 @@ services:
version: "3.4" version: "3.4"
services: services:
neko: neko:
# see docs for more variants image: "m1k1o/neko:arm-chromium"
image: "ghcr.io/m1k1o/neko/arm-chromium:latest"
restart: "unless-stopped" restart: "unless-stopped"
# increase on rpi's with more then 1gb ram. # increase on rpi's with more then 1gb ram.
shm_size: "520mb" shm_size: "520mb"
@ -96,9 +95,9 @@ services:
! videoconvert ! videoconvert
! queue ! queue
! video/x-raw,framerate=30/1,format=NV12 ! video/x-raw,framerate=30/1,format=NV12
! v4l2h264enc extra-controls="controls,h264_profile=1,video_bitrate=1250000;" ! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;"
! h264parse config-interval=3 ! h264parse config-interval=3
! video/x-h264,stream-format=byte-stream,profile=constrained-baseline ! video/x-h264,profile=baseline,stream-format=byte-stream
NEKO_VIDEO_CODEC: h264 NEKO_VIDEO_CODEC: h264
``` ```

View File

@ -1,57 +0,0 @@
# Frequently Asked Questions
## How to enable debug mode?
To see verbose information from n.eko server, you can enable debug mode using `NEKO_DEBUG`.
```diff
version: "3.4"
services:
neko:
image: "m1k1o/neko:firefox"
restart: "unless-stopped"
shm_size: "2gb"
ports:
- "8080:8080"
- "52000-52100:52000-52100/udp"
environment:
NEKO_SCREEN: 1920x1080@30
NEKO_PASSWORD: neko
NEKO_PASSWORD_ADMIN: admin
NEKO_EPR: 52000-52100
NEKO_ICELITE: 1
+ NEKO_DEBUG: 1
```
Ensure, that you have enabled debug mode in javascript console too, in order to see verbose information from client.
## Chinese input method is not working
There exists an extension for Chrome that allows you to use Chinese input method. You can install it from [here](https://chrome.google.com/webstore/detail/mclkkofklkfljcocdinagocijmpgbhab). Alternatively, you can use Google Input Tools from [here](https://www.google.com/inputtools/chrome/).
## Only black screen is displayed but remote cursor is moving for Chromium-based browsers (Chrome, Edge, etc.)
Check if you did not forget to add cap_add to your docker-compose file.
```yaml
cap_add:
- SYS_ADMIN
```
## How can I embed the Neko desktop into web page without login prompt coming up for viewers?
You can use the following URL to embed the Neko desktop into a web page without login prompt coming up for viewers:
```
http://<your-neko-server-ip>:8080/?usr=neko&pwd=neko
```
https://stackoverflow.com/questions/15276929/how-to-make-a-video-fullscreen-when-it-is-placed-inside-an-iframe
Your iframe needs an attribute: `allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"` or more modern `allow="fullscreen *"`. For the second you can remove the star if your iframe has the same origin or replace it with your iframe origin.
## Can I use neko without docker?
It is strongly recommended to use Neko with Docker, as it is the easiest way to run it. But you should be able to install Neko "natively" on your host system. Neko is based on Debian and uses Xorg and Pulseaudio. You would just need to follow steps that are in Dockerfile, install all dependencies on your host system and then just run it.
However, it is recommend to start with existing system that has GUI with desktop manager, is based on Xorg and uses Pulseaudio (e.g. Ubuntu Desktop 22.04). For that matter you only need to install gstreamer dependencies, configure pulseaudio properly and run neko binary (you don't need to build it from scratch, you can copy it from docker image).

View File

@ -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. 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. 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` 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. 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. 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 ```xml
<VirtualHost *:80> <VirtualHost *:80>

View File

@ -1,21 +1,18 @@
# Troubleshooting # 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 ## Test your client
Some browsers may block WebRTC access by default. You can check if it is enabled by going to `about:webrtc` or `chrome://webrtc-internals` in your browser. Some browser may block WebRTC access by default. You can check if it is enabled by going to `about:webrtc` or `chrome://webrtc-internals` in your browser.
Check if your extensions are not blocking WebRTC access. Following extensions are known to block or does not work properly with WebRTC: Check if your extensions are not blocking WebRTC access. For example, Privacy Badger or Private Internet Access blocks WebRTC by default.
- Privacy Badger
- Private Internet Access
- PIA VPN (even if disabled)
Test whether your client [supports](https://www.webrtc-experiment.com/DetectRTC/) and can [connect to WebRTC](https://www.webcasts.com/webrtc/). Test whether your client [supports](https://www.webrtc-experiment.com/DetectRTC/) and can [connect to WebRTC](https://www.webcasts.com/webrtc/).
## Networking ## Networking
If you are absolutely sure, that your client is working correctly, then most likely your networking is not set up correctly. Most problems are networking related.
### Check if your ports are correctly exposed using docker ### Check if your ports are correctly exposed using docker
@ -45,7 +42,7 @@ services:
Ensure, that your ports are reachable through your external IP. Ensure, that your ports are reachable through your external IP.
To validate UDP connection the simplest way, run this on your server: To validate UDP connection the simpliest way, run this on your server:
```shell ```shell
nc -ul 52101 nc -ul 52101
@ -62,30 +59,21 @@ Then try to type on one end, you should see characters on the other side.
If it does not work for you, then most likely your port forwarding is not working correctly. Or your ISP is blocking traffic. If it does not work for you, then most likely your port forwarding is not working correctly. Or your ISP is blocking traffic.
If you get [`Command 'nc' not found.`](https://command-not-found.com/nc) error, you can install `netcat` package using:
```shell
sudo apt-get install netcat
```
### Check if your external IP was determined correctly ### 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 connect to. One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to conenct to.
```shell ```shell
docker-compose logs neko | grep nat_ips docker-compose logs neko | grep nat_ips
``` ```
Note: Some newer versions of docker-compose use `docker compose` instead of `docker-compose`.
You should see this: 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> 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 resolver using `NEKO_IPFETCH`. It needs to return IP address that will be used. 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.
```diff ```diff
version: "3.4" version: "3.4"
@ -106,7 +94,7 @@ services:
+ NEKO_IPFETCH: https://ifconfig.co/ip + NEKO_IPFETCH: https://ifconfig.co/ip
``` ```
Or you can specify your IP address manually using `NEKO_NAT1TO1`: (It's read as NAT 1 to 1, so it's capital letter 'O', not zero '0', in NAT1`TO`1) Or you can specify your IP address manually using `NEKO_NAT1TO1`:
```diff ```diff
version: "3.4" version: "3.4"
@ -131,17 +119,16 @@ If you want to use n.eko only locally, you must put here your local IP address,
### Neko works externally, but not locally ### Neko works externally, but not locally
You are probably missing NAT Loopback (NAT Hairpinning) setting on your router. You are probabbly missing NAT Loopback (NAT Hairpinning) setting on your router.
Example for pfsense with truecharts docker container: Example for pfsense with truecharts docker container:
- First, port forward the relevant ports 8080 and 52000-52100/udp for the 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). - Then turn on `Pure NAT` pfsense (under system > advanced > firewall and nat).
- Make sure to check the two boxes so it works. - Make sure to check the two boxes so it works.
- Make sure `NEKO_NAT1TO1` is blank and `NEKO_IPFETCH` address is working correctly (if unset default value is chosen). - Make sure `NEKO_NAT1TO1` is blank and `NEKO_IPFETCH` address is working correclty (if unset default value is chosen).
- Test externally to confirm it works. - Test externally to confirm it works.
- Internally you have to access it using `<your-public-ip>:port` - Internally you have to access it using `<your-public-ip>:port`
If your router does not support NAT Loopback (NAT Hairpinning), you can use turn servers to overcome this issue. See [more details here](https://neko.m1k1o.net/#/getting-started/?id=networking) on how to setup local coturn instance.
### Neko works locally, but not externally ### Neko works locally, but not externally
@ -214,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. Getting black screen with a cursor, but no browser.
``` ```
Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based browsers. Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based brwosers.
### Unrelated server errors ### Unrelated server errors
@ -223,17 +210,3 @@ Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based bro
``` ```
This error originates from browser, that it could not connect to dbus. This does not affect us and can be ignored. This error originates from browser, that it could not connect to dbus. This does not affect us and can be ignored.
### Broadcast pipeline not working with some ingest servers
See [related issue](https://github.com/m1k1o/neko/issues/276).
```
Could not connect to RTMP stream "'rtmp://<ingest-url>/live/<stream-key-removed> live=1'" for writing
```
Some ingest servers require `live=1` parameter in the URL (e.g. nginx-rtmp-module). Some do not and do not accept aphostrophes (e.g. owncast). You can try to change the pipeline to:
```yaml
NEKO_BROADCAST_PIPELINE: "flvmux name=mux ! rtmpsink location={url} pulsesrc device={device} ! audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. ximagesrc display-name={display} show-pointer=false use-damage=false ! video/x-raw,framerate=28/1 ! videoconvert ! queue ! x264enc bframes=0 key-int-max=0 byte-stream=true tune=zerolatency speed-preset=veryfast ! mux."
```

View File

@ -1,35 +1,10 @@
#!/bin/bash #!/bin/bash
# set -ex
# aborting if any command returns a non-zero value
set -e
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'` BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
GIT_COMMIT=`git rev-parse --short HEAD`
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
# go build -o bin/neko -ldflags "-s -X 'm1k1o/neko.buildDate=${BUILD_TIME}' -X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}' -X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'" -i cmd/neko/main.go
# set git build variables if git exists
if git status > /dev/null 2>&1 && [ -z $GIT_COMMIT ] && [ -z $GIT_BRANCH ] && [ -z $GIT_TAG ];
then
GIT_COMMIT=`git rev-parse --short HEAD`
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
GIT_TAG=`git tag --points-at $GIT_COMMIT | head -n 1`
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
GIT_COMMIT="${GIT_DIRTY}${GIT_COMMIT}"
fi
#
# load dependencies
go get -v -t -d .
#
# build server
go build \
-o bin/neko \
-ldflags "
-s -w
-X 'm1k1o/neko.buildDate=${BUILD_TIME}'
-X 'm1k1o/neko.gitCommit=${GIT_COMMIT}'
-X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'
-X 'm1k1o/neko.gitTag=${GIT_TAG}'
" \
cmd/neko/main.go;

View File

@ -1,55 +1,58 @@
module m1k1o/neko module m1k1o/neko
go 1.20 go 1.18
require ( require (
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/pion/ice/v2 v2.3.0 github.com/kataras/go-events v0.0.3
github.com/pion/ice/v2 v2.2.11 // indirect
github.com/pion/interceptor v0.1.12 github.com/pion/interceptor v0.1.12
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2
github.com/pion/rtp v1.7.13 // indirect github.com/pion/rtp v1.7.13 // indirect
github.com/pion/srtp/v2 v2.0.12 // indirect github.com/pion/srtp/v2 v2.0.10 // indirect
github.com/pion/webrtc/v3 v3.1.55 github.com/pion/webrtc/v3 v3.1.47
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.29.0 github.com/rs/zerolog v1.28.0
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.0
github.com/spf13/viper v1.15.0 github.com/spf13/viper v1.13.0
golang.org/x/crypto v0.6.0 // indirect golang.org/x/crypto v0.1.0 // indirect
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.4.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )
require ( require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pion/datachannel v1.5.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pion/dtls/v2 v2.2.6 // indirect github.com/pion/datachannel v1.5.2 // indirect
github.com/pion/mdns v0.0.7 // indirect github.com/pion/dtls/v2 v2.1.5 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/randutil v0.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.10 // indirect github.com/pion/rtcp v1.2.10 // indirect
github.com/pion/sctp v1.8.6 // indirect github.com/pion/sctp v1.8.3 // indirect
github.com/pion/sdp/v3 v3.0.6 // indirect github.com/pion/sdp/v3 v3.0.6 // indirect
github.com/pion/stun v0.4.0 // indirect github.com/pion/stun v0.3.5 // indirect
github.com/pion/transport/v2 v2.0.2 // indirect github.com/pion/transport v0.13.1 // indirect
github.com/pion/turn/v2 v2.1.0 // indirect github.com/pion/turn/v2 v2.0.8 // indirect
github.com/pion/udp/v2 v2.0.1 // indirect github.com/pion/udp v0.1.1 // indirect
github.com/spf13/afero v1.9.4 // indirect github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/subosito/gotenv v1.4.1 // indirect
gopkg.in/ini.v1 v1.67.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -63,8 +63,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -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.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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 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.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= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -141,11 +141,12 @@ 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/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-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/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.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.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/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/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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -156,15 +157,14 @@ 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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.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.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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@ -176,49 +176,51 @@ 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.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.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw= github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4= github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY= github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
github.com/pion/ice/v2 v2.3.0 h1:G+ysriabk1p9wbySDpdsnlD+6ZspLlDLagRduRfzJPk= github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
github.com/pion/ice/v2 v2.3.0/go.mod h1:+xO/cXVnnVUr6D2ZJcCT5g9LngucUkkTvfnTMqUxKRM= 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/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8= github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA= 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 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U= github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8= github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= 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/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 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= 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 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sctp v1.8.6 h1:CUex11Vkt9YS++VhLf8b55O3VqKrWL6W3SDwX4jAqsI= github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sctp v1.8.6/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= 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/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= 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/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp/v2 v2.0.12 h1:WrmiVCubGMOAObBU1vwWjG0H3VSyQHawKeer2PVA5rY= github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y= github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk= github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw= github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
github.com/pion/transport/v2 v2.0.1/go.mod h1:93OYg91+mrGxKW+Jrgzmqr80kgXqD7J0yybOrdr7w0Y= github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg= github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0= github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI= github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs= github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us= github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54= github.com/pion/webrtc/v3 v3.1.47 h1:2dFEKRI1rzFvehXDq43hK9OGGyTGJSusUi3j6QKHC5s=
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8= github.com/pion/webrtc/v3 v3.1.47/go.mod h1:8U39MYZCLVV4sIBn01htASVNkWQN2zDa/rx5xisEXWs=
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/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -230,40 +232,38 @@ 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 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.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.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.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/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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-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-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-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-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -314,7 +314,6 @@ 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.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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -345,16 +344,20 @@ 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-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-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-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-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-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-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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -374,7 +377,6 @@ 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-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-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-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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -417,20 +419,18 @@ 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-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-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-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-20220520151302-bc2c85ada10a/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-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-20220811171246-fbc7d0a398ab/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-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.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-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.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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -439,10 +439,8 @@ 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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/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.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.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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -494,7 +492,6 @@ 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-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -602,6 +599,7 @@ 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.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.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.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.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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -22,7 +22,7 @@ type BroacastManagerCtx struct {
started bool started bool
} }
func broadcastNew(pipelineFn func(url string) (string, error), url string, started bool) *BroacastManagerCtx { func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string) *BroacastManagerCtx {
logger := log.With(). logger := log.With().
Str("module", "capture"). Str("module", "capture").
Str("submodule", "broadcast"). Str("submodule", "broadcast").
@ -31,8 +31,8 @@ func broadcastNew(pipelineFn func(url string) (string, error), url string, start
return &BroacastManagerCtx{ return &BroacastManagerCtx{
logger: logger, logger: logger,
pipelineFn: pipelineFn, pipelineFn: pipelineFn,
url: url, url: defaultUrl,
started: started && url != "", started: defaultUrl != "",
} }
} }

View File

@ -3,8 +3,8 @@
static void gstreamer_pipeline_log(GstPipelineCtx *ctx, char* level, const char* format, ...) { static void gstreamer_pipeline_log(GstPipelineCtx *ctx, char* level, const char* format, ...) {
va_list argptr; va_list argptr;
va_start(argptr, format); va_start(argptr, format);
char buffer[4096]; char buffer[100];
vsnprintf(buffer, sizeof(buffer), format, argptr); vsprintf(buffer, format, argptr);
va_end(argptr); va_end(argptr);
goPipelineLog(level, buffer, ctx->pipelineId); goPipelineLog(level, buffer, ctx->pipelineId);
} }

View File

@ -31,29 +31,12 @@ var pSerial int32
var pipelines = make(map[int]*Pipeline) var pipelines = make(map[int]*Pipeline)
var pipelinesLock sync.Mutex var pipelinesLock sync.Mutex
var registry *C.GstRegistry var registry *C.GstRegistry
var gMainLoop *C.GMainLoop
func init() { func init() {
C.gst_init(nil, nil) C.gst_init(nil, nil)
registry = C.gst_registry_get() registry = C.gst_registry_get()
} }
func RunMainLoop() {
if gMainLoop != nil {
return
}
gMainLoop = C.g_main_loop_new(nil, C.int(0))
C.g_main_loop_run(gMainLoop)
}
func QuitMainLoop() {
if gMainLoop == nil {
return
}
C.g_main_loop_quit(gMainLoop)
gMainLoop = nil
}
func CreatePipeline(pipelineStr string) (*Pipeline, error) { func CreatePipeline(pipelineStr string) (*Pipeline, error) {
id := atomic.AddInt32(&pSerial, 1) id := atomic.AddInt32(&pSerial, 1)
@ -68,7 +51,6 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
if gstError != nil { if gstError != nil {
defer C.g_error_free(gstError) 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)) return nil, fmt.Errorf("(pipeline error) %s", C.GoString(gstError.message))
} }
@ -80,18 +62,17 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
Int("pipeline_id", int(id)).Logger(), Int("pipeline_id", int(id)).Logger(),
Src: pipelineStr, Src: pipelineStr,
Ctx: ctx, Ctx: ctx,
Sample: make(chan types.Sample),
} }
pipelines[p.id] = p pipelines[p.id] = p
return p, nil return p, nil
} }
func (p *Pipeline) AttachAppsink(sinkName string, sampleChannel chan types.Sample) { func (p *Pipeline) AttachAppsink(sinkName string) {
sinkNameUnsafe := C.CString(sinkName) sinkNameUnsafe := C.CString(sinkName)
defer C.free(unsafe.Pointer(sinkNameUnsafe)) defer C.free(unsafe.Pointer(sinkNameUnsafe))
p.Sample = sampleChannel
C.gstreamer_pipeline_attach_appsink(p.Ctx, sinkNameUnsafe) C.gstreamer_pipeline_attach_appsink(p.Ctx, sinkNameUnsafe)
} }
@ -117,6 +98,7 @@ func (p *Pipeline) Destroy() {
delete(pipelines, p.id) delete(pipelines, p.id)
pipelinesLock.Unlock() pipelinesLock.Unlock()
close(p.Sample)
C.free(unsafe.Pointer(p.Ctx)) C.free(unsafe.Pointer(p.Ctx))
p = nil p = nil
} }
@ -195,7 +177,6 @@ func goHandlePipelineBuffer(buffer unsafe.Pointer, bufferLen C.int, duration C.i
if ok { if ok {
pipeline.Sample <- types.Sample{ pipeline.Sample <- types.Sample{
Data: C.GoBytes(buffer, bufferLen), Data: C.GoBytes(buffer, bufferLen),
Timestamp: time.Now(),
Duration: time.Duration(duration), Duration: time.Duration(duration),
} }
} else { } else {

View File

@ -6,7 +6,6 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"m1k1o/neko/internal/capture/gst"
"m1k1o/neko/internal/config" "m1k1o/neko/internal/config"
"m1k1o/neko/internal/types" "m1k1o/neko/internal/types"
) )
@ -31,18 +30,12 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
// sinks // sinks
broadcast: broadcastNew(func(url string) (string, error) { broadcast: broadcastNew(func(url string) (string, error) {
return NewBroadcastPipeline(config.AudioDevice, config.Display, config.BroadcastPipeline, url) return NewBroadcastPipeline(config.AudioDevice, config.Display, config.BroadcastPipeline, url)
}, config.BroadcastUrl, config.BroadcastAutostart), }, config.BroadcastUrl),
audio: streamSinkNew(config.AudioCodec, func() (string, error) { audio: streamSinkNew(config.AudioCodec, func() (string, error) {
return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate) return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate)
}, "audio"), }, "audio"),
video: streamSinkNew(config.VideoCodec, func() (string, error) { video: streamSinkNew(config.VideoCodec, func() (string, error) {
// use screen fps as default return NewVideoPipeline(config.VideoCodec, config.Display, config.VideoPipeline, config.VideoMaxFPS, config.VideoBitrate, config.VideoHWEnc)
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"), }, "video"),
} }
} }
@ -54,18 +47,7 @@ func (manager *CaptureManagerCtx) Start() {
} }
} }
go gst.RunMainLoop() manager.desktop.OnBeforeScreenSizeChange(func() {
go func() {
for {
before, ok := <-manager.desktop.GetScreenSizeChangeChannel()
if !ok {
manager.logger.Info().Msg("screen size change channel was closed")
return
}
if before {
// before screen size change, we need to destroy all pipelines
if manager.video.Started() { if manager.video.Started() {
manager.video.destroyPipeline() manager.video.destroyPipeline()
} }
@ -73,9 +55,9 @@ func (manager *CaptureManagerCtx) Start() {
if manager.broadcast.Started() { if manager.broadcast.Started() {
manager.broadcast.destroyPipeline() manager.broadcast.destroyPipeline()
} }
} else { })
// after screen size change, we need to recreate all pipelines
manager.desktop.OnAfterScreenSizeChange(func() {
if manager.video.Started() { if manager.video.Started() {
err := manager.video.createPipeline() err := manager.video.createPipeline()
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) { if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
@ -89,9 +71,7 @@ func (manager *CaptureManagerCtx) Start() {
manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline") manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline")
} }
} }
} })
}
}()
} }
func (manager *CaptureManagerCtx) Shutdown() error { func (manager *CaptureManagerCtx) Shutdown() error {
@ -102,8 +82,6 @@ func (manager *CaptureManagerCtx) Shutdown() error {
manager.audio.shutdown() manager.audio.shutdown()
manager.video.shutdown() manager.video.shutdown()
gst.QuitMainLoop()
return nil return nil
} }

View File

@ -5,7 +5,6 @@ import (
"strings" "strings"
"m1k1o/neko/internal/capture/gst" "m1k1o/neko/internal/capture/gst"
"m1k1o/neko/internal/config"
"m1k1o/neko/internal/types/codec" "m1k1o/neko/internal/types/codec"
) )
@ -53,8 +52,8 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url
return pipelineStr, nil return pipelineStr, nil
} }
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc config.HwEnc) (string, error) { func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc string) (string, error) {
pipelineStr := " ! appsink name=appsinkvideo" pipelineStr := " ! appsink name=appsink"
// if using custom pipeline // if using custom pipeline
if pipelineSrc != "" { if pipelineSrc != "" {
@ -62,14 +61,9 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
return pipelineStr, nil return pipelineStr, nil
} }
// use default fps if not set
if fps == 0 {
fps = 25
}
switch rtpCodec.Name { switch rtpCodec.Name {
case codec.VP8().Name: case codec.VP8().Name:
if hwenc == config.HwEncVAAPI { if hwenc == "VAAPI" {
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil { if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
return "", err return "", err
} }
@ -112,30 +106,31 @@ 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) 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: case codec.H264().Name:
// https://gstreamer.freedesktop.org/documentation/aom/av1enc.html?gi-language=c if err := gst.CheckPlugins([]string{"ximagesrc"}); err != nil {
// gstreamer1.0-plugins-bad
// av1enc usage-profile=1
// TODO: check for plugin.
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return "", err return "", err
} }
pipelineStr = strings.Join([]string{ if hwenc == "VAAPI" {
fmt.Sprintf(videoSrc, display, fps), if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
"av1enc", return "", err
fmt.Sprintf("target-bitrate=%d", bitrate*650), }
"cpu-used=4",
"end-usage=cbr", pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate)
// "usage-profile=realtime",
"undershoot=95", } else {
"keyframe-max-dist=25", // https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
"min-quantizer=4", // gstreamer1.0-plugins-bad
"max-quantizer=20", // openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
pipelineStr, if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
}, " ") pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
case codec.H264().Name: break
if err := gst.CheckPlugins([]string{"ximagesrc"}); err != nil { }
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
// gstreamer1.0-plugins-ugly
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
return "", err return "", err
} }
@ -144,35 +139,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
vbvbuf = bitrate vbvbuf = bitrate
} }
if hwenc == config.HwEncVAAPI { pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate, vbvbuf)
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate)
} else if hwenc == config.HwEncNVENC {
if err := gst.CheckPlugins([]string{"nvcodec"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! nvh264enc name=encoder preset=2 gop-size=25 spatial-aq=true temporal-aq=true bitrate=%d vbv-buffer-size=%d rc-mode=6 ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
} else {
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
// gstreamer1.0-plugins-bad
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
break
}
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
// gstreamer1.0-plugins-ugly
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
} }
default: default:
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name) return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
@ -182,7 +149,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
} }
func NewAudioPipeline(rtpCodec codec.RTPCodec, device string, pipelineSrc string, bitrate uint) (string, error) { func NewAudioPipeline(rtpCodec codec.RTPCodec, device string, pipelineSrc string, bitrate uint) (string, error) {
pipelineStr := " ! appsink name=appsinkaudio" pipelineStr := " ! appsink name=appsink"
// if using custom pipeline // if using custom pipeline
if pipelineSrc != "" { if pipelineSrc != "" {

View File

@ -15,7 +15,7 @@ import (
type StreamSinkManagerCtx struct { type StreamSinkManagerCtx struct {
logger zerolog.Logger logger zerolog.Logger
mu sync.Mutex mu sync.Mutex
sampleChannel chan types.Sample wg sync.WaitGroup
codec codec.RTPCodec codec codec.RTPCodec
pipeline *gst.Pipeline pipeline *gst.Pipeline
@ -24,6 +24,8 @@ type StreamSinkManagerCtx struct {
listeners int listeners int
listenersMu sync.Mutex listenersMu sync.Mutex
sampleFn func(sample types.Sample)
} }
func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), video_id string) *StreamSinkManagerCtx { func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), video_id string) *StreamSinkManagerCtx {
@ -36,7 +38,6 @@ func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), vide
logger: logger, logger: logger,
codec: codec, codec: codec,
pipelineFn: pipelineFn, pipelineFn: pipelineFn,
sampleChannel: make(chan types.Sample),
} }
return manager return manager
@ -46,6 +47,11 @@ func (manager *StreamSinkManagerCtx) shutdown() {
manager.logger.Info().Msgf("shutdown") manager.logger.Info().Msgf("shutdown")
manager.destroyPipeline() manager.destroyPipeline()
manager.wg.Wait()
}
func (manager *StreamSinkManagerCtx) OnSample(listener func(sample types.Sample)) {
manager.sampleFn = listener
} }
func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec { func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec {
@ -146,13 +152,26 @@ func (manager *StreamSinkManagerCtx) createPipeline() error {
return err return err
} }
appsinkSubfix := "audio" manager.pipeline.AttachAppsink("appsink")
if manager.codec.IsVideo() { manager.pipeline.Play()
appsinkSubfix = "video"
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.pipeline.AttachAppsink("appsink"+appsinkSubfix, manager.sampleChannel) manager.sampleFn(sample)
manager.pipeline.Play() }
}()
return nil return nil
} }
@ -169,7 +188,3 @@ func (manager *StreamSinkManagerCtx) destroyPipeline() {
manager.logger.Info().Msgf("destroying pipeline") manager.logger.Info().Msgf("destroying pipeline")
manager.pipeline = nil manager.pipeline = nil
} }
func (manager *StreamSinkManagerCtx) GetSampleChannel() chan types.Sample {
return manager.sampleChannel
}

View File

@ -2,7 +2,6 @@ package config
import ( import (
"m1k1o/neko/internal/types/codec" "m1k1o/neko/internal/types/codec"
"strings"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -10,19 +9,11 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type HwEnc int
const (
HwEncNone HwEnc = iota
HwEncVAAPI
HwEncNVENC
)
type Capture struct { type Capture struct {
// video // video
Display string Display string
VideoCodec codec.RTPCodec VideoCodec codec.RTPCodec
VideoHWEnc HwEnc // TODO: Pipeline builder. VideoHWEnc string // TODO: Pipeline builder.
VideoBitrate uint // TODO: Pipeline builder. VideoBitrate uint // TODO: Pipeline builder.
VideoMaxFPS int16 // TODO: Pipeline builder. VideoMaxFPS int16 // TODO: Pipeline builder.
VideoPipeline string VideoPipeline string
@ -36,7 +27,6 @@ type Capture struct {
// broadcast // broadcast
BroadcastPipeline string BroadcastPipeline string
BroadcastUrl string BroadcastUrl string
BroadcastAutostart bool
} }
func (Capture) Init(cmd *cobra.Command) error { func (Capture) Init(cmd *cobra.Command) error {
@ -66,12 +56,6 @@ func (Capture) Init(cmd *cobra.Command) error {
return err 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 // DEPRECATED: video codec
cmd.PersistentFlags().Bool("h264", false, "DEPRECATED: use video_codec") cmd.PersistentFlags().Bool("h264", false, "DEPRECATED: use video_codec")
if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil { if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil {
@ -102,7 +86,7 @@ func (Capture) Init(cmd *cobra.Command) error {
// audio // audio
// //
cmd.PersistentFlags().String("device", "audio_output.monitor", "audio device to capture") cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture")
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil { if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
return err return err
} }
@ -156,16 +140,11 @@ func (Capture) Init(cmd *cobra.Command) error {
return err return err
} }
cmd.PersistentFlags().String("broadcast_url", "", "a default default URL for broadcast streams, can be disabled/changed later by admins in the GUI") cmd.PersistentFlags().String("broadcast_url", "", "URL for broadcasting, setting this value will automatically enable broadcasting")
if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil { if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil {
return err return err
} }
cmd.PersistentFlags().Bool("broadcast_autostart", true, "automatically start broadcasting when neko starts and broadcast_url is set")
if err := viper.BindPFlag("broadcast_autostart", cmd.PersistentFlags().Lookup("broadcast_autostart")); err != nil {
return err
}
return nil return nil
} }
@ -194,24 +173,13 @@ func (s *Capture) Set() {
} else if viper.GetBool("h264") { } else if viper.GetBool("h264") {
s.VideoCodec = codec.H264() s.VideoCodec = codec.H264()
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_VIDEO_CODEC=h264' instead") 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 := strings.ToLower(viper.GetString("hwenc")) videoHWEnc := ""
switch videoHWEnc { if viper.GetString("hwenc") == "VAAPI" {
case "": videoHWEnc = "VAAPI"
fallthrough
case "none":
s.VideoHWEnc = HwEncNone
case "vaapi":
s.VideoHWEnc = HwEncVAAPI
case "nvenc":
s.VideoHWEnc = HwEncNVENC
default:
log.Warn().Str("hwenc", videoHWEnc).Msgf("unknown video hw encoder, using CPU")
} }
s.VideoHWEnc = videoHWEnc
s.VideoBitrate = viper.GetUint("video_bitrate") s.VideoBitrate = viper.GetUint("video_bitrate")
s.VideoMaxFPS = int16(viper.GetInt("max_fps")) s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
@ -253,5 +221,4 @@ func (s *Capture) Set() {
s.BroadcastPipeline = viper.GetString("broadcast_pipeline") s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
s.BroadcastUrl = viper.GetString("broadcast_url") s.BroadcastUrl = viper.GetString("broadcast_url")
s.BroadcastAutostart = viper.GetBool("broadcast_autostart")
} }

View File

@ -14,7 +14,6 @@ type Server struct {
Cert string Cert string
Key string Key string
Bind string Bind string
Proxy bool
Static string Static string
PathPrefix string PathPrefix string
CORS []string CORS []string
@ -36,11 +35,6 @@ func (Server) Init(cmd *cobra.Command) error {
return err return err
} }
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
return err
}
cmd.PersistentFlags().String("static", "./www", "path to neko client files to serve") cmd.PersistentFlags().String("static", "./www", "path to neko client files to serve")
if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil { if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil {
return err return err
@ -63,7 +57,6 @@ func (s *Server) Set() {
s.Cert = viper.GetString("cert") s.Cert = viper.GetString("cert")
s.Key = viper.GetString("key") s.Key = viper.GetString("key")
s.Bind = viper.GetString("bind") s.Bind = viper.GetString("bind")
s.Proxy = viper.GetBool("proxy")
s.Static = viper.GetString("static") s.Static = viper.GetString("static")
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix"))) s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))

View File

@ -10,6 +10,7 @@ import (
type WebSocket struct { type WebSocket struct {
Password string Password string
AdminPassword string AdminPassword string
Proxy bool
Locks []string Locks []string
ControlProtection bool ControlProtection bool
@ -29,6 +30,11 @@ func (WebSocket) Init(cmd *cobra.Command) error {
return err return err
} }
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
return err
}
cmd.PersistentFlags().StringSlice("locks", []string{}, "resources, that will be locked when starting (control, login)") cmd.PersistentFlags().StringSlice("locks", []string{}, "resources, that will be locked when starting (control, login)")
if err := viper.BindPFlag("locks", cmd.PersistentFlags().Lookup("locks")); err != nil { if err := viper.BindPFlag("locks", cmd.PersistentFlags().Lookup("locks")); err != nil {
return err return err
@ -57,6 +63,7 @@ func (WebSocket) Init(cmd *cobra.Command) error {
func (s *WebSocket) Set() { func (s *WebSocket) Set() {
s.Password = viper.GetString("password") s.Password = viper.GetString("password")
s.AdminPassword = viper.GetString("password_admin") s.AdminPassword = viper.GetString("password_admin")
s.Proxy = viper.GetBool("proxy")
s.Locks = viper.GetStringSlice("locks") s.Locks = viper.GetStringSlice("locks")
s.ControlProtection = viper.GetBool("control_protection") s.ControlProtection = viper.GetBool("control_protection")

View File

@ -9,6 +9,7 @@ import (
"m1k1o/neko/internal/desktop/xevent" "m1k1o/neko/internal/desktop/xevent"
"m1k1o/neko/internal/desktop/xorg" "m1k1o/neko/internal/desktop/xorg"
"github.com/kataras/go-events"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -19,18 +20,16 @@ type DesktopManagerCtx struct {
logger zerolog.Logger logger zerolog.Logger
wg sync.WaitGroup wg sync.WaitGroup
shutdown chan struct{} shutdown chan struct{}
emmiter events.EventEmmiter
config *config.Desktop config *config.Desktop
screenSizeChangeChannel chan bool
} }
func New(config *config.Desktop) *DesktopManagerCtx { func New(config *config.Desktop) *DesktopManagerCtx {
return &DesktopManagerCtx{ return &DesktopManagerCtx{
logger: log.With().Str("module", "desktop").Logger(), logger: log.With().Str("module", "desktop").Logger(),
shutdown: make(chan struct{}), shutdown: make(chan struct{}),
emmiter: events.New(),
config: config, config: config,
screenSizeChangeChannel: make(chan bool),
} }
} }
@ -48,22 +47,14 @@ func (manager *DesktopManagerCtx) Start() {
go xevent.EventLoop(manager.config.Display) go xevent.EventLoop(manager.config.Display)
go func() { manager.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
for {
msg, ok := <-xevent.EventErrorChannel
if !ok {
manager.logger.Info().Msg("xevent error channel was closed")
return
}
manager.logger.Warn(). manager.logger.Warn().
Uint8("error_code", msg.Error_code). Uint8("error_code", error_code).
Str("message", msg.Message). Str("message", message).
Uint8("request_code", msg.Request_code). Uint8("request_code", request_code).
Uint8("minor_code", msg.Minor_code). Uint8("minor_code", minor_code).
Msg("X event error occurred") Msg("X event error occurred")
} })
}()
manager.wg.Add(1) manager.wg.Add(1)
@ -84,15 +75,22 @@ func (manager *DesktopManagerCtx) Start() {
}() }()
} }
func (manager *DesktopManagerCtx) GetScreenSizeChangeChannel() chan bool { func (manager *DesktopManagerCtx) OnBeforeScreenSizeChange(listener func()) {
return manager.screenSizeChangeChannel 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) Shutdown() error { func (manager *DesktopManagerCtx) Shutdown() error {
manager.logger.Info().Msgf("desktop shutting down") manager.logger.Info().Msgf("desktop shutting down")
close(manager.shutdown) close(manager.shutdown)
close(manager.screenSizeChangeChannel)
manager.wg.Wait() manager.wg.Wait()
xorg.DisplayClose() xorg.DisplayClose()

View File

@ -1,18 +1,33 @@
package desktop package desktop
import ( import "m1k1o/neko/internal/desktop/xevent"
"m1k1o/neko/internal/desktop/xevent"
"m1k1o/neko/internal/types"
)
func (manager *DesktopManagerCtx) GetCursorChangedChannel() chan uint64 { func (manager *DesktopManagerCtx) OnCursorChanged(listener func(serial uint64)) {
return xevent.CursorChangedChannel xevent.Emmiter.On("cursor-changed", func(payload ...any) {
listener(payload[0].(uint64))
})
} }
func (manager *DesktopManagerCtx) GetClipboardUpdatedChannel() chan struct{} { func (manager *DesktopManagerCtx) OnClipboardUpdated(listener func()) {
return xevent.ClipboardUpdatedChannel xevent.Emmiter.On("clipboard-updated", func(payload ...any) {
listener()
})
} }
func (manager *DesktopManagerCtx) GetEventErrorChannel() chan types.DesktopErrorMessage { func (manager *DesktopManagerCtx) OnFileChooserDialogOpened(listener func()) {
return xevent.EventErrorChannel 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))
})
} }

View File

@ -10,24 +10,13 @@ import "C"
import ( import (
"unsafe" "unsafe"
"m1k1o/neko/internal/types" "github.com/kataras/go-events"
) )
var CursorChangedChannel chan uint64 var Emmiter events.EventEmmiter
var ClipboardUpdatedChannel chan struct{}
var EventErrorChannel chan types.DesktopErrorMessage
func init() { func init() {
CursorChangedChannel = make(chan uint64) Emmiter = events.New()
ClipboardUpdatedChannel = make(chan struct{})
EventErrorChannel = make(chan types.DesktopErrorMessage)
go func() {
for {
// TODO: Reserved for future use.
<-CursorChangedChannel
}
}()
} }
func EventLoop(display string) { func EventLoop(display string) {
@ -37,19 +26,14 @@ func EventLoop(display string) {
C.XEventLoop(displayUnsafe) C.XEventLoop(displayUnsafe)
} }
// TODO: Shutdown function.
//close(CursorChangedChannel)
//close(ClipboardUpdatedChannel)
//close(EventErrorChannel)
//export goXEventCursorChanged //export goXEventCursorChanged
func goXEventCursorChanged(event C.XFixesCursorNotifyEvent) { func goXEventCursorChanged(event C.XFixesCursorNotifyEvent) {
CursorChangedChannel <- uint64(event.cursor_serial) Emmiter.Emit("cursor-changed", uint64(event.cursor_serial))
} }
//export goXEventClipboardUpdated //export goXEventClipboardUpdated
func goXEventClipboardUpdated() { func goXEventClipboardUpdated() {
ClipboardUpdatedChannel <- struct{}{} Emmiter.Emit("clipboard-updated")
} }
//export goXEventConfigureNotify //export goXEventConfigureNotify
@ -64,12 +48,7 @@ func goXEventUnmapNotify(window C.Window) {
//export goXEventError //export goXEventError
func goXEventError(event *C.XErrorEvent, message *C.char) { func goXEventError(event *C.XErrorEvent, message *C.char) {
EventErrorChannel <- types.DesktopErrorMessage{ Emmiter.Emit("event-error", uint8(event.error_code), C.GoString(message), uint8(event.request_code), uint8(event.minor_code))
Error_code: uint8(event.error_code),
Message: C.GoString(message),
Request_code: uint8(event.request_code),
Minor_code: uint8(event.minor_code),
}
} }
//export goXEventActive //export goXEventActive

View File

@ -72,10 +72,10 @@ func (manager *DesktopManagerCtx) ScreenConfigurations() map[int]types.ScreenCon
func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error { func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error {
mu.Lock() mu.Lock()
manager.GetScreenSizeChangeChannel() <- true manager.emmiter.Emit("before_screen_size_change")
defer func() { defer func() {
manager.GetScreenSizeChangeChannel() <- false manager.emmiter.Emit("after_screen_size_change")
mu.Unlock() mu.Unlock()
}() }()

View File

@ -11,8 +11,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/middleware"
"github.com/go-chi/cors" "github.com/go-chi/cors"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -35,9 +35,6 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
router := chi.NewRouter() router := chi.NewRouter()
router.Use(middleware.RequestID) // Create a request ID for each request router.Use(middleware.RequestID) // Create a request ID for each request
if conf.Proxy {
router.Use(middleware.RealIP)
}
router.Use(middleware.RequestLogger(&logformatter{logger})) router.Use(middleware.RequestLogger(&logformatter{logger}))
router.Use(middleware.Recoverer) // Recover from panics without crashing server router.Use(middleware.Recoverer) // Recover from panics without crashing server
router.Use(middleware.Compress(5, "application/octet-stream")) router.Use(middleware.Compress(5, "application/octet-stream"))
@ -166,13 +163,7 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
return return
} }
err = r.ParseMultipartForm(32 << 20) r.ParseMultipartForm(32 << 20)
if err != nil || r.MultipartForm == nil {
logger.Warn().Err(err).Msg("failed to parse multipart form")
http.Error(w, "error parsing form", http.StatusBadRequest)
return
}
for _, formheader := range r.MultipartForm.File["files"] { for _, formheader := range r.MultipartForm.File["files"] {
filePath := webSocketHandler.FileTransferPath(formheader.Filename) filePath := webSocketHandler.FileTransferPath(formheader.Filename)
@ -193,11 +184,6 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
io.Copy(f, formfile) io.Copy(f, formfile)
} }
err = r.MultipartForm.RemoveAll()
if err != nil {
logger.Warn().Err(err).Msg("failed to remove multipart form")
}
}) })
} }

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/middleware"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/kataras/go-events"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -16,8 +17,8 @@ func New(capture types.CaptureManager) *SessionManager {
logger: log.With().Str("module", "session").Logger(), logger: log.With().Str("module", "session").Logger(),
host: "", host: "",
capture: capture, capture: capture,
eventsChannel: make(chan types.SessionEvent, 10),
members: make(map[string]*Session), members: make(map[string]*Session),
emmiter: events.New(),
} }
} }
@ -27,7 +28,7 @@ type SessionManager struct {
host string host string
capture types.CaptureManager capture types.CaptureManager
members map[string]*Session members map[string]*Session
eventsChannel chan types.SessionEvent emmiter events.EventEmmiter
// TODO: Handle locks in sessions as flags. // TODO: Handle locks in sessions as flags.
controlLocked bool controlLocked bool
} }
@ -48,12 +49,7 @@ func (manager *SessionManager) New(id string, admin bool, socket types.WebSocket
manager.capture.Video().AddListener() manager.capture.Video().AddListener()
manager.mu.Unlock() manager.mu.Unlock()
manager.eventsChannel <- types.SessionEvent{ manager.emmiter.Emit("created", id, session)
Type: types.SESSION_CREATED,
Id: id,
Session: session,
}
return session return session
} }
@ -72,12 +68,7 @@ func (manager *SessionManager) SetHost(id string) error {
if ok { if ok {
manager.host = id manager.host = id
manager.emmiter.Emit("host", id)
manager.eventsChannel <- types.SessionEvent{
Type: types.SESSION_HOST_SET,
Id: id,
}
return nil return nil
} }
@ -95,11 +86,7 @@ func (manager *SessionManager) GetHost() (types.Session, bool) {
func (manager *SessionManager) ClearHost() { func (manager *SessionManager) ClearHost() {
id := manager.host id := manager.host
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 { func (manager *SessionManager) Has(id string) bool {
@ -176,11 +163,7 @@ func (manager *SessionManager) Destroy(id string) {
manager.capture.Video().RemoveListener() manager.capture.Video().RemoveListener()
manager.mu.Unlock() manager.mu.Unlock()
manager.eventsChannel <- types.SessionEvent{ manager.emmiter.Emit("destroyed", id, session)
Type: types.SESSION_DESTROYED,
Id: id,
Session: session,
}
manager.logger.Err(err).Str("session_id", id).Msg("destroying session") manager.logger.Err(err).Str("session_id", id).Msg("destroying session")
return return
} }
@ -238,6 +221,32 @@ func (manager *SessionManager) AdminBroadcast(v interface{}, exclude interface{}
return nil return nil
} }
func (manager *SessionManager) GetEventsChannel() chan types.SessionEvent { func (manager *SessionManager) OnHost(listener func(id string)) {
return manager.eventsChannel 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))
})
} }

Some files were not shown because too many files have changed in this diff Show More