mirror of
https://github.com/m1k1o/neko.git
synced 2025-04-30 10:56:20 +02:00
Compare commits
39 commits
Author | SHA1 | Date | |
---|---|---|---|
|
0765352abd | ||
|
f145bd58c9 | ||
|
23820f6255 | ||
|
33ce337cd3 | ||
|
11cef143a3 | ||
|
73e61de52e | ||
|
01112c5e8f | ||
|
fc3b3a4dc6 | ||
|
a8cc365e0f | ||
|
c9ca4e7144 | ||
|
c1ccae4ac4 | ||
|
a46cb0536b | ||
|
b2219396dd | ||
|
2ec35d9d0c | ||
|
4eec843ed2 | ||
|
46e9b19e09 | ||
|
007b55a32b | ||
|
3c787baa40 | ||
|
b8bfcaf4bf | ||
|
6c5cd1260d | ||
|
b783a9adbe | ||
|
81c259cdc9 | ||
|
972d16031e | ||
|
3a8a5c30ef | ||
|
0e3bcedcd4 | ||
|
e3a1929f7f | ||
|
936794da31 | ||
|
1e91dcd7d9 | ||
|
a032c9f42e | ||
|
8b49fb7ca9 | ||
|
da35b05c9c | ||
|
4d6ad8e17d | ||
|
a75424d2f3 | ||
|
1c63b56b94 | ||
|
9eb4c36596 | ||
|
1f7d12b388 | ||
|
68e23fa8e7 | ||
|
ec44bf0e04 | ||
|
b383e1e24d |
58 changed files with 1108 additions and 751 deletions
15
.github/workflows/dockerhub.yml
vendored
15
.github/workflows/dockerhub.yml
vendored
|
@ -4,6 +4,8 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- 'webpage/**'
|
||||
#
|
||||
# Run this action periodically to keep browsers up-to-date
|
||||
# even if there is no activity in this repo.
|
||||
|
@ -11,6 +13,12 @@ on:
|
|||
schedule:
|
||||
- cron: "43 2 * * 1"
|
||||
|
||||
# allow only one workflow to run at a time
|
||||
# and cancel in-progress jobs if a new one is triggered
|
||||
concurrency:
|
||||
group: "dockerhub"
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: m1k1o/neko
|
||||
|
||||
|
@ -42,7 +50,7 @@ jobs:
|
|||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Generate base Dockerfile
|
||||
|
@ -72,7 +80,8 @@ jobs:
|
|||
matrix:
|
||||
tag:
|
||||
- firefox
|
||||
- waterfox
|
||||
# Temporarily disabled due to Cloudflare blocked download link
|
||||
#- waterfox
|
||||
- chromium
|
||||
- google-chrome
|
||||
- ungoogled-chromium
|
||||
|
@ -106,7 +115,7 @@ jobs:
|
|||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
|
|
5
.github/workflows/ghcr.yml
vendored
5
.github/workflows/ghcr.yml
vendored
|
@ -24,8 +24,9 @@ jobs:
|
|||
include:
|
||||
- name: firefox
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
- name: waterfox
|
||||
platforms: linux/amd64
|
||||
# Temporarily disabled due to Cloudflare blocked download link
|
||||
#- name: waterfox
|
||||
# platforms: linux/amd64
|
||||
- name: chromium
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
- name: google-chrome
|
||||
|
|
3
.github/workflows/ghcr_intel.yml
vendored
3
.github/workflows/ghcr_intel.yml
vendored
|
@ -25,7 +25,8 @@ jobs:
|
|||
matrix:
|
||||
include:
|
||||
- name: firefox
|
||||
- name: waterfox
|
||||
# Temporarily disabled due to Cloudflare blocked download link
|
||||
#- name: waterfox
|
||||
- name: chromium
|
||||
- name: google-chrome
|
||||
- name: ungoogled-chromium
|
||||
|
|
|
@ -5,6 +5,9 @@ FROM ./client/ AS client
|
|||
FROM ./utils/xorg-deps/ AS xorg-deps
|
||||
FROM ./runtime/$RUNTIME_DOCKERFILE AS runtime
|
||||
|
||||
# tells neko-rooms which version of the API to use
|
||||
LABEL net.m1k1o.neko.api-version=3
|
||||
|
||||
COPY --from=server /src/bin/plugins/ /etc/neko/plugins/
|
||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||
COPY --from=client /src/dist/ /var/www
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
<a href="https://discord.gg/3U6hWpC">
|
||||
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
|
||||
</a>
|
||||
<a href="https://hellogithub.com/repository/4536d4546af24196af3f08a023dfa007" target="_blank">
|
||||
<img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=4536d4546af24196af3f08a023dfa007&claim_uid=0x19e4dJwD83aW2&theme=small" alt="Featured|HelloGitHub" />
|
||||
</a>
|
||||
<a href="https://github.com/m1k1o/neko/actions">
|
||||
<img src="https://github.com/m1k1o/neko/actions/workflows/ghcr.yml/badge.svg" alt="build">
|
||||
</a>
|
||||
|
@ -155,7 +158,7 @@ Full documentation is available at [neko.m1k1o.net](https://neko.m1k1o.net/). Ke
|
|||
- [Migration from V2](https://neko.m1k1o.net/docs/v3/migration-from-v2)
|
||||
- [Getting Started](https://neko.m1k1o.net/docs/v3/quick-start)
|
||||
- [Installation](https://neko.m1k1o.net/docs/v3/installation)
|
||||
- [Examples](https://neko.m1k1o.net/v3/installation/examples)
|
||||
- [Examples](https://neko.m1k1o.net/docs/v3/installation/examples)
|
||||
- [Configuration](https://neko.m1k1o.net/docs/v3/configuration)
|
||||
- [Frequently Asked Questions](https://neko.m1k1o.net/docs/v3/faq)
|
||||
- [Troubleshooting](https://neko.m1k1o.net/docs/v3/troubleshooting)
|
||||
|
|
19
SECURITY.md
Normal file
19
SECURITY.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If there are any vulnerabilities in **m1k1o/neko**, don't hesitate to _report them_.
|
||||
|
||||
1. Send an email to `security@m1k1o.net`.
|
||||
|
||||
2. Describe the vulnerability.
|
||||
|
||||
If you have a fix, that is most welcome -- please attach or summarize it in your message!
|
||||
|
||||
3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report.
|
||||
|
||||
Please **do not disclose the vulnerability publicly** until a fix is released!
|
||||
|
||||
4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it.
|
||||
|
||||
We appreciate your help in keeping Neko secure.
|
|
@ -15,8 +15,8 @@ lockPref("plugins.hide_infobar_for_missing_plugin", true);
|
|||
lockPref("profile.allow_automigration", false);
|
||||
lockPref("signon.prefillForms", false);
|
||||
lockPref("signon.rememberSignons", false);
|
||||
lockPref("xpinstall.enabled", false);
|
||||
lockPref("xpinstall.whitelist.required", true);
|
||||
//lockPref("xpinstall.enabled", false);
|
||||
//lockPref("xpinstall.whitelist.required", true);
|
||||
lockPref("browser.download.manager.retention", 0);
|
||||
lockPref("browser.download.folderList", 2);
|
||||
lockPref("browser.download.forbid_open_with", true);
|
||||
|
|
|
@ -10,7 +10,7 @@ RUN set -eux; apt-get update; \
|
|||
xz-utils bzip2 libgtk-3-0 libdbus-glib-1-2; \
|
||||
#
|
||||
# fetch latest release
|
||||
wget -O /tmp/waterfox-setup.tar.bz2 "${SRC_URL}"; \
|
||||
wget --user-agent="Mozilla/5.0" -O /tmp/waterfox-setup.tar.bz2 "${SRC_URL}"; \
|
||||
mkdir /usr/lib/waterfox; \
|
||||
tar -xjf /tmp/waterfox-setup.tar.bz2 -C /usr/lib; \
|
||||
rm -f /tmp/waterfox-setup.tar.bz2; \
|
||||
|
|
|
@ -15,8 +15,8 @@ lockPref("plugins.hide_infobar_for_missing_plugin", true);
|
|||
lockPref("profile.allow_automigration", false);
|
||||
lockPref("signon.prefillForms", false);
|
||||
lockPref("signon.rememberSignons", false);
|
||||
lockPref("xpinstall.enabled", false);
|
||||
lockPref("xpinstall.whitelist.required", true);
|
||||
//lockPref("xpinstall.enabled", false);
|
||||
//lockPref("xpinstall.whitelist.required", true);
|
||||
lockPref("browser.download.manager.retention", 0);
|
||||
lockPref("browser.download.folderList", 2);
|
||||
lockPref("browser.download.forbid_open_with", true);
|
||||
|
|
11
build
11
build
|
@ -211,14 +211,15 @@ fi
|
|||
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
# use system architecture if not specified
|
||||
if [ "$(uname -m)" == "x86_64" ]; then
|
||||
UNAME="$(uname -m)"
|
||||
if [ "$UNAME" == "x86_64" ]; then
|
||||
PLATFORM="linux/amd64"
|
||||
elif [ "$(uname -m)" == "aarch64" ]; then
|
||||
elif [ "$UNAME" == "aarch64" ] || [ "$UNAME" == "arm64" ]; then
|
||||
PLATFORM="linux/arm64"
|
||||
elif [ "$(uname -m)" == "armv7l" ]; then
|
||||
elif [ "$UNAME" == "armv7l" ]; then
|
||||
PLATFORM="linux/arm/v7"
|
||||
else
|
||||
log "Unknown architecture: $(uname -m)"
|
||||
log "Unknown architecture: $UNAME"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
@ -313,7 +314,7 @@ fi
|
|||
|
||||
log "Building base image: $BASE_IMAGE"
|
||||
docker run --rm -i \
|
||||
-v ./:/src \
|
||||
-v "$(pwd)":/src \
|
||||
-e "RUNTIME_DOCKERFILE=$RUNTIME_DOCKERFILE" \
|
||||
--workdir /src \
|
||||
--entrypoint go \
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<li
|
||||
v-if="hosting && is_touch_device"
|
||||
:class="extraControls || 'extra-control'"
|
||||
@click.stop.prevent="toggleMobileKeyboard"
|
||||
@click.stop.prevent="openMobileKeyboard"
|
||||
>
|
||||
<i class="fas fa-keyboard" />
|
||||
</li>
|
||||
|
@ -367,12 +367,11 @@
|
|||
|
||||
get is_touch_device() {
|
||||
return (
|
||||
// check if the device has a touch screen
|
||||
// detect if the device has touch support
|
||||
('ontouchstart' in window || navigator.maxTouchPoints > 0) &&
|
||||
// we also check if the device has a pointer
|
||||
!window.matchMedia('(pointer:fine)').matches &&
|
||||
// and is capable of hover, then it probably has a mouse
|
||||
!window.matchMedia('(hover:hover)').matches
|
||||
// the primary input mechanism includes a pointing device of
|
||||
// limited accuracy, such as a finger on a touchscreen.
|
||||
window.matchMedia('(pointer: coarse)').matches
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -822,64 +821,20 @@
|
|||
@Watch('hosting')
|
||||
@Watch('locked')
|
||||
onFocus() {
|
||||
// focus opens the keyboard on mobile
|
||||
if (this.is_touch_device) {
|
||||
return
|
||||
}
|
||||
|
||||
// in order to capture key events, overlay must be focused
|
||||
if (this.focused && this.hosting && !this.locked) {
|
||||
this._overlay.focus()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// mobile keyboard
|
||||
//
|
||||
|
||||
kbdShow = false
|
||||
kbdOpen = false
|
||||
|
||||
showMobileKeyboard() {
|
||||
// skip if not a touch device
|
||||
if (!this.is_touch_device) return
|
||||
|
||||
this.kbdShow = true
|
||||
this.kbdOpen = false
|
||||
|
||||
const overlay = this.$refs.overlay as HTMLTextAreaElement
|
||||
overlay.focus()
|
||||
window.visualViewport?.addEventListener('resize', this.onVisualViewportResize)
|
||||
}
|
||||
|
||||
hideMobileKeyboard() {
|
||||
// skip if not a touch device
|
||||
if (!this.is_touch_device) return
|
||||
|
||||
this.kbdShow = false
|
||||
this.kbdOpen = false
|
||||
|
||||
const overlay = this.$refs.overlay as HTMLTextAreaElement
|
||||
window.visualViewport?.removeEventListener('resize', this.onVisualViewportResize)
|
||||
overlay.blur()
|
||||
}
|
||||
|
||||
toggleMobileKeyboard() {
|
||||
// skip if not a touch device
|
||||
if (!this.is_touch_device) return
|
||||
|
||||
if (this.kbdShow) {
|
||||
this.hideMobileKeyboard()
|
||||
} else {
|
||||
this.showMobileKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
// visual viewport resize event is fired when keyboard is opened or closed
|
||||
// android does not blur textarea when keyboard is closed, so we need to do it manually
|
||||
onVisualViewportResize() {
|
||||
if (!this.kbdShow) return
|
||||
|
||||
if (!this.kbdOpen) {
|
||||
this.kbdOpen = true
|
||||
} else {
|
||||
this.hideMobileKeyboard()
|
||||
}
|
||||
openMobileKeyboard() {
|
||||
// focus opens the keyboard on mobile
|
||||
this._overlay.focus()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -103,7 +103,9 @@ ENV NEKO_PLUGINS_DIR=/etc/neko/plugins/
|
|||
#
|
||||
# add healthcheck
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || exit 1
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
wget --no-check-certificate -O - https://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
exit 1
|
||||
|
||||
#
|
||||
# run neko
|
||||
|
|
|
@ -95,8 +95,9 @@ ENV NEKO_PLUGINS_DIR=/etc/neko/plugins/
|
|||
#
|
||||
# add healthcheck
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || exit 1
|
||||
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
wget --no-check-certificate -O - https://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
exit 1
|
||||
#
|
||||
# run neko
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]
|
||||
|
|
|
@ -115,8 +115,9 @@ ENV RENDER_GID=
|
|||
#
|
||||
# add healthcheck
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || exit 1
|
||||
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
wget --no-check-certificate -O - https://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
exit 1
|
||||
#
|
||||
# run neko
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]
|
||||
|
|
|
@ -262,7 +262,9 @@ COPY --from=gstreamer /usr/share/gstreamer /usr/share/gstreamer
|
|||
#
|
||||
# add healthcheck
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || exit 1
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
wget --no-check-certificate -O - https://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
exit 1
|
||||
|
||||
#
|
||||
# run neko
|
||||
|
|
|
@ -254,7 +254,9 @@ COPY --from=gstreamer /usr/share/gstreamer /usr/share/gstreamer
|
|||
#
|
||||
# add healthcheck
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || exit 1
|
||||
CMD wget -O - http://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
wget --no-check-certificate -O - https://localhost:${NEKO_SERVER_BIND#*:}/health || \
|
||||
exit 1
|
||||
|
||||
#
|
||||
# run neko
|
||||
|
|
|
@ -9,10 +9,10 @@ GIT_COMMIT=`git rev-parse --short HEAD`
|
|||
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
|
||||
|
||||
echo "Building server image"
|
||||
docker build -t neko_server --build-arg "GIT_COMMIT=$GIT_COMMIT" --build-arg "GIT_BRANCH=$GIT_BRANCH" -f ../Dockerfile ..
|
||||
docker build -t neko_server:src -f ../Dockerfile ..
|
||||
|
||||
echo "Building base image"
|
||||
../../build -y -b neko_server -t base -f "$1"
|
||||
../../build -y -b neko_server:base -f "$1"
|
||||
|
||||
echo "Building app image"
|
||||
docker build -t neko_server:app --build-arg "BASE_IMAGE=neko_server:base" -f ./runtime/Dockerfile ./runtime
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "$(docker images -q neko_server 2> /dev/null)" == "" ]; then
|
||||
echo "Image 'neko_server' not found. Run ./build first."
|
||||
if [ "$(docker images -q neko_server:src 2> /dev/null)" == "" ]; then
|
||||
echo "Image 'neko_server:src' not found. Run ./build first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker run -it --rm \
|
||||
--entrypoint="go" \
|
||||
-v "${PWD}/../:/src" \
|
||||
neko_server fmt ./...
|
||||
neko_server:src fmt ./...
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "$(docker images -q neko_server 2> /dev/null)" == "" ]; then
|
||||
echo "Image 'neko_server' not found. Run ./build first."
|
||||
if [ "$(docker images -q neko_server:src 2> /dev/null)" == "" ]; then
|
||||
echo "Image 'neko_server:src' not found. Run ./build first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -10,7 +10,7 @@ docker run -it \
|
|||
--name "neko_server_go" \
|
||||
--entrypoint="go" \
|
||||
-v "${PWD}/../:/src" \
|
||||
neko_server "$@";
|
||||
neko_server:src "$@";
|
||||
#
|
||||
# copy package files
|
||||
docker cp neko_server_go:/src/go.mod "../go.mod"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "$(docker images -q neko_server 2> /dev/null)" == "" ]; then
|
||||
echo "Image 'neko_server' not found. Run ./build first."
|
||||
if [ "$(docker images -q neko_server:src 2> /dev/null)" == "" ]; then
|
||||
echo "Image 'neko_server:src' not found. Run ./build first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -11,4 +11,4 @@ fi
|
|||
docker run --rm -it \
|
||||
-v "${PWD}/../:/src" \
|
||||
--entrypoint="/bin/bash" \
|
||||
neko_server -c '[ -f ./bin/golangci-lint ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.31.0;./bin/golangci-lint run';
|
||||
neko_server:src -c '[ -f ./bin/golangci-lint ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.31.0;./bin/golangci-lint run';
|
||||
|
|
|
@ -10,7 +10,7 @@ set -e
|
|||
docker run --rm -it \
|
||||
-v "${PWD}/../:/src" \
|
||||
--entrypoint="/bin/bash" \
|
||||
neko_server "./build" "$@";
|
||||
neko_server:src "./build" "$@";
|
||||
|
||||
#
|
||||
# remove old plugins
|
||||
|
|
|
@ -90,12 +90,12 @@ func (Capture) Init(cmd *cobra.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("capture.video.pipelines", "[]", "pipelines config in JSON used for video streaming")
|
||||
cmd.PersistentFlags().String("capture.video.pipelines", "{}", "pipelines config used for video streaming")
|
||||
if err := viper.BindPFlag("capture.video.pipelines", cmd.PersistentFlags().Lookup("capture.video.pipelines")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("capture.video.pipeline", "", "gstreamer pipeline used for video streaming; shortcut for having only a single video pipeline instead of multiple, ignored if capture.video.pipelines is set")
|
||||
cmd.PersistentFlags().String("capture.video.pipeline", "", "shortcut for configuring only a single gstreamer pipeline, ignored if pipelines is set")
|
||||
if err := viper.BindPFlag("capture.video.pipeline", cmd.PersistentFlags().Lookup("capture.video.pipeline")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -451,6 +451,7 @@ func (s *Capture) SetV2() {
|
|||
enableLegacy = true
|
||||
}
|
||||
|
||||
modifiedVideoCodec := false
|
||||
if videoCodec := viper.GetString("video_codec"); videoCodec != "" {
|
||||
s.VideoCodec, ok = codec.ParseStr(videoCodec)
|
||||
if !ok || s.VideoCodec.Type != webrtc.RTPCodecTypeVideo {
|
||||
|
@ -459,24 +460,29 @@ func (s *Capture) SetV2() {
|
|||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_VIDEO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_VIDEO_CODEC' instead")
|
||||
enableLegacy = true
|
||||
modifiedVideoCodec = true
|
||||
}
|
||||
|
||||
if viper.GetBool("vp8") {
|
||||
s.VideoCodec = codec.VP8()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP8=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp8' instead")
|
||||
enableLegacy = true
|
||||
modifiedVideoCodec = true
|
||||
} else if viper.GetBool("vp9") {
|
||||
s.VideoCodec = codec.VP9()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP9=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp9' instead")
|
||||
enableLegacy = true
|
||||
modifiedVideoCodec = true
|
||||
} else if viper.GetBool("h264") {
|
||||
s.VideoCodec = codec.H264()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_CAPTURE_VIDEO_CODEC=h264' instead")
|
||||
enableLegacy = true
|
||||
modifiedVideoCodec = true
|
||||
} else if viper.GetBool("av1") {
|
||||
s.VideoCodec = codec.AV1()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_CAPTURE_VIDEO_CODEC=av1' instead")
|
||||
enableLegacy = true
|
||||
modifiedVideoCodec = true
|
||||
}
|
||||
|
||||
videoHWEnc := HwEncUnset
|
||||
|
@ -498,7 +504,7 @@ func (s *Capture) SetV2() {
|
|||
videoPipeline := viper.GetString("video")
|
||||
|
||||
// video pipeline
|
||||
if videoHWEnc != HwEncUnset || videoBitrate != 0 || videoMaxFPS != 0 || videoPipeline != "" {
|
||||
if modifiedVideoCodec || videoHWEnc != HwEncUnset || videoBitrate != 0 || videoMaxFPS != 0 || videoPipeline != "" {
|
||||
pipeline, err := NewVideoPipeline(s.VideoCodec, s.Display, videoPipeline, videoMaxFPS, videoBitrate, videoHWEnc)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("unable to create video pipeline, using default")
|
||||
|
@ -534,6 +540,7 @@ func (s *Capture) SetV2() {
|
|||
enableLegacy = true
|
||||
}
|
||||
|
||||
modifiedAudioCodec := false
|
||||
if audioCodec := viper.GetString("audio_codec"); audioCodec != "" {
|
||||
s.AudioCodec, ok = codec.ParseStr(audioCodec)
|
||||
if !ok || s.AudioCodec.Type != webrtc.RTPCodecTypeAudio {
|
||||
|
@ -542,31 +549,36 @@ func (s *Capture) SetV2() {
|
|||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_AUDIO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_AUDIO_CODEC' instead")
|
||||
enableLegacy = true
|
||||
modifiedAudioCodec = true
|
||||
}
|
||||
|
||||
if viper.GetBool("opus") {
|
||||
s.AudioCodec = codec.Opus()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_OPUS=true', use 'NEKO_CAPTURE_AUDIO_CODEC=opus' instead")
|
||||
enableLegacy = true
|
||||
modifiedAudioCodec = true
|
||||
} else if viper.GetBool("g722") {
|
||||
s.AudioCodec = codec.G722()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_G722=true', use 'NEKO_CAPTURE_AUDIO_CODEC=g722' instead")
|
||||
enableLegacy = true
|
||||
modifiedAudioCodec = true
|
||||
} else if viper.GetBool("pcmu") {
|
||||
s.AudioCodec = codec.PCMU()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMU=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcmu' instead")
|
||||
enableLegacy = true
|
||||
modifiedAudioCodec = true
|
||||
} else if viper.GetBool("pcma") {
|
||||
s.AudioCodec = codec.PCMA()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMA=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcma' instead")
|
||||
enableLegacy = true
|
||||
modifiedAudioCodec = true
|
||||
}
|
||||
|
||||
audioBitrate := viper.GetUint("audio_bitrate")
|
||||
audioPipeline := viper.GetString("audio")
|
||||
|
||||
// audio pipeline
|
||||
if audioBitrate != 0 || audioPipeline != "" {
|
||||
if modifiedAudioCodec || audioBitrate != 0 || audioPipeline != "" {
|
||||
pipeline, err := NewAudioPipeline(s.AudioCodec, s.AudioDevice, audioPipeline, audioBitrate)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("unable to create audio pipeline, using default")
|
||||
|
@ -604,7 +616,7 @@ func (s *Capture) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ func (s *Desktop) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,45 +22,45 @@ type Member struct {
|
|||
}
|
||||
|
||||
func (Member) Init(cmd *cobra.Command) error {
|
||||
cmd.PersistentFlags().String("member.provider", "multiuser", "choose member provider")
|
||||
cmd.PersistentFlags().String("member.provider", "multiuser", "selected member provider")
|
||||
if err := viper.BindPFlag("member.provider", cmd.PersistentFlags().Lookup("member.provider")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// file provider
|
||||
cmd.PersistentFlags().String("member.file.path", "", "member file provider: storage path")
|
||||
cmd.PersistentFlags().String("member.file.path", "", "member file provider: path to the file containing the users and their passwords")
|
||||
if err := viper.BindPFlag("member.file.path", cmd.PersistentFlags().Lookup("member.file.path")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().Bool("member.file.hash", true, "member file provider: whether to hash passwords using sha256 (recommended)")
|
||||
cmd.PersistentFlags().Bool("member.file.hash", true, "member file provider: whether the passwords are hashed using sha256 or not (recommended)")
|
||||
if err := viper.BindPFlag("member.file.hash", cmd.PersistentFlags().Lookup("member.file.hash")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// object provider
|
||||
cmd.PersistentFlags().String("member.object.users", "[]", "member object provider: users in JSON format")
|
||||
cmd.PersistentFlags().String("member.object.users", "[]", "member object provider: list of users with their passwords and profiles")
|
||||
if err := viper.BindPFlag("member.object.users", cmd.PersistentFlags().Lookup("member.object.users")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// multiuser provider
|
||||
cmd.PersistentFlags().String("member.multiuser.user_password", "neko", "member multiuser provider: user password")
|
||||
cmd.PersistentFlags().String("member.multiuser.user_password", "neko", "member multiuser provider: password for regular users")
|
||||
if err := viper.BindPFlag("member.multiuser.user_password", cmd.PersistentFlags().Lookup("member.multiuser.user_password")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("member.multiuser.admin_password", "admin", "member multiuser provider: admin password")
|
||||
cmd.PersistentFlags().String("member.multiuser.admin_password", "admin", "member multiuser provider: password for admin users")
|
||||
if err := viper.BindPFlag("member.multiuser.admin_password", cmd.PersistentFlags().Lookup("member.multiuser.admin_password")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("member.multiuser.user_profile", "{}", "member multiuser provider: user profile in JSON format")
|
||||
cmd.PersistentFlags().String("member.multiuser.user_profile", "{}", "member multiuser provider: profile template for regular users")
|
||||
if err := viper.BindPFlag("member.multiuser.user_profile", cmd.PersistentFlags().Lookup("member.multiuser.user_profile")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("member.multiuser.admin_profile", "{}", "member multiuser provider: admin profile in JSON format")
|
||||
cmd.PersistentFlags().String("member.multiuser.admin_profile", "{}", "member multiuser provider: profile template for admin users")
|
||||
if err := viper.BindPFlag("member.multiuser.admin_profile", cmd.PersistentFlags().Lookup("member.multiuser.admin_profile")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ func (s *Member) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ func (s *Root) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ func (s *Server) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -206,7 +206,7 @@ func (s *Session) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,17 +67,17 @@ func (WebRTC) Init(cmd *cobra.Command) error {
|
|||
}
|
||||
|
||||
// Looks like this is conflicting with the frontend and backend ICE servers since latest versions
|
||||
//cmd.PersistentFlags().String("webrtc.iceservers", "[]", "Global STUN and TURN servers in JSON format with `urls`, `username` and `credential` keys")
|
||||
//cmd.PersistentFlags().String("webrtc.iceservers", "[]", "STUN and TURN servers used by the ICE agent")
|
||||
//if err := viper.BindPFlag("webrtc.iceservers", cmd.PersistentFlags().Lookup("webrtc.iceservers")); err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
cmd.PersistentFlags().String("webrtc.iceservers.frontend", "[]", "Frontend only STUN and TURN servers in JSON format with `urls`, `username` and `credential` keys")
|
||||
cmd.PersistentFlags().String("webrtc.iceservers.frontend", "[]", "STUN and TURN servers used by the frontend")
|
||||
if err := viper.BindPFlag("webrtc.iceservers.frontend", cmd.PersistentFlags().Lookup("webrtc.iceservers.frontend")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("webrtc.iceservers.backend", "[]", "Backend only STUN and TURN servers in JSON format with `urls`, `username` and `credential` keys")
|
||||
cmd.PersistentFlags().String("webrtc.iceservers.backend", "[]", "STUN and TURN servers used by the backend")
|
||||
if err := viper.BindPFlag("webrtc.iceservers.backend", cmd.PersistentFlags().Lookup("webrtc.iceservers.backend")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -217,14 +217,14 @@ func (s *WebRTC) Set() {
|
|||
|
||||
// parse frontend ice servers
|
||||
if err := viper.UnmarshalKey("webrtc.iceservers.frontend", &s.ICEServersFrontend, viper.DecodeHook(
|
||||
utils.JsonStringAutoDecode([]types.ICEServer{}),
|
||||
utils.JsonStringAutoDecode(s.ICEServersFrontend),
|
||||
)); err != nil {
|
||||
log.Warn().Err(err).Msgf("unable to parse frontend ICE servers")
|
||||
}
|
||||
|
||||
// parse backend ice servers
|
||||
if err := viper.UnmarshalKey("webrtc.iceservers.backend", &s.ICEServersBackend, viper.DecodeHook(
|
||||
utils.JsonStringAutoDecode([]types.ICEServer{}),
|
||||
utils.JsonStringAutoDecode(s.ICEServersBackend),
|
||||
)); err != nil {
|
||||
log.Warn().Err(err).Msgf("unable to parse backend ICE servers")
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ func (s *WebRTC) Set() {
|
|||
// parse global ice servers
|
||||
var iceServers []types.ICEServer
|
||||
if err := viper.UnmarshalKey("webrtc.iceservers", &iceServers, viper.DecodeHook(
|
||||
utils.JsonStringAutoDecode([]types.ICEServer{}),
|
||||
utils.JsonStringAutoDecode(iceServers),
|
||||
)); err != nil {
|
||||
log.Warn().Err(err).Msgf("unable to parse global ICE servers")
|
||||
}
|
||||
|
@ -409,7 +409,7 @@ func (s *WebRTC) SetV2() {
|
|||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,19 @@ import (
|
|||
"github.com/m1k1o/neko/server/pkg/xevent"
|
||||
)
|
||||
|
||||
const (
|
||||
ClipboardTextPlainTarget = "UTF8_STRING"
|
||||
ClipboardTextHtmlTarget = "text/html"
|
||||
)
|
||||
|
||||
func (manager *DesktopManagerCtx) ClipboardGetText() (*types.ClipboardText, error) {
|
||||
text, err := manager.ClipboardGetBinary("STRING")
|
||||
text, err := manager.ClipboardGetBinary(ClipboardTextPlainTarget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rich text must not always be available, can fail silently.
|
||||
html, _ := manager.ClipboardGetBinary("text/html")
|
||||
html, _ := manager.ClipboardGetBinary(ClipboardTextHtmlTarget)
|
||||
|
||||
return &types.ClipboardText{
|
||||
Text: string(text),
|
||||
|
@ -31,10 +36,10 @@ func (manager *DesktopManagerCtx) ClipboardSetText(data types.ClipboardText) err
|
|||
// is set, if available. Otherwise plain text.
|
||||
|
||||
if data.HTML != "" {
|
||||
return manager.ClipboardSetBinary("text/html", []byte(data.HTML))
|
||||
return manager.ClipboardSetBinary(ClipboardTextHtmlTarget, []byte(data.HTML))
|
||||
}
|
||||
|
||||
return manager.ClipboardSetBinary("STRING", []byte(data.Text))
|
||||
return manager.ClipboardSetBinary(ClipboardTextPlainTarget, []byte(data.Text))
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) ClipboardGetBinary(mime string) ([]byte, error) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/api"
|
||||
oldEvent "github.com/m1k1o/neko/server/internal/http/legacy/event"
|
||||
|
@ -35,9 +36,6 @@ var (
|
|||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
DefaultDialer = websocket.DefaultDialer
|
||||
)
|
||||
|
||||
type LegacyHandler struct {
|
||||
|
@ -45,16 +43,21 @@ type LegacyHandler struct {
|
|||
serverAddr string
|
||||
bannedIPs map[string]struct{}
|
||||
sessionIPs map[string]string
|
||||
wsDialer *websocket.Dialer
|
||||
}
|
||||
|
||||
func New() *LegacyHandler {
|
||||
func New(serverAddr string) *LegacyHandler {
|
||||
// Init
|
||||
|
||||
return &LegacyHandler{
|
||||
logger: log.With().Str("module", "legacy").Logger(),
|
||||
serverAddr: "127.0.0.1:8080",
|
||||
serverAddr: serverAddr,
|
||||
bannedIPs: make(map[string]struct{}),
|
||||
sessionIPs: make(map[string]string),
|
||||
wsDialer: &websocket.Dialer{
|
||||
Proxy: nil, // disable proxy for local requests
|
||||
HandshakeTimeout: 45 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +102,7 @@ func (h *LegacyHandler) Route(r types.Router) {
|
|||
defer s.destroy()
|
||||
|
||||
// dial to the remote backend
|
||||
connBackend, _, err := DefaultDialer.Dial("ws://"+h.serverAddr+"/api/ws?token="+url.QueryEscape(s.token), nil)
|
||||
connBackend, _, err := h.wsDialer.Dial("ws://"+h.serverAddr+"/api/ws?token="+url.QueryEscape(s.token), nil)
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).Msg("couldn't dial to the remote backend")
|
||||
|
||||
|
@ -142,7 +145,7 @@ func (h *LegacyHandler) Route(r types.Router) {
|
|||
m = websocket.FormatCloseMessage(e.Code, e.Text)
|
||||
}
|
||||
}
|
||||
errc <- err
|
||||
errc <- fmt.Errorf("src read message error: %w", err)
|
||||
dst.WriteMessage(websocket.CloseMessage, m)
|
||||
break
|
||||
}
|
||||
|
@ -163,12 +166,20 @@ func (h *LegacyHandler) Route(r types.Router) {
|
|||
})
|
||||
continue
|
||||
} else if errors.Is(err, ErrWebsocketSend) {
|
||||
errc <- err
|
||||
errc <- fmt.Errorf("dst write message error: %w", err)
|
||||
break
|
||||
} else {
|
||||
h.logger.Error().Err(err).Msg("couldn't rewrite text message")
|
||||
}
|
||||
}
|
||||
// forward ping messages
|
||||
if msgType == websocket.PingMessage {
|
||||
err = dst.WriteMessage(websocket.PingMessage, nil)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,9 +192,9 @@ func (h *LegacyHandler) Route(r types.Router) {
|
|||
var message string
|
||||
select {
|
||||
case err = <-errClient:
|
||||
message = "websocketproxy: Error when copying from backend to client: %v"
|
||||
message = "websocketproxy: Error when copying from backend to client"
|
||||
case err = <-errBackend:
|
||||
message = "websocketproxy: Error when copying from client to backend: %v"
|
||||
message = "websocketproxy: Error when copying from client to backend"
|
||||
}
|
||||
|
||||
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
|
||||
|
|
|
@ -55,13 +55,18 @@ type session struct {
|
|||
}
|
||||
|
||||
func (h *LegacyHandler) newSession(r *http.Request) *session {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.Proxy = nil // disable proxy for local requests
|
||||
|
||||
return &session{
|
||||
r: r,
|
||||
h: h,
|
||||
logger: h.logger,
|
||||
serverAddr: h.serverAddr,
|
||||
client: http.DefaultClient,
|
||||
sessions: make(map[string]*memberStruct),
|
||||
client: &http.Client{
|
||||
Transport: transport,
|
||||
},
|
||||
sessions: make(map[string]*memberStruct),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package http
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
|
@ -56,11 +57,6 @@ func New(WebSocketManager types.WebSocketManager, ApiManager types.ApiManager, c
|
|||
return config.AllowOrigin(r.Header.Get("Origin"))
|
||||
}))
|
||||
|
||||
// Legacy handler
|
||||
if viper.GetBool("legacy") {
|
||||
legacy.New().Route(router)
|
||||
}
|
||||
|
||||
batch := batchHandler{
|
||||
Router: router,
|
||||
PathPrefix: "/api",
|
||||
|
@ -122,6 +118,24 @@ func (manager *HttpManagerCtx) Start() {
|
|||
}
|
||||
}()
|
||||
manager.logger.Info().Msgf("https listening on %s", manager.http.Addr)
|
||||
|
||||
// if we have legacy mode, we need to start local http server too
|
||||
if viper.GetBool("legacy") {
|
||||
// create a listener for the API server with a random port
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
manager.logger.Panic().Err(err).Msg("unable to start legacy http proxy")
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(listener, manager.router); err != http.ErrServerClosed {
|
||||
manager.logger.Panic().Err(err).Msg("unable to start http server")
|
||||
}
|
||||
}()
|
||||
manager.logger.Info().Msgf("legacy proxy listening on %s", listener.Addr().String())
|
||||
|
||||
legacy.New(listener.Addr().String()).Route(manager.router)
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
if err := manager.http.ListenAndServe(); err != http.ErrServerClosed {
|
||||
|
@ -129,6 +143,11 @@ func (manager *HttpManagerCtx) Start() {
|
|||
}
|
||||
}()
|
||||
manager.logger.Info().Msgf("http listening on %s", manager.http.Addr)
|
||||
|
||||
// start legacy proxy if enabled
|
||||
if viper.GetBool("legacy") {
|
||||
legacy.New(manager.http.Addr).Route(manager.router)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ func (manager *WebRTCManagerCtx) handleLegacy(
|
|||
}
|
||||
|
||||
logger.
|
||||
Debug().
|
||||
Trace().
|
||||
Str("x", strconv.Itoa(int(payload.X))).
|
||||
Str("y", strconv.Itoa(int(payload.Y))).
|
||||
Msg("scroll")
|
||||
|
@ -97,7 +97,7 @@ func (manager *WebRTCManagerCtx) handleLegacy(
|
|||
return nil
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("button down %d", payload.Key)
|
||||
logger.Trace().Msgf("button down %d", payload.Key)
|
||||
} else {
|
||||
err := manager.desktop.KeyDown(uint32(payload.Key))
|
||||
if err != nil {
|
||||
|
@ -105,7 +105,7 @@ func (manager *WebRTCManagerCtx) handleLegacy(
|
|||
return nil
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("key down %d", payload.Key)
|
||||
logger.Trace().Msgf("key down %d", payload.Key)
|
||||
}
|
||||
case OP_KEY_UP:
|
||||
payload := &PayloadKey{}
|
||||
|
@ -121,7 +121,7 @@ func (manager *WebRTCManagerCtx) handleLegacy(
|
|||
return nil
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("button up %d", payload.Key)
|
||||
logger.Trace().Msgf("button up %d", payload.Key)
|
||||
} else {
|
||||
err := manager.desktop.KeyUp(uint32(payload.Key))
|
||||
if err != nil {
|
||||
|
@ -129,7 +129,7 @@ func (manager *WebRTCManagerCtx) handleLegacy(
|
|||
return nil
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("key up %d", payload.Key)
|
||||
logger.Trace().Msgf("key up %d", payload.Key)
|
||||
}
|
||||
case OP_KEY_CLK:
|
||||
// unused
|
||||
|
|
|
@ -276,7 +276,9 @@ func (manager *WebSocketManagerCtx) connect(connection *websocket.Conn, r *http.
|
|||
|
||||
e, ok := err.(*websocket.CloseError)
|
||||
if !ok {
|
||||
err = errors.Unwrap(err) // unwrap if possible
|
||||
if e := errors.Unwrap(err); e != nil {
|
||||
err = e // unwrap if possible
|
||||
}
|
||||
logger.Warn().Err(err).Msg("read message error")
|
||||
// client is expected to reconnect soon
|
||||
delayedDisconnect = true
|
||||
|
|
|
@ -43,7 +43,9 @@ func (peer *WebSocketPeerCtx) Send(event string, payload any) {
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
err = errors.Unwrap(err) // unwrap if possible
|
||||
if e := errors.Unwrap(err); e != nil {
|
||||
err = e // unwrap if possible
|
||||
}
|
||||
peer.logger.Warn().Err(err).Str("event", event).Msg("send message error")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Def, Opt } from '@site/src/components/Anchor';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
# Configuration
|
||||
|
||||
|
@ -291,24 +293,23 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
This is the initial configuration of the room that can be modified by an admin in real-time.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
session:
|
||||
private_mode: false
|
||||
locked_logins: false
|
||||
locked_controls: false
|
||||
control_protection: false
|
||||
implicit_hosting: true
|
||||
inactive_cursors: false
|
||||
merciful_reconnect: true
|
||||
heartbeat_interval: 120
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'session.private_mode',
|
||||
'session.locked_logins',
|
||||
'session.locked_controls',
|
||||
'session.control_protection',
|
||||
'session.implicit_hosting',
|
||||
'session.inactive_cursors',
|
||||
'session.merciful_reconnect',
|
||||
'session.heartbeat_interval',
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="session.private_mode" /> whether private mode is enabled, users do not receive the room video or audio.
|
||||
- <Def id="session.locked_logins" /> whether logins are locked for users, admins can still login.
|
||||
- <Def id="session.locked_controls" /> whether controls are locked for users, admins can still control.
|
||||
- <Def id="session.control_protection" /> users can gain control only if at least one admin is in the room.
|
||||
- <Def id="session.implicit_hosting" /> allows switching control implicitly without the need for explicit control request before
|
||||
- <Def id="session.inactive_cursors" /> whether to show inactive cursors server-wide (only for users that have it enabled in their profile)
|
||||
- <Def id="session.implicit_hosting" /> automatically grants control to a user when they click on the screen, unless an admin has locked the controls.
|
||||
- <Def id="session.inactive_cursors" /> whether to show inactive cursors server-wide (only for users that have it enabled in their profile).
|
||||
- <Def id="session.merciful_reconnect" /> whether to allow reconnecting to the websocket even if the previous connection was not closed. This means that a new login can kick out the previous one.
|
||||
- <Def id="session.heartbeat_interval" /> interval in seconds for sending a heartbeat message to the server. This is used to keep the connection alive and to detect when the connection is lost.
|
||||
|
||||
|
@ -316,18 +317,17 @@ session:
|
|||
|
||||
This is the configuration of the neko server.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
server:
|
||||
bind: "127.0.0.1:8080"
|
||||
cert: "/path/to/cert.pem"
|
||||
key: "/path/to/key.pem"
|
||||
cors: [ "*" ]
|
||||
metrics: true
|
||||
path_prefix: "/neko"
|
||||
pprof: true
|
||||
proxy: true
|
||||
static: "/var/www/neko"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'server.bind',
|
||||
'server.cert',
|
||||
'server.key',
|
||||
'server.cors',
|
||||
'server.metrics',
|
||||
'server.path_prefix',
|
||||
'server.pprof',
|
||||
'server.proxy',
|
||||
'server.static',
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="server.bind" /> address/port/socket to serve neko. For docker you might want to bind to `0.0.0.0` to allow connections from outside the container.
|
||||
- <Def id="server.cert" /> and <Def id="server.key" /> paths to the SSL cert and key used to secure the neko server. If both are empty, the server will run in plain HTTP.
|
||||
|
@ -345,14 +345,13 @@ server:
|
|||
|
||||
This is the configuration of the logging system.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
log:
|
||||
dir: <string>
|
||||
json: true
|
||||
level: "info"
|
||||
nocolor: true
|
||||
time: "unix"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'log.dir',
|
||||
'log.json',
|
||||
'log.level',
|
||||
'log.nocolor',
|
||||
'log.time',
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="log.dir" /> directory to store logs. If empty, logs are written to stdout. This is useful when running neko in a container.
|
||||
- <Def id="log.json" /> when true, logs are written in JSON format.
|
||||
|
@ -368,10 +367,7 @@ Shortcut environment variable to enable DEBUG mode: `NEKO_DEBUG=true`
|
|||
|
||||
Here is a full configuration with default values as shown in the help command. Please refer to the sub-sections for more details.
|
||||
|
||||
import Configuration from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
<Configuration configOptions={configOptions} />
|
||||
<ConfigurationTab options={configOptions} heading={true} />
|
||||
|
||||
## Next Steps {#next}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ description: Configuration related to the Authentication and Sessions in Neko.
|
|||
---
|
||||
|
||||
import { Def, Opt } from '@site/src/components/Anchor';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
# Authentication
|
||||
|
||||
|
@ -78,6 +80,10 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
Member providers are responsible for deciding whether given credentials are valid or not. This validation can either be done against a local database or an external system.
|
||||
|
||||
:::info
|
||||
Currently, Neko supports configuring only one authentication provider at a time. This means you must choose a single provider that best fits your deployment needs.
|
||||
:::
|
||||
|
||||
### Multi-User Provider {#member.multiuser}
|
||||
|
||||
This is the **default provider** that works exactly like the authentication used to work in v2 of neko.
|
||||
|
@ -86,21 +92,25 @@ This provider allows you to define two types of users: **regular** users and **a
|
|||
|
||||
Profiles for regular users and admins are optional, if not provided, the default profiles are used (see below in the example configuration).
|
||||
|
||||
```yaml title="config.yaml"
|
||||
member:
|
||||
provider: multiuser
|
||||
multiuser:
|
||||
# Password for admins, in plain text.
|
||||
admin_password: "adminPassword"
|
||||
# Profile fields as described above
|
||||
admin_profile:
|
||||
...
|
||||
# Password for regular users, in plain text.
|
||||
user_password: "userPassword"
|
||||
# Profile fields as described above
|
||||
user_profile:
|
||||
...
|
||||
```
|
||||
<ConfigurationTab options={{
|
||||
"member.provider": 'multiuser',
|
||||
"member.multiuser.admin_password": {
|
||||
defaultValue: "admin",
|
||||
description: "Password for admins, in plain text.",
|
||||
},
|
||||
"member.multiuser.admin_profile": {
|
||||
defaultValue: {},
|
||||
description: "Profile fields as described above",
|
||||
},
|
||||
"member.multiuser.user_password": {
|
||||
defaultValue: "neko",
|
||||
description: "Password for regular users, in plain text.",
|
||||
},
|
||||
"member.multiuser.user_profile": {
|
||||
defaultValue: {},
|
||||
description: "Profile fields as described above",
|
||||
},
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>See example configuration</summary>
|
||||
|
@ -152,8 +162,8 @@ For easier configuration, you can specify only passwords using environment varia
|
|||
|
||||
```yaml title="docker-compose.yaml"
|
||||
environment:
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "neko"
|
||||
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: "admin"
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "neko"
|
||||
```
|
||||
:::
|
||||
|
||||
|
@ -161,15 +171,17 @@ environment:
|
|||
|
||||
This provider reads the user's credentials from a file. It is useful for small deployments where you don't want to set up a database or LDAP server and still want to have persistent users.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
member:
|
||||
provider: file
|
||||
file:
|
||||
# Absolute path to the file containing the users and their passwords.
|
||||
path: /opt/neko/members.json
|
||||
# Whether the passwords are hashed using sha256 or not.
|
||||
hash: true
|
||||
```
|
||||
<ConfigurationTab options={{
|
||||
"member.provider": 'file',
|
||||
"member.file.path": {
|
||||
defaultValue: "/opt/neko/members.json",
|
||||
description: "Absolute path to the file containing the users and their passwords.",
|
||||
},
|
||||
"member.file.hash": {
|
||||
defaultValue: false,
|
||||
description: "Whether the passwords are hashed using sha256 or not.",
|
||||
},
|
||||
}} />
|
||||
|
||||
It allows you to store the user's credentials in a JSON file. The JSON structure maps user logins to their passwords and profiles.
|
||||
|
||||
|
@ -239,18 +251,13 @@ You can leave the file empty and add users later using the HTTP API.
|
|||
|
||||
This provider is the same as the file provider, but it saves the users only in memory. That means that the users are lost when the server is restarted. However, the default users can be set in the configuration file. The difference from the multi-user provider is that the users are not generated on demand and we define exactly which users with their passwords and profiles are allowed to log in. They cannot be logged in twice with the same username.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
member:
|
||||
provider: object
|
||||
object:
|
||||
# List of users with their passwords and profiles
|
||||
- username: "admin"
|
||||
# Password in plain text
|
||||
password: "admin"
|
||||
# Profile fields as described above
|
||||
profile:
|
||||
...
|
||||
```
|
||||
<ConfigurationTab options={{
|
||||
"member.provider": 'object',
|
||||
"member.object.users": {
|
||||
defaultValue: [],
|
||||
description: "List of users with their passwords and profiles.",
|
||||
},
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>See example configuration</summary>
|
||||
|
@ -296,10 +303,9 @@ member:
|
|||
|
||||
This provider allows any user to log in without any authentication. It is useful for testing and development purposes.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
member:
|
||||
provider: noauth
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={{
|
||||
"member.provider": 'noauth',
|
||||
}} comments={false} />
|
||||
|
||||
:::danger
|
||||
Do not use this provider in production environments unless you know exactly what you are doing. It allows anyone to log in and control neko as an admin.
|
||||
|
@ -311,10 +317,9 @@ Currently, there are only two providers available for sessions: **memory** and *
|
|||
|
||||
Simply by specifying the `session.file` to a file path, the session provider will store the sessions in a file. Otherwise, the sessions are stored in memory and are lost when the server is restarted.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
session:
|
||||
file: /opt/neko/sessions.json
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={{
|
||||
"session.file": '/opt/neko/sessions.json',
|
||||
}} comments={false} />
|
||||
|
||||
:::info
|
||||
In the future, we plan to add more session providers, such as Redis, PostgreSQL, etc. So the Configuration Options may change.
|
||||
|
@ -324,10 +329,9 @@ In the future, we plan to add more session providers, such as Redis, PostgreSQL,
|
|||
|
||||
The API User is a special user that is used to authenticate the HTTP API requests. It cannot connect to the room, but it can perform administrative tasks. The API User does not have a password but only a token that is used to authenticate the requests. If the token is not set, the API User is disabled.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
session:
|
||||
api_token: "apiToken"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={{
|
||||
"session.api_token": '<secret_token>',
|
||||
}} comments={false} />
|
||||
|
||||
:::tip
|
||||
This user is useful in some situations when the rooms are generated by the server and the token is guaranteed to be random every time a short-lived room is run. It is not a good idea to define this token for long-lived rooms, as it can be stolen and used to perform administrative tasks.
|
||||
|
@ -347,17 +351,15 @@ The authentication between the client and the server can be done using cookies o
|
|||
If you disable the cookies, the token will be sent to the client in the login response and saved in local storage. This is less secure than using cookies, as the token **can be stolen using XSS attacks**. Therefore, it is recommended to use cookies.
|
||||
:::
|
||||
|
||||
```yaml title="config.yaml"
|
||||
session:
|
||||
cookie:
|
||||
enabled: true
|
||||
name: "NEKO_SESSION"
|
||||
expiration: "24h"
|
||||
secure: true
|
||||
http_only: true
|
||||
domain: ""
|
||||
path: ""
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'session.cookie.enabled',
|
||||
'session.cookie.name',
|
||||
'session.cookie.expiration',
|
||||
'session.cookie.secure',
|
||||
'session.cookie.http_only',
|
||||
'session.cookie.domain',
|
||||
'session.cookie.path'
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="session.cookie.enabled" /> - Whether the cookies are enabled or not.
|
||||
- <Def id="session.cookie.name" /> - Name of the cookie used to store the session.
|
||||
|
|
|
@ -3,6 +3,8 @@ description: Configuration related to Gstreamer capture in Neko.
|
|||
---
|
||||
|
||||
import { Def, Opt } from '@site/src/components/Anchor';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
# Audio & Video Capture
|
||||
|
||||
|
@ -27,22 +29,19 @@ All video pipelines must use the same video codec (defined in the <Opt id="video
|
|||
|
||||
The Gstreamer pipeline is started when the first client requests the video stream and is stopped after the last client disconnects.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
video:
|
||||
display: "<display_name>"
|
||||
codec: "vp8" # default video codec
|
||||
ids: [ <pipeline_id1>, <pipeline_id2>, ... ]
|
||||
pipelines:
|
||||
<pipeline_id1>: <pipeline_config>
|
||||
<pipeline_id2>: <pipeline_config>
|
||||
...
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
"capture.video.display",
|
||||
"capture.video.codec",
|
||||
"capture.video.ids",
|
||||
"capture.video.pipeline",
|
||||
"capture.video.pipelines",
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="video.display" /> is the name of the [X display](https://www.x.org/wiki/) that you want to capture. If not specified, the environment variable `DISPLAY` will be used.
|
||||
- <Def id="video.codec" /> available codecs are `vp8`, `vp9`, `av1`, `h264`. [Supported video codecs](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/WebRTC_codecs#supported_video_codecs) are dependent on the WebRTC implementation used by the client, `vp8` and `h264` are supported by all WebRTC implementations.
|
||||
- <Def id="video.ids" /> is a list of pipeline ids that are defined in the <Opt id="video.pipelines" /> section. The first pipeline in the list will be the default pipeline.
|
||||
- <Def id="video.pipelines" /> is a dictionary of pipeline configurations. Each pipeline configuration is defined by a unique pipeline id. They can be defined in two ways: either by building the pipeline dynamically using [Expression-Driven Configuration](#video.expression) or by defining the pipeline using a [Gstreamer Pipeline Description](#video.pipeline).
|
||||
- <Def id="video.pipeline" /> is a shorthand for defining [Gstreamer pipeline description](#video.gst_pipeline) for a single pipeline. This is option is ignored if <Opt id="video.pipelines" /> is defined.
|
||||
- <Def id="video.pipelines" /> is a dictionary of pipeline configurations. Each pipeline configuration is defined by a unique pipeline id. They can be defined in two ways: either by building the pipeline dynamically using [Expression-Driven Configuration](#video.expression) or by defining the pipeline using a [Gstreamer Pipeline Description](#video.gst_pipeline).
|
||||
|
||||
### Expression-Driven Configuration {#video.expression}
|
||||
|
||||
|
@ -151,7 +150,7 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
</details>
|
||||
|
||||
### Gstreamer Pipeline Description {#video.pipeline}
|
||||
### Gstreamer Pipeline Description {#video.gst_pipeline}
|
||||
|
||||
If you want to define the pipeline using a [Gstreamer pipeline description](https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description), you can do so by setting the <Def id="video.pipelines.gst_pipeline" /> parameter.
|
||||
|
||||
|
@ -169,7 +168,7 @@ Since now you have to define the whole pipeline, you need to specify the src ele
|
|||
Your typical pipeline string would look like this:
|
||||
|
||||
```
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false ! <your_elements> ! appsink name=appsink"
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false ! <your_elements> ! appsink name=appsink
|
||||
```
|
||||
|
||||
See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentation/ximagesrc/index.html) and [appsink](https://gstreamer.freedesktop.org/documentation/app/appsink.html) for more information.
|
||||
|
@ -189,8 +188,9 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
|
|||
hq:
|
||||
gst_pipeline: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! videoconvert
|
||||
! videoconvert ! queue
|
||||
! vp8enc
|
||||
name=encoder
|
||||
target-bitrate=3072000
|
||||
cpu-used=4
|
||||
end-usage=cbr
|
||||
|
@ -207,8 +207,9 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
|
|||
lq:
|
||||
gst_pipeline: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! videoconvert
|
||||
! videoconvert ! queue
|
||||
! vp8enc
|
||||
name=encoder
|
||||
target-bitrate=1024000
|
||||
cpu-used=4
|
||||
end-usage=cbr
|
||||
|
@ -236,8 +237,9 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
|
|||
main:
|
||||
gst_pipeline: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! videoconvert
|
||||
! videoconvert ! queue
|
||||
! x264enc
|
||||
name=encoder
|
||||
threads=4
|
||||
bitrate=4096
|
||||
key-int-max=15
|
||||
|
@ -248,23 +250,62 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
|
|||
! appsink name=appsink
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="nvh264enc" label="NVENC H264 configuration">
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
video:
|
||||
codec: h264
|
||||
ids: [ main ]
|
||||
pipelines:
|
||||
main:
|
||||
gst_pipeline: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! videoconvert ! queue
|
||||
! video/x-raw,format=NV12
|
||||
! nvh264enc
|
||||
name=encoder
|
||||
preset=2
|
||||
gop-size=25
|
||||
spatial-aq=true
|
||||
temporal-aq=true
|
||||
bitrate=4096
|
||||
vbv-buffer-size=4096
|
||||
rc-mode=6
|
||||
! h264parse config-interval=-1
|
||||
! video/x-h264,stream-format=byte-stream
|
||||
! appsink name=appsink
|
||||
```
|
||||
|
||||
This configuration requires [Nvidia GPU](https://developer.nvidia.com/cuda-gpus) with [NVENC](https://developer.nvidia.com/nvidia-video-codec-sdk) support.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
Overview of available encoders for each codec is shown in the table below. The encoder name is used in the <Opt id="video.pipelines.gst_encoder" /> parameter. The parameters for each encoder are different and you can find the documentation for each encoder in the links below.
|
||||
|
||||
| codec | encoder | vaapi encoder | nvenc encoder |
|
||||
| ----- | ------- | ------------- | ------------- |
|
||||
| VP8 | [vp8enc](https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html?gi-language=c) | [vaapivp8enc](https://github.com/GStreamer/gstreamer-vaapi/blob/master/gst/vaapi/gstvaapiencode_vp8.c) | ? |
|
||||
| VP9 | [vp9enc](https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html?gi-language=c) | [vaapivp9enc](https://github.com/GStreamer/gstreamer-vaapi/blob/master/gst/vaapi/gstvaapiencode_vp9.c) | ? |
|
||||
| AV1 | [av1enc](https://gstreamer.freedesktop.org/documentation/aom/av1enc.html?gi-language=c) | ? | [nvav1enc](https://gstreamer.freedesktop.org/documentation/nvcodec/nvav1enc.html?gi-language=c) |
|
||||
| H264 | [x264enc](https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c) | [vaapih264enc](https://gstreamer.freedesktop.org/documentation/vaapi/vaapih264enc.html?gi-language=c) | [nvh264enc](https://gstreamer.freedesktop.org/documentation/nvcodec/nvh264enc.html?gi-language=c) |
|
||||
| H265 | [x265enc](https://gstreamer.freedesktop.org/documentation/x265/index.html?gi-language=c) | [vaapih265enc](https://gstreamer.freedesktop.org/documentation/vaapi/vaapih265enc.html?gi-language=c) | [nvh265enc](https://gstreamer.freedesktop.org/documentation/nvcodec/nvh265enc.html?gi-language=c) |
|
||||
|
||||
|
||||
## WebRTC Audio {#audio}
|
||||
|
||||
Only one audio pipeline can be defined in neko. The audio pipeline is used to capture and encode audio, similar to the video pipeline. The encoded audio is then sent to the client using WebRTC.
|
||||
|
||||
The Gstreamer pipeline is started when the first client requests the video stream and is stopped after the last client disconnects.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
audio:
|
||||
device: "audio_output.monitor" # default audio device
|
||||
codec: "opus" # default audio codec
|
||||
pipeline: "<gstreamer_pipeline>"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
"capture.audio.device",
|
||||
"capture.audio.codec",
|
||||
"capture.audio.pipeline",
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="audio.device" /> is the name of the [pulseaudio device](https://wiki.archlinux.org/title/PulseAudio/Examples) that you want to capture. If not specified, the default audio device will be used.
|
||||
- <Def id="audio.codec" /> available codecs are `opus`, `g722`, `pcmu`, `pcma`. [Supported audio codecs](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/WebRTC_codecs#supported_audio_codecs) are dependent on the WebRTC implementation used by the client, `opus` is supported by all WebRTC implementations.
|
||||
|
@ -293,23 +334,21 @@ Neko allows you to broadcast out-of-the-box the display and audio capture to a t
|
|||
|
||||
The Gstreamer pipeline is started when the broadcast is started and is stopped when the broadcast is stopped regardless of the clients connected.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
broadcast:
|
||||
audio_bitrate: 128 # in KB/s
|
||||
video_bitrate: 4096 # in KB/s
|
||||
preset: "veryfast"
|
||||
pipeline: "<gstreamer_pipeline>"
|
||||
url: "rtmp://<server>/<application>/<stream_key>"
|
||||
autostart: true
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
"capture.broadcast.audio_bitrate",
|
||||
"capture.broadcast.video_bitrate",
|
||||
"capture.broadcast.preset",
|
||||
"capture.broadcast.pipeline",
|
||||
"capture.broadcast.url",
|
||||
"capture.broadcast.autostart",
|
||||
]} comments={false} />
|
||||
|
||||
The default encoder uses `h264` for video and `aac` for audio, muxed in the `flv` container and sent over the `rtmp` protocol. You can change the encoder settings by setting a custom Gstreamer pipeline description in the <Opt id="broadcast.pipeline" /> parameter.
|
||||
|
||||
- <Def id="broadcast.audio_bitrate" /> and <Def id="broadcast.video_bitrate" /> are the bitrate settings for the default audio and video encoders expressed in kilobits per second.
|
||||
- <Def id="broadcast.preset" /> is the encoding speed preset for the default video encoder. See available presets [here](https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c#GstX264EncPreset).
|
||||
- <Def id="broadcast.pipeline" /> when set, encoder settings above are ignored and the custom Gstreamer pipeline description is used. In the pipeline, you can use `{display}`, `{device}` and `{url}` as placeholders for the X display name, pulseaudio audio device name, and broadcast URL respectively.
|
||||
- <Def id="broadcast.url" /> is the URL of the RTMP server where the broadcast will be sent. This can be set later using the API if the URL is not known at the time of configuration or is expected to change.
|
||||
- <Def id="broadcast.url" /> is the URL of the RTMP server where the broadcast will be sent e.g. `rtmp://<server>/<application>/<stream_key>`. This can be set later using the API if the URL is not known at the time of configuration or is expected to change.
|
||||
- <Def id="broadcast.autostart" /> is a boolean value that determines whether the broadcast should start automatically when neko starts, works only if the URL is set.
|
||||
|
||||
<details>
|
||||
|
@ -380,14 +419,12 @@ This is a fallback mechanism and should not be used as a primary video stream be
|
|||
|
||||
The Gstreamer pipeline is started in the background when the first client requests the screencast and is stopped after a period of inactivity.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
screencast:
|
||||
enabled: true
|
||||
rate: "10/1"
|
||||
quality: 60
|
||||
pipeline: "<gstreamer_pipeline>"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
"capture.screencast.enabled",
|
||||
"capture.screencast.rate",
|
||||
"capture.screencast.quality",
|
||||
"capture.screencast.pipeline",
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="screencast.enabled" /> is a boolean value that determines whether the screencast is enabled or not.
|
||||
- <Def id="screencast.rate" /> is the framerate of the screencast. It is expressed as a fraction of frames per second, for example, `10/1` means 10 frames per second.
|
||||
|
@ -422,14 +459,12 @@ Neko allows you to capture the webcam on the client machine and send it to the s
|
|||
|
||||
The Gstreamer pipeline is started when the client shares their webcam and is stopped when the client stops sharing the webcam. Maximum one webcam pipeline can be active at a time.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
webcam:
|
||||
enabled: true
|
||||
device: "/dev/video0" # default webcam device
|
||||
width: 640
|
||||
height: 480
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
"capture.webcam.enabled",
|
||||
"capture.webcam.device",
|
||||
"capture.webcam.width",
|
||||
"capture.webcam.height",
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="webcam.enabled" /> is a boolean value that determines whether the webcam capture is enabled or not.
|
||||
- <Def id="webcam.device" /> is the name of the [video4linux device](https://www.kernel.org/doc/html/v4.12/media/v4l-drivers/index.html) that will be used as a virtual webcam.
|
||||
|
@ -463,12 +498,10 @@ Neko allows you to capture the microphone on the client machine and send it to t
|
|||
|
||||
The Gstreamer pipeline is started when the client shares their microphone and is stopped when the client stops sharing the microphone. Maximum one microphone pipeline can be active at a time.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
microphone:
|
||||
enabled: true
|
||||
device: "audio_input"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
"capture.microphone.enabled",
|
||||
"capture.microphone.device",
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="microphone.enabled" /> is a boolean value that determines whether the microphone capture is enabled or not.
|
||||
- <Def id="microphone.device" /> is the name of the [pulseaudio device](https://wiki.archlinux.org/title/PulseAudio/Examples) that will be used as a virtual microphone.
|
||||
|
|
|
@ -3,6 +3,8 @@ description: Configuration related to the Desktop Environment in Neko.
|
|||
---
|
||||
|
||||
import { Def, Opt } from '@site/src/components/Anchor';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
# Desktop Environment
|
||||
|
||||
|
@ -10,18 +12,15 @@ This section describes how to configure the desktop environment inside neko.
|
|||
|
||||
Neko uses the [X Server](https://www.x.org/archive/X11R7.6/doc/man/man1/Xserver.1.xhtml) as the display server with [Openbox](http://openbox.org/wiki/Main_Page) as the default window manager. For audio, [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) is used.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
desktop:
|
||||
display: "<display>"
|
||||
screen: "1280x720@30" # default
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'desktop.display',
|
||||
'desktop.screen'
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="display" /> refers to the X server that is running on the system. If it is not specified, the environment variable `DISPLAY` is used. The same display is referred to in the [Capture](capture#video.display) configuration to capture the screen. In most cases, we want to use the same display for both.
|
||||
- <Def id="screen" /> refers to the screen resolution and refresh rate. The format is `<width>x<height>@<refresh rate>`. If not specified, the default is `1280x720@30`.
|
||||
|
||||
:::tip
|
||||
You can specify the screen resolution using the environment variable `NEKO_DESKTOP_SCREEN`.
|
||||
|
||||
Admin can change the resolution in the GUI.
|
||||
:::
|
||||
|
||||
|
@ -33,12 +32,10 @@ Neko uses the [XTEST Extension Library](https://www.x.org/releases/X11R7.7/doc/l
|
|||
Currently, only touchscreens are supported through the custom driver.
|
||||
:::
|
||||
|
||||
```yaml title="config.yaml"
|
||||
desktop:
|
||||
input:
|
||||
enabled: true # default
|
||||
socket: "/tmp/xf86-input-neko.sock" # default
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'desktop.input.enabled',
|
||||
'desktop.input.socket'
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="input.enabled" /> enables the input device support. If not specified, the default is `false`.
|
||||
- <Def id="input.socket" /> refers to the socket file that the custom driver creates. If not specified, the default is `/tmp/xf86-input-neko.sock`.
|
||||
|
@ -51,10 +48,9 @@ When using Docker, the custom driver is already included in the image and the so
|
|||
|
||||
Most of the time, only a single application is used in the minimal desktop environment without any taskbar or desktop icons. It could happen that the user accidentally minimizes the application and then it is not possible to restore it. To prevent this, we can use the `unminimize` feature that simply listens for the minimize event and restores the window back to the original state.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
desktop:
|
||||
unminimize: true # default
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'desktop.unminimize'
|
||||
]} comments={false} />
|
||||
|
||||
## Upload Drop {#upload_drop}
|
||||
|
||||
|
@ -62,10 +58,9 @@ The upload drop is a feature that allows the user to upload files to the applica
|
|||
|
||||
The current approach is to catch the drag and drop events on the client side, upload them to the server along with the coordinates of the drop event, and then open an invisible overlay window on the server that has set the file path to the uploaded file and allows it to be dragged and dropped into the application. Then the mouse events are simulated to drag the file from the overlay window to the application window.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
desktop:
|
||||
upload_drop: true # default
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'desktop.upload_drop'
|
||||
]} comments={false} />
|
||||
|
||||
## File Chooser Dialog {#file_chooser_dialog}
|
||||
|
||||
|
@ -77,7 +72,6 @@ The file chooser dialog is a feature that allows handling the file chooser dialo
|
|||
|
||||
The current approach is to put the file chooser dialog in the background as soon as it is displayed, prompt the user to upload the file, and then select this file in the file chooser dialog by simulating the keyboard events to navigate to the file and press the open button. **This is very error-prone and may not work as expected.**
|
||||
|
||||
```yaml title="config.yaml"
|
||||
desktop:
|
||||
file_chooser_dialog: false # default
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'desktop.file_chooser_dialog'
|
||||
]} comments={false} />
|
||||
|
|
|
@ -177,11 +177,20 @@
|
|||
"key": [
|
||||
"capture",
|
||||
"video",
|
||||
"pipelines"
|
||||
"pipeline"
|
||||
],
|
||||
"type": "string",
|
||||
"defaultValue": "[]",
|
||||
"description": "pipelines config in JSON used for video streaming"
|
||||
"description": "shortcut for configuring only a single gstreamer pipeline, ignored if pipelines is set"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"capture",
|
||||
"video",
|
||||
"pipelines"
|
||||
],
|
||||
"type": "object",
|
||||
"defaultValue": {},
|
||||
"description": "pipelines config used for video streaming"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -295,7 +304,7 @@
|
|||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "true",
|
||||
"description": "member file provider: whether to hash passwords using sha256 (recommended)"
|
||||
"description": "member file provider: whether the passwords are hashed using sha256 or not (recommended)"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -304,7 +313,7 @@
|
|||
"path"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "member file provider: storage path"
|
||||
"description": "member file provider: path to the file containing the users and their passwords"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -314,7 +323,7 @@
|
|||
],
|
||||
"type": "string",
|
||||
"defaultValue": "admin",
|
||||
"description": "member multiuser provider: admin password"
|
||||
"description": "member multiuser provider: password for admin users"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -322,9 +331,9 @@
|
|||
"multiuser",
|
||||
"admin_profile"
|
||||
],
|
||||
"type": "string",
|
||||
"defaultValue": "{}",
|
||||
"description": "member multiuser provider: admin profile in JSON format"
|
||||
"type": "object",
|
||||
"defaultValue": {},
|
||||
"description": "member multiuser provider: profile template for admin users"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -334,7 +343,7 @@
|
|||
],
|
||||
"type": "string",
|
||||
"defaultValue": "neko",
|
||||
"description": "member multiuser provider: user password"
|
||||
"description": "member multiuser provider: password for regular users"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -342,9 +351,9 @@
|
|||
"multiuser",
|
||||
"user_profile"
|
||||
],
|
||||
"type": "string",
|
||||
"defaultValue": "{}",
|
||||
"description": "member multiuser provider: user profile in JSON format"
|
||||
"type": "object",
|
||||
"defaultValue": {},
|
||||
"description": "member multiuser provider: profile template for regular users"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -352,9 +361,9 @@
|
|||
"object",
|
||||
"users"
|
||||
],
|
||||
"type": "string",
|
||||
"defaultValue": "[]",
|
||||
"description": "member object provider: users in JSON format"
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"description": "member object provider: list of users with their passwords and profiles"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -363,7 +372,7 @@
|
|||
],
|
||||
"type": "string",
|
||||
"defaultValue": "multiuser",
|
||||
"description": "choose member provider"
|
||||
"description": "selected member provider"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -758,9 +767,9 @@
|
|||
"iceservers",
|
||||
"backend"
|
||||
],
|
||||
"type": "urls",
|
||||
"defaultValue": "[]",
|
||||
"description": "Backend only STUN and TURN servers in JSON format with urls, `username` and `credential` keys"
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"description": "STUN and TURN servers used by the backend"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -768,9 +777,9 @@
|
|||
"iceservers",
|
||||
"frontend"
|
||||
],
|
||||
"type": "urls",
|
||||
"defaultValue": "[]",
|
||||
"description": "Frontend only STUN and TURN servers in JSON format with urls, `username` and `credential` keys"
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"description": "STUN and TURN servers used by the frontend"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
--capture.video.codec string video codec to be used (default "vp8")
|
||||
--capture.video.display string X display to capture
|
||||
--capture.video.ids strings ordered list of video ids
|
||||
--capture.video.pipelines string pipelines config in JSON used for video streaming (default "[]")
|
||||
--capture.video.pipeline string shortcut for configuring only a single gstreamer pipeline, ignored if pipelines is set
|
||||
--capture.video.pipelines string pipelines config used for video streaming (default "{}")
|
||||
--capture.webcam.device string v4l2sink device used for webcam (default "/dev/video0")
|
||||
--capture.webcam.enabled enable webcam stream
|
||||
--capture.webcam.height int webcam stream height (default 720)
|
||||
|
@ -28,14 +29,14 @@
|
|||
--desktop.screen string default screen size and framerate (default "1280x720@30")
|
||||
--desktop.unminimize automatically unminimize window when it is minimized (default true)
|
||||
--desktop.upload_drop whether drop upload is enabled (default true)
|
||||
--member.file.hash member file provider: whether to hash passwords using sha256 (recommended) (default true)
|
||||
--member.file.path string member file provider: storage path
|
||||
--member.multiuser.admin_password string member multiuser provider: admin password (default "admin")
|
||||
--member.multiuser.admin_profile string member multiuser provider: admin profile in JSON format (default "{}")
|
||||
--member.multiuser.user_password string member multiuser provider: user password (default "neko")
|
||||
--member.multiuser.user_profile string member multiuser provider: user profile in JSON format (default "{}")
|
||||
--member.object.users string member object provider: users in JSON format (default "[]")
|
||||
--member.provider string choose member provider (default "multiuser")
|
||||
--member.file.hash member file provider: whether the passwords are hashed using sha256 or not (recommended) (default true)
|
||||
--member.file.path string member file provider: path to the file containing the users and their passwords
|
||||
--member.multiuser.admin_password string member multiuser provider: password for admin users (default "admin")
|
||||
--member.multiuser.admin_profile string member multiuser provider: profile template for admin users (default "{}")
|
||||
--member.multiuser.user_password string member multiuser provider: password for regular users (default "neko")
|
||||
--member.multiuser.user_profile string member multiuser provider: profile template for regular users (default "{}")
|
||||
--member.object.users string member object provider: list of users with their passwords and profiles (default "[]")
|
||||
--member.provider string selected member provider (default "multiuser")
|
||||
--plugins.dir string path to neko plugins to load (default "./bin/plugins")
|
||||
--plugins.enabled load plugins in runtime
|
||||
--plugins.required if true, neko will exit if there is an error when loading a plugin
|
||||
|
@ -78,8 +79,8 @@
|
|||
--webrtc.estimator.unstable_duration duration how long to wait for stalled connection (neutral trend with low bandwidth) before downgrading (default 6s)
|
||||
--webrtc.estimator.upgrade_backoff duration how long to wait before upgrading again after previous upgrade (default 5s)
|
||||
--webrtc.icelite configures whether or not the ICE agent should be a lite agent
|
||||
--webrtc.iceservers.backend urls Backend only STUN and TURN servers in JSON format with urls, `username` and `credential` keys (default "[]")
|
||||
--webrtc.iceservers.frontend urls Frontend only STUN and TURN servers in JSON format with urls, `username` and `credential` keys (default "[]")
|
||||
--webrtc.iceservers.backend string STUN and TURN servers used by the backend (default "[]")
|
||||
--webrtc.iceservers.frontend string STUN and TURN servers used by the frontend (default "[]")
|
||||
--webrtc.icetrickle configures whether cadidates should be sent asynchronously using Trickle ICE (default true)
|
||||
--webrtc.ip_retrieval_url string URL address used for retrieval of the external IP address (default "https://checkip.amazonaws.com")
|
||||
--webrtc.nat1to1 strings sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used
|
||||
|
|
|
@ -3,17 +3,18 @@ description: Configuration related to the Neko plugins.
|
|||
---
|
||||
|
||||
import { Def, Opt } from '@site/src/components/Anchor';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
# Plugins Configuration
|
||||
|
||||
Neko allows you to extend its functionality by using [plugins](https://pkg.go.dev/plugin). Go plugins come with a lot of benefits as well as some limitations. The main advantage is that you can extend the functionality of the application without recompiling the main application. But the main limitation is that you need to use the same Go version and all dependencies with the same version as the main application.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
plugins:
|
||||
enabled: true
|
||||
required: true
|
||||
dir: "./bin/plugins"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'plugins.enabled',
|
||||
'plugins.required',
|
||||
'plugins.dir',
|
||||
]} comments={false} />
|
||||
|
||||
- <Def id="enabled" /> enables the plugin support. If set to `false`, the plugins are not loaded.
|
||||
- <Def id="required" /> makes the plugin loading mandatory, meaning that if a plugin fails to load, the application will not start.
|
||||
|
@ -29,10 +30,9 @@ There exist a few pre-loaded internal plugins that are shipped with Neko:
|
|||
|
||||
The chat plugin is a simple pre-loaded internal plugin that allows you to chat with other users in the same session. The chat messages are sent to the server and then broadcasted to all users in the same session.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
chat:
|
||||
enabled: true
|
||||
```
|
||||
<ConfigurationTab options={{
|
||||
'chat.enabled': true
|
||||
}} />
|
||||
|
||||
- <Def id="chat.enabled" /> enables the chat support. If set to `false`, the chat is disabled.
|
||||
|
||||
|
@ -51,12 +51,15 @@ plugins:
|
|||
|
||||
The file transfer plugin is a simple pre-loaded internal plugin that allows you to transfer files between the client and the server. The files are uploaded to the server and then downloaded by the client.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
filetransfer:
|
||||
enabled: true
|
||||
dir: "./uploads"
|
||||
refresh_interval: 30s
|
||||
```
|
||||
|
||||
<ConfigurationTab options={{
|
||||
'filetransfer.enabled': true,
|
||||
'filetransfer.dir': './uploads',
|
||||
'filetransfer.refresh_interval': {
|
||||
type: 'duration',
|
||||
defaultValue: '30s',
|
||||
},
|
||||
}} />
|
||||
|
||||
- <Def id="filetransfer.enabled" /> enables the file transfer support. If set to `false`, the file transfer is disabled.
|
||||
- <Def id="filetransfer.dir" /> refers to the directory where the files are stored.
|
||||
|
@ -70,4 +73,3 @@ plugins:
|
|||
```
|
||||
|
||||
- `filetransfer.enabled` in the room settings context controls whether the file transfer is enabled for any user in the room, and in the user's profile context controls whether the user can transfer files.
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ description: Configuration related to the WebRTC and Networking in Neko.
|
|||
---
|
||||
|
||||
import { Def, Opt } from '@site/src/components/Anchor';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
# WebRTC Configuration
|
||||
|
||||
|
@ -18,19 +20,17 @@ ICE, which stands for Interactive Connectivity Establishment, is a protocol used
|
|||
|
||||
ICE Trickle is a feature that allows ICE candidates to be sent as they are discovered, rather than waiting for all candidates to be discovered before sending them. It means that the ICE connection can be established faster as the server can start connecting to the client as soon as it has a few ICE candidates and doesn't have to wait for all of them to be discovered.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
icetrickle: false
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'webrtc.icetrickle'
|
||||
]} comments={false} />
|
||||
|
||||
### ICE Lite {#icelite}
|
||||
|
||||
ICE Lite is a minimal implementation of the ICE protocol intended for servers running on a public IP address. It is not enabled by default to allow more complex ICE configurations out of the box.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
icelite: false
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'webrtc.icelite'
|
||||
]} comments={false} />
|
||||
|
||||
:::info
|
||||
When using ICE Servers, ICE Lite must be disabled.
|
||||
|
@ -100,16 +100,10 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
The ICE servers are divided into two groups:
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
iceservers:
|
||||
frontend:
|
||||
# List of ICE Server configurations as described above
|
||||
- urls: "stun:stun.l.google.com:19302"
|
||||
backend:
|
||||
# List of ICE Server configurations as described above
|
||||
- urls: "stun:stun.l.google.com:19302"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'webrtc.iceservers.frontend',
|
||||
'webrtc.iceservers.backend'
|
||||
]} />
|
||||
|
||||
- <Def id="iceservers.frontend" /> - ICE servers that are sent to the client and used to establish a connection between the client and the server.
|
||||
- <Def id="iceservers.backend" /> - ICE servers that are used by the server to gather ICE candidates. They might contain private IP addresses or other sensitive information that should not be sent to the client.
|
||||
|
@ -162,15 +156,14 @@ There exist two types of connections:
|
|||
|
||||
The ephemeral UDP port range can be configured using the following configuration:
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
epr: "59000-59100"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={{
|
||||
'webrtc.epr': "59000-59100"
|
||||
}} comments={false} />
|
||||
|
||||
The range `59000-59100` contains 101 ports, which should be open on the server's firewall. The server uses these ports to establish a connection with the client. You can specify a different range of ports if needed, with fewer or more ports, depending on the number of simultaneous connections you expect.
|
||||
|
||||
:::tip
|
||||
You can specify the ephemeral UDP port range as an environment variable in the `docker-compose.yaml` file using the `NEKO_WEBRTC_EPR` environment variable. When using docker, make sure to expose the ports in the `docker-compose.yaml`.
|
||||
:::tip Make sure
|
||||
When specifying the ephemeral UDP port range in `docker-compose.yaml`, make sure to use the same range for ports **as UDP**.
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
environment:
|
||||
|
@ -186,19 +179,18 @@ It is important to expose the same ports to the host machine, without any remapp
|
|||
|
||||
The UDP/TCP multiplexing port can be configured using the following configuration:
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
udpmux: 59000
|
||||
tcpmux: 59000
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={{
|
||||
'webrtc.udpmux': 59000,
|
||||
'webrtc.tcpmux': 59000
|
||||
}} comments={false} />
|
||||
|
||||
- <Def id="udpmux" /> - The port used for UDP connections.
|
||||
- <Def id="tcpmux" /> - The port used for TCP connections.
|
||||
|
||||
The server uses only port `59000` for both UDP and TCP connections. This port should be open on the server's firewall. You can specify a different port if needed, or specify only one of the two protocols. UDP is generally better for latency, but some networks block UDP so it is good to have TCP available as a fallback.
|
||||
|
||||
:::tip
|
||||
You can specify the UDP/TCP multiplexing port as an environment variable in the `docker-compose.yaml` file using the `NEKO_WEBRTC_TCPMUX` and `NEKO_WEBRTC_UDPMUX` environment variables. When using docker, make sure to expose the ports in the `docker-compose.yaml`.
|
||||
:::tip Make sure
|
||||
When specifying the UDP/TCP multiplexing port in `docker-compose.yaml`, make sure to correctly specify the protocol in the ports section.
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
environment:
|
||||
|
@ -217,42 +209,20 @@ It is important to expose the same ports to the host machine, without any remapp
|
|||
The server IP address is sent to the client in ICE candidates so that the client can establish a connection with the server. By default, the server IP address is automatically resolved by the server to the public IP address of the server. If the server is behind a NAT, you want to specify a different IP address or use neko only in a local network, you can specify the server IP address manually.
|
||||
|
||||
#### NAT 1-to-1 {#nat1to1}
|
||||
<ConfigurationTab options={configOptions} filter={{
|
||||
'webrtc.nat1to1': '10.10.0.5'
|
||||
}} comments={false} />
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
nat1to1:
|
||||
# IPv4 address of the server
|
||||
- 10.10.0.5
|
||||
# IPv6 address of the server
|
||||
- 2001:db8:85a3::8a2e:370:7334
|
||||
```
|
||||
|
||||
Currently, only one IPv4 and one IPv6 address can be specified. Therefore if you want to access your instance from both local and public networks, your router must support [NAT loopback (hairpinning)](https://en.wikipedia.org/wiki/Network_address_translation#NAT_hairpinning).
|
||||
|
||||
:::tip
|
||||
You can specify the server IP address as an environment variable in the `docker-compose.yaml` file using the `NEKO_WEBRTC_NAT1TO1` environment variable.
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
environment:
|
||||
NEKO_WEBRTC_NAT1TO1: "10.10.0.5"
|
||||
```
|
||||
|
||||
If you want to specify also an IPv6 address, use whitespace to separate the addresses.
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
environment:
|
||||
NEKO_WEBRTC_NAT1TO1: "10.10.0.5 2001:db8:85a3::8a2e:370:7334"
|
||||
```
|
||||
:::
|
||||
Currently, only one address can be specified. Therefore if you want to access your instance from both local and public networks, your router must support [NAT loopback (hairpinning)](https://en.wikipedia.org/wiki/Network_address_translation#NAT_hairpinning).
|
||||
|
||||
#### IP Retrieval URL {#ip_retrieval_url}
|
||||
|
||||
If you do not specify the server IP address, the server will try to resolve the public IP address of the server automatically.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
ip_retrieval_url: "https://checkip.amazonaws.com"
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'webrtc.ip_retrieval_url'
|
||||
]} comments={false} />
|
||||
|
||||
The server will send an HTTP GET request to the specified URL to retrieve the public IP address of the server.
|
||||
|
||||
## Bandwidth Estimator {#estimator}
|
||||
|
@ -263,29 +233,6 @@ The bandwidth estimator is an experimental feature and might not work as expecte
|
|||
|
||||
The bandwidth estimator is a feature that allows the server to estimate the available bandwidth between the client and the server. It is used to switch between different video qualities based on the available bandwidth. The bandwidth estimator is disabled by default.
|
||||
|
||||
```yaml title="config.yaml"
|
||||
webrtc:
|
||||
estimator:
|
||||
# Whether to enable the bandwidth estimator
|
||||
enabled: false
|
||||
# Whether the bandwidth estimator is passive - only used for logging and not for actual decisions
|
||||
passive: false
|
||||
# Enable debug logging for the bandwidth estimator (will print the current state and decisions)
|
||||
debug: false
|
||||
# Initial bitrate for the bandwidth estimator to start with (in bps)
|
||||
initial_bitrate: 1000000
|
||||
# How often to read and process bandwidth estimation reports
|
||||
read_interval: "2s"
|
||||
# How long to wait for a stable connection (upward or neutral trend) before upgrading
|
||||
stable_duration: "12s"
|
||||
# How long to wait for a stalled connection (neutral trend with low bandwidth) before downgrading
|
||||
unstable_duration: "6s"
|
||||
# How long to wait for stalled bandwidth estimation before downgrading
|
||||
stalled_duration: "24s"
|
||||
# How long to wait before downgrading again after the previous downgrade
|
||||
downgrade_backoff: "10s"
|
||||
# How long to wait before upgrading again after the previous upgrade
|
||||
upgrade_backoff: "5s"
|
||||
# How much bigger the difference between estimated and stream bitrate must be to trigger a change
|
||||
diff_threshold: 0.15
|
||||
```
|
||||
<ConfigurationTab options={configOptions} filter={[
|
||||
'webrtc.estimator'
|
||||
]} comments={true} />
|
||||
|
|
|
@ -158,6 +158,20 @@ By default, the browsers in Neko do not allow installing extensions except for t
|
|||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>How to find the extension ID?</summary>
|
||||
|
||||
Extension IDs for Firefox are not available in the URL like in Chrome. You can find the extension ID by navigating to the `about:debugging#/runtime/this-firefox` page and clicking on the extension you want to install. The extension ID will be displayed in the URL.
|
||||
|
||||
Another way is to find the extension on the [Official Add-ons Webpage](https://addons.mozilla.org/en-US/firefox/), then open DevTools (<code>F12</code>) and go to the `Console` tab. Enter the following command:
|
||||
|
||||
```javascript
|
||||
Object.keys(JSON.parse(document.getElementById('redux-store-state').textContent).addons.byGUID)[0]
|
||||
```
|
||||
|
||||
This will return the ID of the first extension on the page.
|
||||
</details>
|
||||
|
||||
### Chromium-based Browsers {#chromium-based}
|
||||
|
||||
The full configuration options for the Chromium-based policy JSON file can be found in the [Chrome Enterprise](https://chromeenterprise.google/policies) documentation.
|
||||
|
|
|
@ -36,6 +36,7 @@ This project uses a monorepo structure with the following directories:
|
|||
|
||||
- `webpage/`: Webpage code for neko.m1k1o.net, written in [TypeScript](https://www.typescriptlang.org/) and [Docusaurus](https://docusaurus.io/), deployed on [GitHub Pages](https://pages.github.com/).
|
||||
- `webpage/docs/`: Documentation for the neko project, including this README file.
|
||||
- `webpage/scripts/`: Helper scripts for generating configuration and OpenAPI docs.
|
||||
- `webpage/src/`: Source code, components, and styles for the webpage.
|
||||
- `webpage/static/`: Static files for the webpage, such as images and icons.
|
||||
- `webpage/versioned_*`: Versioned documentation files for the neko project, generated by [Docusaurus](https://docusaurus.io/).
|
|
@ -12,24 +12,53 @@ The base image is available as multi-arch image at [`ghcr.io/m1k1o/neko/base`](h
|
|||
|
||||
## Naming Convention {#naming}
|
||||
|
||||
Neko Docker images are available on [GitHub Container Registry (GHCR)](https://github.com/m1k1o?tab=packages&repo_name=neko). The naming convention for Neko Docker images is as follows:
|
||||
Neko images are available on two public registries. The [GitHub Container Registry (GHCR)](#ghcr.io) hosts stable releases with all flavors and architectures. The latest development version of the Neko image for the AMD64 architecture is available on [Docker Hub](#docker.io).
|
||||
|
||||
:::info
|
||||
You should always prefer the GHCR registry, as it supports flavors and specific versions, unless you want to test the latest development version.
|
||||
:::
|
||||
|
||||
### GitHub Container Registry (GHCR) {#ghcr.io}
|
||||
|
||||
Neko Docker images are available on the [GitHub Container Registry (GHCR)](https://github.com/m1k1o?tab=packages&repo_name=neko). The naming convention for Neko Docker images is as follows:
|
||||
|
||||
```
|
||||
ghcr.io/m1k1o/neko/[<flavor>-]<application>:<version>
|
||||
```
|
||||
|
||||
- `<flavor>` is the optional flavor of the image, see [Available Flavors](#flavors) for more information.
|
||||
- `<application>` is the application name or base image, see [Available Applications](#apps) for more information.
|
||||
- `<version>` is the [semantic version](https://semver.org/) of the image from the [GitHub tags](https://github.com/m1k1o/neko/tags). There is always a `latest` tag available.
|
||||
- `<flavor>` is the optional flavor of the image. See [Available Flavors](#flavors) for more information.
|
||||
- `<application>` is the application name or base image. See [Available Applications](#apps) for more information.
|
||||
- `<version>` is the version of the image. See [Versioning](#ghcr.io-versioning) for more information.
|
||||
|
||||
An alternative registry is also available on [Docker Hub](https://hub.docker.com/r/m1k1o/neko), however, only images without flavor and with the latest version are available there.
|
||||
#### Versioning scheme {#ghcr.io-versioning}
|
||||
|
||||
The versioning scheme follows the [Semantic Versioning 2.0.0](https://semver.org/) specification. The following tags are available for each image:
|
||||
|
||||
- `latest` - Points to the most recent stable release.
|
||||
- `MAJOR` - Tracks the latest release within the specified major version.
|
||||
- `MAJOR.MINOR` - Tracks the latest release within the specified major and minor version.
|
||||
- `MAJOR.MINOR.PATCH` - Refers to a specific release.
|
||||
|
||||
For example:
|
||||
- `ghcr.io/m1k1o/neko/firefox:latest` - Latest stable version.
|
||||
- `ghcr.io/m1k1o/neko/firefox:3` - Latest release in the 3.x.x series.
|
||||
- `ghcr.io/m1k1o/neko/firefox:3.0` - Latest release in the 3.0.x series.
|
||||
- `ghcr.io/m1k1o/neko/firefox:3.0.0` - Specific version 3.0.0.
|
||||
|
||||
A full list of published versions can be found in the [GitHub tags](https://github.com/m1k1o/neko/tags).
|
||||
|
||||
### Docker Hub {#docker.io}
|
||||
|
||||
An alternative registry is available on [Docker Hub](https://hub.docker.com/r/m1k1o/neko). This registry hosts images built from the latest code in the [master branch](https://github.com/m1k1o/neko/tree/master). However, it only includes images without flavors and supports the AMD64 architecture. The naming convention for these images is as follows:
|
||||
|
||||
```
|
||||
m1k1o/neko:<application>
|
||||
```
|
||||
|
||||
- `<application>` is the application name or base image. See [Available Applications](#apps) for more information.
|
||||
|
||||
:::info
|
||||
You should always prefer the GHCR registry with the ability to use flavors and specific versions.
|
||||
`m1k1o/neko:latest` is an alias for `m1k1o/neko:firefox` due to historical reasons. It is recommended to use the `ghcr.io/m1k1o/neko/firefox:latest` image instead.
|
||||
:::
|
||||
|
||||
## Available Applications {#apps}
|
||||
|
@ -46,6 +75,10 @@ In comparison to Chromium-based browsers, Firefox-based browsers do not require
|
|||
| <AppIcon id="tor-browser" /> | [Tor Browser](https://www.torproject.org/) <br /> A browser designed to access the Tor network for enhanced privacy. | [`ghcr.io/m1k1o/neko/tor-browser`](https://ghcr.io/m1k1o/neko/tor-browser) |
|
||||
| <AppIcon id="waterfox" /> | [Waterfox](https://www.waterfox.net/) <br /> A privacy-focused browser based on Firefox. | [`ghcr.io/m1k1o/neko/waterfox`](https://ghcr.io/m1k1o/neko/waterfox) |
|
||||
|
||||
:::warning
|
||||
**Waterfox** is currently not built automatically, because Cloudflare blocks the download and therefore github actions are failing. You can build it manually to get the latest version.
|
||||
:::
|
||||
|
||||
Check the [Firefox-based browsers customization guide](/docs/v3/customization/browsers#firefox-based) for more information on how to customize Firefox-based browsers (configuring profile, installing extensions, etc.).
|
||||
|
||||
### Chromium-based browsers {#chromium-based-browsers}
|
||||
|
@ -162,42 +195,6 @@ docker run \
|
|||
See [neko-apps](https://github.com/m1k1o/neko-apps) repository for more applications.
|
||||
:::
|
||||
|
||||
|
||||
## Supported Architectures {#arch}
|
||||
|
||||
Neko Docker images are built with docker buildx and are available for multiple architectures. The following architectures are supported by the base image:
|
||||
|
||||
- `linux/amd64` - 64-bit Intel/AMD architecture (most common).
|
||||
- `linux/arm64` - 64-bit ARM architecture (e.g., Raspberry Pi 4, Apple M1/M2).
|
||||
- `linux/arm/v7` - 32-bit ARM architecture (e.g., Raspberry Pi 3, Raspberry Pi Zero).
|
||||
|
||||
### Availability Matrix {#availability}
|
||||
|
||||
The availability of applications for ARM architecture is limited due to the lack of support for some applications. The following table shows the availability of each application for each architecture. The `✅` symbol indicates that the application is available for that architecture, while the `❌` symbol indicates that it is not available.
|
||||
|
||||
| Application | AMD64 | ARM64 | ARMv7 | Reference |
|
||||
| ----------------- | ----- | ----- | ----- | --------- |
|
||||
| Firefox | ✅ | ✅ \* | ✅ \* | - |
|
||||
| Waterfox | ✅ | ❌ | ❌ | [Github Issue](https://github.com/BrowserWorks/Waterfox/issues/1506), [Reddit](https://www.reddit.com/r/waterfox/comments/jpqsds/are_there_any_builds_for_arm64/) |
|
||||
| Chromium | ✅ | ✅ \* | ✅ \* | - |
|
||||
| Google Chrome | ✅ | ❌ | ❌ | [Community Post](https://askubuntu.com/a/1383791) |
|
||||
| Ungoogled Chromium| ✅ | ❌ | ❌ | [Downloads Page](https://ungoogled-software.github.io/ungoogled-chromium-binaries/) |
|
||||
| Microsoft Edge | ✅ | ❌ | ❌ | [Community Post](https://techcommunity.microsoft.com/discussions/edgeinsiderdiscussions/edge-for-linuxarm64/1532272) |
|
||||
| Brave | ✅ | ✅ \* | ❌ | [Requirements Page](https://support.brave.com/hc/en-us/articles/360021357112-What-are-the-system-requirements-to-install-Brave) |
|
||||
| Vivaldi | ✅ | ✅ \* | ✅ \* | - |
|
||||
| Opera | ✅ | ❌ | ❌ | [Forum Post](https://forums.opera.com/topic/52811/opera-do-not-support-arm64-on-linux) |
|
||||
| Tor Browser | ✅ | ❌ | ❌ | [Forum Post](https://forum.torproject.org/t/tor-browser-for-arm-linux/5240) |
|
||||
| Remmina | ✅ | ✅ | ✅ | - |
|
||||
| VLC | ✅ | ✅ | ✅ | - |
|
||||
| Xfce | ✅ | ✅ | ✅ | - |
|
||||
| KDE | ✅ | ✅ | ✅ | - |
|
||||
|
||||
\* No DRM support.
|
||||
|
||||
:::tip
|
||||
[Oracle Cloud ARM free tier](https://www.oracle.com/cloud/free/) is a great way to test Neko on ARM architecture for free. You can use the `ghcr.io/m1k1o/neko/xfce` image to run a full desktop environment with Xfce and test the applications.
|
||||
:::
|
||||
|
||||
## Available Flavors {#flavors}
|
||||
|
||||
:::danger Keep in Mind
|
||||
|
@ -250,3 +247,37 @@ The base image is available at [`ghcr.io/m1k1o/neko/nvidia-base`](https://ghcr.i
|
|||
There is a known issue with EGL and Chromium-based browsers, see [m1k1o/neko #279](https://github.com/m1k1o/neko/issues/279).
|
||||
:::
|
||||
|
||||
## Supported Architectures {#arch}
|
||||
|
||||
Neko Docker images are built with docker buildx and are available for multiple architectures. The following architectures are supported by the base image:
|
||||
|
||||
- `linux/amd64` - 64-bit Intel/AMD architecture (most common).
|
||||
- `linux/arm64` - 64-bit ARM architecture (e.g., Raspberry Pi 4, Apple M1/M2).
|
||||
- `linux/arm/v7` - 32-bit ARM architecture (e.g., Raspberry Pi 3, Raspberry Pi Zero).
|
||||
|
||||
### Availability Matrix {#availability}
|
||||
|
||||
The availability of applications for ARM architecture is limited due to the lack of support for some applications. The following table shows the availability of each application for each architecture. The `✅` symbol indicates that the application is available for that architecture, while the `❌` symbol indicates that it is not available.
|
||||
|
||||
| Application | AMD64 | ARM64 | ARMv7 | Reference |
|
||||
| ----------------------------------------- | ----- | ----- | ----- | --------- |
|
||||
| [Firefox](#firefox) | ✅ | ✅ \* | ✅ \* | - |
|
||||
| [Tor Browser](#tor-browser) | ✅ | ❌ | ❌ | [Forum Post](https://forum.torproject.org/t/tor-browser-for-arm-linux/5240) |
|
||||
| [Waterfox](#waterfox) | ✅ | ❌ | ❌ | [Github Issue](https://github.com/BrowserWorks/Waterfox/issues/1506), [Reddit](https://www.reddit.com/r/waterfox/comments/jpqsds/are_there_any_builds_for_arm64/) |
|
||||
| [Chromium](#chromium) | ✅ | ✅ \* | ✅ \* | - |
|
||||
| [Google Chrome](#google-chrome) | ✅ | ❌ | ❌ | [Community Post](https://askubuntu.com/a/1383791) |
|
||||
| [Ungoogled Chromium](#ungoogled-chromium) | ✅ | ❌ | ❌ | [Downloads Page](https://ungoogled-software.github.io/ungoogled-chromium-binaries/) |
|
||||
| [Microsoft Edge](#microsoft-edge) | ✅ | ❌ | ❌ | [Community Post](https://techcommunity.microsoft.com/discussions/edgeinsiderdiscussions/edge-for-linuxarm64/1532272) |
|
||||
| [Brave](#brave) | ✅ | ✅ \* | ❌ | [Requirements Page](https://support.brave.com/hc/en-us/articles/360021357112-What-are-the-system-requirements-to-install-Brave) |
|
||||
| [Vivaldi](#vivaldi) | ✅ | ✅ \* | ✅ \* | - |
|
||||
| [Opera](#opera) | ✅ | ❌ | ❌ | [Forum Post](https://forums.opera.com/topic/52811/opera-do-not-support-arm64-on-linux) |
|
||||
| [Xfce](#xfce) | ✅ | ✅ | ✅ | - |
|
||||
| [KDE](#kde) | ✅ | ✅ | ✅ | - |
|
||||
| [Remmina](#remmina) | ✅ | ✅ | ✅ | - |
|
||||
| [VLC](#vlc) | ✅ | ✅ | ✅ | - |
|
||||
|
||||
\* No DRM support.
|
||||
|
||||
:::tip
|
||||
[Oracle Cloud ARM free tier](https://www.oracle.com/cloud/free/) is a great way to test Neko on ARM architecture for free. You can use the `ghcr.io/m1k1o/neko/xfce` image to run a full desktop environment with Xfce and test the applications.
|
||||
:::
|
||||
|
|
|
@ -76,30 +76,11 @@ services:
|
|||
NEKO_WEBRTC_NAT1TO1: <your-IP>
|
||||
```
|
||||
|
||||
## Raspberry Pi {#raspberry-pi}
|
||||
|
||||
```yaml title="config.yaml"
|
||||
capture:
|
||||
video:
|
||||
codec: h264
|
||||
ids: [ main ]
|
||||
pipelines:
|
||||
main:
|
||||
gst_pipeline: |
|
||||
ximagesrc display-name=%s use-damage=0 show-pointer=true use-damage=false
|
||||
! video/x-raw,framerate=30/1
|
||||
! videoconvert
|
||||
! queue
|
||||
! video/x-raw,framerate=30/1,format=NV12
|
||||
! v4l2h264enc extra-controls="controls,h264_profile=1,video_bitrate=1250000;"
|
||||
! h264parse config-interval=3
|
||||
! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
|
||||
```
|
||||
## Raspberry Pi GPU Acceleration {#raspberry-pi}
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
services:
|
||||
neko:
|
||||
# see docs for more variants
|
||||
image: "ghcr.io/m1k1o/neko/chromium:latest"
|
||||
restart: "unless-stopped"
|
||||
# increase on rpi's with more then 1gb ram.
|
||||
|
@ -110,12 +91,121 @@ services:
|
|||
# note: this is important since we need a GPU for hardware acceleration alternatively
|
||||
# mount the devices into the docker.
|
||||
privileged: true
|
||||
volumes:
|
||||
- "./config.yaml:/etc/neko/neko.yaml"
|
||||
environment:
|
||||
NEKO_CAPTURE_VIDEO_PIPELINE: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! video/x-raw,framerate=25/1
|
||||
! videoconvert ! queue
|
||||
! video/x-raw,format=NV12
|
||||
! v4l2h264enc
|
||||
name=encoder
|
||||
extra-controls="controls,h264_profile=1,video_bitrate=1250000;"
|
||||
! h264parse config-interval=-1
|
||||
! video/x-h264,stream-format=byte-stream
|
||||
! appsink name=appsink
|
||||
NEKO_CAPTURE_VIDEO_CODEC: "h264"
|
||||
NEKO_DESKTOP_SCREEN: '1280x720@30'
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: 'neko'
|
||||
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: 'admin'
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: neko
|
||||
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: admin
|
||||
NEKO_WEBRTC_EPR: 52000-52100
|
||||
NEKO_WEBRTC_ICELITE: 1
|
||||
```
|
||||
|
||||
## Nvidia GPU Acceleration {#nvidia}
|
||||
|
||||
Neko supports hardware acceleration using Nvidia GPUs. To use this feature, you need to have the Nvidia Container Toolkit installed on your system. You can find the installation instructions [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html).
|
||||
|
||||
This example shows how to accelerate video encoding and as well the browser rendering using the GPU. You can test if the GPU is used by running `nvidia-smi`, which should show the GPU usage of both the browser and neko. In the browser, you can run the [WebGL Aquarium Demo](https://webglsamples.org/aquarium/aquarium.html) to test the GPU usage.
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
services:
|
||||
neko:
|
||||
image: "ghcr.io/m1k1o/neko/nvidia-firefox:latest"
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "52000-52100:52000-52100/udp"
|
||||
environment:
|
||||
NEKO_CAPTURE_VIDEO_PIPELINE: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! video/x-raw,framerate=25/1
|
||||
! videoconvert ! queue
|
||||
! video/x-raw,format=NV12
|
||||
! nvh264enc
|
||||
name=encoder
|
||||
preset=2
|
||||
gop-size=25
|
||||
spatial-aq=true
|
||||
temporal-aq=true
|
||||
bitrate=4096
|
||||
vbv-buffer-size=4096
|
||||
rc-mode=6
|
||||
! h264parse config-interval=-1
|
||||
! video/x-h264,stream-format=byte-stream
|
||||
! appsink name=appsink
|
||||
NEKO_CAPTURE_VIDEO_CODEC: "h264"
|
||||
NEKO_DESKTOP_SCREEN: 1920x1080@30
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: neko
|
||||
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: admin
|
||||
NEKO_WEBRTC_EPR: 52000-52100
|
||||
NEKO_WEBRTC_ICELITE: 1
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
See available [Nvidia Docker Images](/docs/v3/installation/docker-images#nvidia).
|
||||
|
||||
If you only want to accelerate the encoding, **not the browser rendering**, you can use the default image with additional environment variables:
|
||||
|
||||
```yaml title="docker-compose.yaml"
|
||||
services:
|
||||
neko:
|
||||
# highlight-next-line
|
||||
image: "ghcr.io/m1k1o/neko/firefox:latest"
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "52000-52100:52000-52100/udp"
|
||||
environment:
|
||||
# highlight-start
|
||||
NVIDIA_VISIBLE_DEVICES: all
|
||||
NVIDIA_DRIVER_CAPABILITIES: all
|
||||
# highlight-end
|
||||
NEKO_CAPTURE_VIDEO_PIPELINE: |
|
||||
ximagesrc display-name={display} show-pointer=true use-damage=false
|
||||
! video/x-raw,framerate=25/1
|
||||
! videoconvert ! queue
|
||||
! video/x-raw,format=NV12
|
||||
! nvh264enc
|
||||
name=encoder
|
||||
preset=2
|
||||
gop-size=25
|
||||
spatial-aq=true
|
||||
temporal-aq=true
|
||||
bitrate=4096
|
||||
vbv-buffer-size=4096
|
||||
rc-mode=6
|
||||
! h264parse config-interval=-1
|
||||
! video/x-h264,stream-format=byte-stream
|
||||
! appsink name=appsink
|
||||
NEKO_CAPTURE_VIDEO_CODEC: "h264"
|
||||
NEKO_DESKTOP_SCREEN: 1920x1080@30
|
||||
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: neko
|
||||
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: admin
|
||||
NEKO_WEBRTC_EPR: 52000-52100
|
||||
NEKO_WEBRTC_ICELITE: 1
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
|
||||
Currently, Neko is in compatibility mode, meaning that as soon as a single V2 configuration option is set, the legacy mode is enabled. This approach allows for a smooth transition from V2 to V3, where it does not expose the V2 API for new users but still allows existing users who use the old configuration to continue using it as before.
|
||||
|
||||
The legacy mode can be explicitly enabled or disabled by setting the `NEKO_LEGACY` environment variable to `true` or `false`.
|
||||
The legacy mode includes a compatibility layer that allows V2 clients to connect to V3. Currently, the **client is not compatible with V3**, so the legacy mode is enabled by default. It can be explicitly enabled or disabled by setting the `NEKO_LEGACY` environment variable to `true` or `false`.
|
||||
|
||||
:::tip
|
||||
You can migrate to a new configuration even if you are using a V2 client. Just make sure to set the `NEKO_LEGACY` environment variable to `true`.
|
||||
:::warning
|
||||
The legacy mode is **still used by the client**. It is recommended to migrate to the new configuration options, but do not disable the legacy mode unless you are using a new client that is compatible with V3 (e.g., [demodesk/neko-client](https://github.com/demodesk/neko-client)). Once the new client is released, the legacy mode will be automatically removed from the server.
|
||||
:::
|
||||
|
||||
:::info Built-in Client
|
||||
When using Neko in a container with a built-in client, the client will always be compatible with the server regardless of what configuration is used.
|
||||
:::
|
||||
If you set both V3 and V2 configuration options, the V2 configuration options will take precedence over the V3 configuration options. This is to ensure that the legacy mode works as expected and does not break existing configurations.
|
||||
|
||||
## Docker Images {#docker}
|
||||
|
||||
|
@ -42,8 +40,10 @@ In order to migrate from V2 to V3, you need to update the configuration to the n
|
|||
| `NEKO_IMPLICIT_CONTROL` | `NEKO_SESSION_IMPLICIT_HOSTING` |
|
||||
| `NEKO_CONTROL_PROTECTION` | `NEKO_SESSION_CONTROL_PROTECTION` |
|
||||
| `NEKO_HEARTBEAT_INTERVAL` | `NEKO_SESSION_HEARTBEAT_INTERVAL` |
|
||||
| `NEKO_FILE_TRANSFER_ENABLED` | `NEKO_FILETRANSFER_ENABLED` |
|
||||
| `NEKO_FILE_TRANSFER_PATH` | `NEKO_FILETRANSFER_DIR` |
|
||||
|
||||
See the V3 [configuration options](/docs/v3/configuration).
|
||||
See the V3 [configuration options](/docs/v3/configuration). For file transfer, see the [File Transfer Plugin](/docs/v3/configuration/plugins#filetransfer).
|
||||
|
||||
### WebRTC Video {#config.video}
|
||||
|
||||
|
@ -58,9 +58,9 @@ See the V3 configuration options for the [WebRTC Video](/docs/v3/configuration/c
|
|||
| `NEKO_VP8=true` *deprecated* | `NEKO_CAPTURE_VIDEO_CODEC=vp8` |
|
||||
| `NEKO_VP9=true` *deprecated* | `NEKO_CAPTURE_VIDEO_CODEC=vp9` |
|
||||
| `NEKO_VIDEO` | `NEKO_CAPTURE_VIDEO_PIPELINE`, V3 allows multiple video pipelines |
|
||||
| `NEKO_VIDEO_BITRATE` | **removed**, use custom pipeline instead |
|
||||
| `NEKO_HWENC` | **removed**, use custom pipeline instead |
|
||||
| `NEKO_MAX_FPS` | **removed**, use custom pipeline instead |
|
||||
| `NEKO_VIDEO_BITRATE` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.gst_pipeline) instead |
|
||||
| `NEKO_HWENC` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.gst_pipeline) instead |
|
||||
| `NEKO_MAX_FPS` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.gst_pipeline) instead |
|
||||
|
||||
|
||||
:::warning Limitation
|
||||
|
@ -133,10 +133,10 @@ See the V3 configuration options for the [WebRTC](/docs/v3/configuration/webrtc)
|
|||
|
||||
Here is a full list of all the configuration options available in Neko V2 that are still available in Neko V3 with legacy support enabled.
|
||||
|
||||
import Configuration from '@site/src/components/Configuration';
|
||||
import { ConfigurationTab } from '@site/src/components/Configuration';
|
||||
import configOptions from './help.json';
|
||||
|
||||
<Configuration configOptions={configOptions} />
|
||||
<ConfigurationTab options={configOptions} heading={true} />
|
||||
|
||||
See the full [V3 configuration reference](/docs/v3/configuration/#full) for more details.
|
||||
|
||||
|
@ -160,6 +160,8 @@ Only the [`multiuser`](/docs/v3/configuration/authentication#member.multiuser) p
|
|||
|
||||
Since WebSocket messages are not user-facing API, there exists no migration guide for them. When the legacy API is enabled, the user connects to the `/ws` endpoint and is handled by the compatibility layer V2 API. The V3 API is available at the `/api/ws` endpoint.
|
||||
|
||||
V2 used to send WebSocket ping messages every 60 seconds, whereas V3 sends them every 10 seconds and additionally uses a heartbeat mechanism to verify if the connection is still active.
|
||||
|
||||
### WebRTC API {#api.webrtc}
|
||||
|
||||
Since the WebRTC API is not user-facing API, there exists no migration guide for it. It has been changed to Big Endian format (previously Little Endian) to allow easier manipulation on the client side.
|
||||
|
|
|
@ -15,6 +15,102 @@
|
|||
"defaultValue": "false",
|
||||
"description": "save logs to file"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"cert"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to the SSL cert used to secure the neko server"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"key"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to the SSL key used to secure the neko server"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"bind"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "address/port/socket to serve neko"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"proxy"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "enable reverse proxy mode"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"static"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to neko client files to serve"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"path_prefix"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path prefix for HTTP requests"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"cors"
|
||||
],
|
||||
"type": "strings",
|
||||
"description": "list of allowed origins for CORS"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"locks"
|
||||
],
|
||||
"type": "strings",
|
||||
"description": "resources, that will be locked when starting (control, login)"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"implicit_control"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "if enabled members can gain control implicitly"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"control_protection"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "control protection means, users can gain control only if at least one admin is in the room"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"heartbeat_interval"
|
||||
],
|
||||
"type": "int",
|
||||
"defaultValue": "120",
|
||||
"description": "heartbeat interval in seconds"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"file_transfer_enabled"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "enable file transfer feature"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"file_transfer_path"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to use for file transfer"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"display"
|
||||
|
@ -192,93 +288,12 @@
|
|||
"type": "string",
|
||||
"description": "admin password for connecting to stream"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"cert"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to the SSL cert used to secure the neko server"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"key"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to the SSL key used to secure the neko server"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"bind"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "address/port/socket to serve neko"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"proxy"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "enable reverse proxy mode"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"static"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path to neko client files to serve"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"path_prefix"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "path prefix for HTTP requests"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"cors"
|
||||
],
|
||||
"type": "strings",
|
||||
"description": "list of allowed origins for CORS"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"locks"
|
||||
],
|
||||
"type": "strings",
|
||||
"description": "resources, that will be locked when starting (control, login)"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"implicit_control"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "if enabled members can gain control implicitly"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"control_protection"
|
||||
],
|
||||
"type": "boolean",
|
||||
"defaultValue": "false",
|
||||
"description": "control protection means, users can gain control only if at least one admin is in the room"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"heartbeat_interval"
|
||||
],
|
||||
"type": "int",
|
||||
"defaultValue": "120",
|
||||
"description": "heartbeat interval in seconds"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"nat1to1"
|
||||
],
|
||||
"type": "strings",
|
||||
"description": "sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP "
|
||||
"description": "sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
@ -307,14 +322,14 @@
|
|||
"iceserver"
|
||||
],
|
||||
"type": "strings",
|
||||
"description": "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection "
|
||||
"description": "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
"iceservers"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection "
|
||||
"description": "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer"
|
||||
},
|
||||
{
|
||||
"key": [
|
||||
|
|
|
@ -1,7 +1,22 @@
|
|||
|
||||
--legacy enable legacy mode (default true)
|
||||
|
||||
--logs save logs to file
|
||||
|
||||
--cert string path to the SSL cert used to secure the neko server
|
||||
--key string path to the SSL key used to secure the neko server
|
||||
--bind string address/port/socket to serve neko
|
||||
--proxy enable reverse proxy mode
|
||||
--static string path to neko client files to serve
|
||||
--path_prefix string path prefix for HTTP requests
|
||||
--cors strings list of allowed origins for CORS
|
||||
--locks strings resources, that will be locked when starting (control, login)
|
||||
--implicit_control if enabled members can gain control implicitly
|
||||
--control_protection control protection means, users can gain control only if at least one admin is in the room
|
||||
--heartbeat_interval int heartbeat interval in seconds (default 120)
|
||||
--file_transfer_enabled enable file transfer feature
|
||||
--file_transfer_path string path to use for file transfer
|
||||
|
||||
--display string XDisplay to capture
|
||||
--video_codec string video codec to be used
|
||||
--av1 DEPRECATED: use video_codec
|
||||
|
@ -12,6 +27,7 @@
|
|||
--video_bitrate int video bitrate in kbit/s
|
||||
--hwenc string use hardware accelerated encoding
|
||||
--max_fps int maximum fps delivered via WebRTC, 0 is for no maximum
|
||||
|
||||
--device string audio device to capture
|
||||
--audio_codec string audio codec to be used
|
||||
--g722 DEPRECATED: use audio_codec
|
||||
|
@ -20,6 +36,7 @@
|
|||
--pcmu DEPRECATED: use audio_codec
|
||||
--audio string audio codec parameters to use for streaming
|
||||
--audio_bitrate int audio bitrate in kbit/s
|
||||
|
||||
--broadcast_pipeline string custom gst pipeline used for broadcasting, strings {url} {device} {display} will be replaced
|
||||
--broadcast_url string a default default URL for broadcast streams, can be disabled/changed later by admins in the GUI
|
||||
--broadcast_autostart automatically start broadcasting when neko starts and broadcast_url is set
|
||||
|
@ -29,24 +46,11 @@
|
|||
--password string password for connecting to stream
|
||||
--password_admin string admin password for connecting to stream
|
||||
|
||||
--cert string path to the SSL cert used to secure the neko server
|
||||
--key string path to the SSL key used to secure the neko server
|
||||
--bind string address/port/socket to serve neko
|
||||
--proxy enable reverse proxy mode
|
||||
--static string path to neko client files to serve
|
||||
--path_prefix string path prefix for HTTP requests
|
||||
--cors strings list of allowed origins for CORS
|
||||
|
||||
--locks strings resources, that will be locked when starting (control, login)
|
||||
--implicit_control if enabled members can gain control implicitly
|
||||
--control_protection control protection means, users can gain control only if at least one admin is in the room
|
||||
--heartbeat_interval int heartbeat interval in seconds (default 120)
|
||||
|
||||
--nat1to1 strings sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP
|
||||
--nat1to1 strings sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used
|
||||
--tcpmux int single TCP mux port for all peers
|
||||
--udpmux int single UDP mux port for all peers
|
||||
--icelite configures whether or not the ice agent should be a lite agent
|
||||
--iceserver strings describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection
|
||||
--iceservers string describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection
|
||||
--iceserver strings describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer
|
||||
--iceservers string describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer
|
||||
--ipfetch string automatically fetch IP address from given URL when nat1to1 is not present
|
||||
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from
|
||||
|
|
|
@ -6,6 +6,8 @@ If you want to run Neko behind a reverse proxy, you can use the following exampl
|
|||
Do not forget to enable [`server.proxy=true`](/docs/v3/configuration#server.proxy) in your `config.yml` file to allow the server to trust the proxy headers.
|
||||
:::
|
||||
|
||||
Neko pings websocket client every 10 seconds, and client is scheduled to send [heartbeat](/docs/v3/configuration#session.heartbeat_interval) to the server every 120 seconds. Make sure, that your timeout settings in the reverse proxy are set accordingly.
|
||||
|
||||
## Traefik v2 {#traefik-v2}
|
||||
|
||||
See the example below for a `docker-compose.yml` file.
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"gen-help": "node ./src/components/Configuration/generate.js ./docs/configuration/help.txt ./docs/configuration/help.json",
|
||||
"gen-help:v2": "node ./src/components/Configuration/generate.js ./docs/v2-migration/help.txt ./docs/v2-migration/help.json",
|
||||
"gen-config": "node ./src/components/Configuration/generate.js ./docs/configuration/help.txt ./docs/configuration/help.json",
|
||||
"gen-config:v2": "node ./src/components/Configuration/generate.js ./docs/migration-from-v2/help.txt ./docs/migration-from-v2/help.json",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"gen-api-docs": "./gen-api-docs.sh",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Clean the API docs
|
||||
docusaurus clean-api-docs all
|
22
webpage/scripts/gen-config.sh
Executable file
22
webpage/scripts/gen-config.sh
Executable file
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
HELP_FILE="$(realpath -m docs/configuration/help.txt)"
|
||||
|
||||
pushd ../server
|
||||
go run cmd/neko/main.go serve --help > $HELP_FILE
|
||||
popd
|
||||
|
||||
# remove all lines with " V2: "
|
||||
sed -i '/ V2: /d' $HELP_FILE
|
||||
# remove all lines with " V2 DEPRECATED: "
|
||||
sed -i '/ V2 DEPRECATED: /d' $HELP_FILE
|
||||
# remove --legacy
|
||||
sed -i '/--legacy/d' $HELP_FILE
|
||||
|
||||
# remove evething until first "Flags:"
|
||||
sed -i '1,/Flags:/d' $HELP_FILE
|
||||
# remove --help
|
||||
sed -i '/--help/d' $HELP_FILE
|
||||
|
||||
npm run gen-config
|
|
@ -17,6 +17,16 @@ const parseConfigOptions = (text) => {
|
|||
defaultValue = 'true';
|
||||
}
|
||||
}
|
||||
// this is an opaque object
|
||||
if (type === 'string' && defaultValue === '{}') {
|
||||
type = 'object';
|
||||
defaultValue = {};
|
||||
}
|
||||
// this is an opaque array
|
||||
if (type === 'string' && defaultValue === '[]') {
|
||||
type = 'array';
|
||||
defaultValue = [];
|
||||
}
|
||||
return { key: key.split('.'), type, defaultValue: defaultValue || undefined, description };
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -3,130 +3,244 @@ import Tabs from '@theme/Tabs';
|
|||
import TabItem from '@theme/TabItem';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
|
||||
interface ConfigOption {
|
||||
key: string[];
|
||||
description: string;
|
||||
defaultValue?: string;
|
||||
interface ConfigOptionValue {
|
||||
type?: string;
|
||||
description?: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
interface ConfigurationTabProps {
|
||||
configOptions: ConfigOption[];
|
||||
interface ConfigOption extends ConfigOptionValue {
|
||||
key: string[];
|
||||
}
|
||||
|
||||
const ConfigurationTab: React.FC<ConfigurationTabProps> = ({ configOptions }) => {
|
||||
const environmentVariables = () => {
|
||||
let code = '';
|
||||
configOptions.forEach(option => {
|
||||
let value = ""
|
||||
if (option.defaultValue) {
|
||||
value = `"${option.defaultValue}"`
|
||||
} else if (option.type) {
|
||||
value = `<${option.type}>`
|
||||
}
|
||||
code += `# ${option.description}\n`;
|
||||
code += `NEKO_${option.key.join('_').toUpperCase()}: ${value}\n`;
|
||||
});
|
||||
return (
|
||||
<CodeBlock language="yaml">
|
||||
{code}
|
||||
</CodeBlock>
|
||||
);
|
||||
function configKey(key: string | string[], value: ConfigOptionValue | any): ConfigOption {
|
||||
if (typeof key === 'string') {
|
||||
key = key.split('.');
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return {
|
||||
key,
|
||||
type: value.type || getType(value.defaultValue),
|
||||
description: value.description,
|
||||
defaultValue: value.defaultValue,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
key,
|
||||
type: getType(value),
|
||||
defaultValue: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function configKeys(optValues: Record<string, ConfigOptionValue | any>): ConfigOption[] {
|
||||
let options: ConfigOption[] = [];
|
||||
Object.entries(optValues).forEach(([key, value]) => {
|
||||
options.push(configKey(key, value));
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
function filterKeys(options: ConfigOption[], filter: string): ConfigOption[] {
|
||||
return options.filter(option => {
|
||||
const key = option.key.join('.');
|
||||
return key.startsWith(filter)
|
||||
});
|
||||
}
|
||||
|
||||
function defaultValue(value: ConfigOptionValue): string {
|
||||
switch (value.type) {
|
||||
case 'boolean':
|
||||
return `${value.defaultValue || false}`;
|
||||
case 'int':
|
||||
case 'float':
|
||||
case 'number':
|
||||
return `${value.defaultValue || 0}`;
|
||||
case 'duration':
|
||||
case 'string':
|
||||
return `${value.defaultValue ? `"${value.defaultValue}"` : '<string>'}`;
|
||||
case 'strings':
|
||||
return '<comma-separated list of strings>';
|
||||
case 'object':
|
||||
return '<json encoded object>';
|
||||
case 'array':
|
||||
return '<json encoded array>';
|
||||
default:
|
||||
return value.type ? `<${value.type}>` : '';
|
||||
}
|
||||
}
|
||||
|
||||
function getType(value: any): string {
|
||||
if (Array.isArray(value)) {
|
||||
return 'array';
|
||||
}
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
export function EnvironmentVariables({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) {
|
||||
if (typeof comments === 'undefined') {
|
||||
comments = true;
|
||||
}
|
||||
|
||||
const cmdArguments = () => {
|
||||
let code = '';
|
||||
configOptions.forEach(option => {
|
||||
code += `# ${option.description}\n`;
|
||||
code += `--${option.key.join('.')}`;
|
||||
if (option.type) {
|
||||
code += ` <${option.type}>`;
|
||||
}
|
||||
code += '\n';
|
||||
});
|
||||
return (
|
||||
<CodeBlock language="shell">
|
||||
{code}
|
||||
</CodeBlock>
|
||||
);
|
||||
let code = '';
|
||||
options.forEach(option => {
|
||||
const description = option.description ? option.description : '';
|
||||
const type = option.type ? ` (${option.type})` : '';
|
||||
if (comments && description) {
|
||||
code += `# ${description}${type}\n`;
|
||||
}
|
||||
code += `NEKO_${option.key.join('_').toUpperCase()}=${defaultValue(option)}\n`;
|
||||
});
|
||||
|
||||
return (
|
||||
<CodeBlock language="shell" {...props}>
|
||||
{code}
|
||||
</CodeBlock>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandLineArguments({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) {
|
||||
if (typeof comments === 'undefined') {
|
||||
comments = true;
|
||||
}
|
||||
|
||||
const yamlFile = () => {
|
||||
const final = Symbol('final');
|
||||
let code = '';
|
||||
options.forEach(option => {
|
||||
const description = option.description ? option.description : '';
|
||||
const type = option.type ? ` (${option.type})` : '';
|
||||
if (comments && description) {
|
||||
code += `# ${description}${type}\n`;
|
||||
}
|
||||
code += `--${option.key.join('.')} ${defaultValue(option)}\n`;
|
||||
});
|
||||
|
||||
const buildYaml = (obj, prefix = '') => {
|
||||
let code = '';
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key];
|
||||
if (typeof value === 'object' && !Array.isArray(value) && !value[final]) {
|
||||
code += prefix+`${key}:\n`;
|
||||
code += buildYaml(value, prefix + ' ');
|
||||
return (
|
||||
<CodeBlock language="shell" {...props}>
|
||||
{code}
|
||||
</CodeBlock>
|
||||
);
|
||||
}
|
||||
|
||||
export function YamlFileContent({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) {
|
||||
if (typeof comments === 'undefined') {
|
||||
comments = true;
|
||||
}
|
||||
|
||||
const final = Symbol('final');
|
||||
|
||||
const buildYaml = (obj: Record<string, any>, prefix = '') => {
|
||||
let code = '';
|
||||
Object.entries(obj).forEach(([key, option]) => {
|
||||
if (typeof option === 'object' && !Array.isArray(option) && !option[final]) {
|
||||
code += prefix+`${key}:\n`;
|
||||
code += buildYaml(option, prefix + ' ');
|
||||
} else {
|
||||
const description = option.description ? option.description : '';
|
||||
const type = option.type ? ` (${option.type})` : '';
|
||||
if (comments && description) {
|
||||
code += `${prefix}# ${description}${type}\n`;
|
||||
}
|
||||
let value: string;
|
||||
if (option.type === 'strings') {
|
||||
value = option.defaultValue ? `[ "${option.defaultValue}" ]` : '[ <string> ]';
|
||||
} else if (option.type === 'object') {
|
||||
value = "{}"
|
||||
} else if (option.type === 'array') {
|
||||
value = "[]"
|
||||
} else {
|
||||
let val = '';
|
||||
switch (value.type) {
|
||||
case 'boolean':
|
||||
val = `${value.defaultValue || false}`;
|
||||
break;
|
||||
case 'int':
|
||||
case 'float':
|
||||
val = `${value.defaultValue || 0}`;
|
||||
break;
|
||||
case 'strings':
|
||||
val = `[ ${value.defaultValue ? value.defaultValue.map(v => `"${v}"`).join(', ') : '<string>'} ]`;
|
||||
break;
|
||||
case 'duration':
|
||||
case 'string':
|
||||
val = `${value.defaultValue ? `"${value.defaultValue}"` : '<string>'}`;
|
||||
break;
|
||||
default:
|
||||
val = `<${value.type}>`;
|
||||
break;
|
||||
}
|
||||
code += prefix+`# ${value.description || ''}\n`;
|
||||
code += prefix+`${key}: ${val}\n`;
|
||||
value = defaultValue(option);
|
||||
}
|
||||
});
|
||||
return code;
|
||||
};
|
||||
code += `${prefix}${key}: ${value}\n`;
|
||||
}
|
||||
});
|
||||
return code;
|
||||
};
|
||||
|
||||
const yamlCode = buildYaml(configOptions.reduce((acc, option) => {
|
||||
const keys = option.key;
|
||||
let current = acc;
|
||||
keys.forEach((key, index) => {
|
||||
if (!current[key]) {
|
||||
current[key] = index === keys.length - 1 ? option : {};
|
||||
}
|
||||
current = current[key];
|
||||
});
|
||||
current[final] = true;
|
||||
return acc;
|
||||
}, {}));
|
||||
const yamlCode = buildYaml(options.reduce((acc, option) => {
|
||||
const keys = option.key;
|
||||
let current = acc;
|
||||
keys.forEach((key, index) => {
|
||||
if (!current[key]) {
|
||||
current[key] = index === keys.length - 1 ? option : {};
|
||||
}
|
||||
current = current[key];
|
||||
});
|
||||
current[final] = true;
|
||||
return acc;
|
||||
}, {}));
|
||||
|
||||
return (
|
||||
<CodeBlock language="yaml">
|
||||
{yamlCode}
|
||||
</CodeBlock>
|
||||
);
|
||||
return (
|
||||
<CodeBlock language="yaml" {...props}>
|
||||
{yamlCode}
|
||||
</CodeBlock>
|
||||
);
|
||||
}
|
||||
|
||||
type ConfigurationTabProps = {
|
||||
options?: ConfigOption[] | Record<string, ConfigOptionValue | any>;
|
||||
heading?: boolean;
|
||||
comments?: boolean;
|
||||
filter?: string | string[] | Record<string, ConfigOptionValue | any>;
|
||||
};
|
||||
|
||||
export function ConfigurationTab({ options, heading, comments, filter, ...props }: ConfigurationTabProps) {
|
||||
var configOptions: ConfigOption[] = [];
|
||||
if (Array.isArray(options)) {
|
||||
configOptions = options;
|
||||
} else {
|
||||
configOptions = configKeys(options)
|
||||
}
|
||||
if (typeof comments === 'undefined') {
|
||||
comments = true;
|
||||
}
|
||||
if (typeof heading === 'undefined') {
|
||||
heading = false;
|
||||
}
|
||||
|
||||
if (Array.isArray(filter)) {
|
||||
let filteredOptions: ConfigOption[] = [];
|
||||
for (const f of filter) {
|
||||
filteredOptions = [ ...filteredOptions, ...filterKeys(configOptions, f) ];
|
||||
}
|
||||
configOptions = filteredOptions;
|
||||
} else if (typeof filter === 'string') {
|
||||
configOptions = filterKeys(configOptions, filter);
|
||||
} else if (typeof filter === 'object') {
|
||||
let filteredOptions: ConfigOption[] = [];
|
||||
for (const k in filter) {
|
||||
let filtered = configOptions.find(option => {
|
||||
return option.key.join('.') === k;
|
||||
});
|
||||
let replaced = configKey(k, filter[k]);
|
||||
filteredOptions = [ ...filteredOptions, { ...filtered, ...replaced } ];
|
||||
}
|
||||
configOptions = filteredOptions;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tabs>
|
||||
<TabItem value="env" label="Environment Variables">
|
||||
<Tabs groupId="configuration" defaultValue="yaml" values={[
|
||||
{ label: 'YAML Configuration File', value: 'yaml' },
|
||||
{ label: 'Environment Variables', value: 'env' },
|
||||
{ label: 'Command Line Arguments', value: 'args' },
|
||||
]} {...props}>
|
||||
<TabItem value="env" label="Environment Variables">
|
||||
{heading && (
|
||||
<p>You can set the following environment variables in your <code>docker-compose.yaml</code> file or in your shell environment.</p>
|
||||
{environmentVariables()}
|
||||
</TabItem>
|
||||
<TabItem value="args" label="Command Line Arguments">
|
||||
)}
|
||||
{EnvironmentVariables({ options: configOptions, comments })}
|
||||
</TabItem>
|
||||
<TabItem value="args" label="Command Line Arguments">
|
||||
{heading && (
|
||||
<p>You can list the following command line arguments using <code>neko serve --help</code>.</p>
|
||||
{cmdArguments()}
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML Configuration File">
|
||||
)}
|
||||
{CommandLineArguments({ options: configOptions, comments })}
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML Configuration File">
|
||||
{heading && (
|
||||
<p>You can create a <code>/etc/neko/neko.yaml</code> file with the following configuration options.</p>
|
||||
{yamlFile()}
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
{YamlFileContent({ options: configOptions, comments })}
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigurationTab;
|
||||
}
|
||||
|
|
|
@ -9,3 +9,11 @@ We are here to assist you with any issues you may face while setting up or using
|
|||
- For installation or usage questions, [join our Discord](https://discord.gg/3U6hWpC) and post in the [#community-help](https://discord.com/channels/665851821906067466/696222582114091088) channel.
|
||||
- To report bugs or request features, [open a new issue on GitHub](https://github.com/m1k1o/neko/issues).
|
||||
- If you find an issue with this documentation, click the `Edit this page` button at the bottom of the page and then the `edit` button on the GitHub page that opens to make edits directly from your browser. (See a [step-by-step guide here](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files)).
|
||||
|
||||
**Enterprise Support**
|
||||
|
||||
For enterprises or organizations requiring **dedicated, paid support**, custom solutions, or priority assistance, please contact us at <a href="mailto:neko@m1k1o.net">neko@m1k1o.net</a>.
|
||||
|
||||
**Security**
|
||||
|
||||
If you discover a security vulnerability in Neko, please report it to us directly at <a href="mailto:security@m1k1o.net">security@m1k1o.net</a>. We take security seriously and will work with you to address the issue promptly. Please do not disclose the vulnerability publicly until we have had a chance to address it. We appreciate your help in keeping Neko secure.
|
||||
|
|
3
webpage/static/.well-known/security.txt
Normal file
3
webpage/static/.well-known/security.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
Contact: mailto:security@m1k1o.net
|
||||
Policy: https://github.com/m1k1o/neko/security
|
||||
Expires: 2030-04-01T00:00:00z
|
Loading…
Add table
Reference in a new issue