From 212bf8a607566b53539ff70c6e3b901fc2d5130e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20P=C3=A9rez?= <65142395+p3javier@users.noreply.github.com> Date: Sat, 22 Mar 2025 23:56:43 +0100 Subject: [PATCH 01/91] feat(i18n): enhance language management by saving user preference and auto detect browser language (#487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(i18n): enhance language management by saving user preference and detecting browser language - Added a watcher to save the selected language to localStorage on change. - Implemented browser language detection to set the default language based on user settings or browser preferences. * use ~/utils/localstorage, remove setting savedLang in mounted as this is already set in i18.ts. * do not repeat fallbackLocale and remove console log. --------- Co-authored-by: Miroslav Šedivý --- client/src/components/menu.vue | 8 +++++++- client/src/plugins/i18n.ts | 24 ++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/client/src/components/menu.vue b/client/src/components/menu.vue index ead446be..0b034da1 100644 --- a/client/src/components/menu.vue +++ b/client/src/components/menu.vue @@ -60,8 +60,9 @@ From b383e1e24d13d7892671917b6e3d93c0dae674da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Wed, 2 Apr 2025 22:36:12 +0200 Subject: [PATCH 44/91] add HelloGitHub Badge #419. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e1fbad7b..ad5c7b1c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ Chat on discord + + Featured|HelloGitHub + build From ec44bf0e04c7efd7e97c206a533f56fcfd11f0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 3 Apr 2025 14:17:47 +0200 Subject: [PATCH 45/91] legacy handler set server bind. --- server/internal/http/legacy/handler.go | 4 ++-- server/internal/http/manager.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/internal/http/legacy/handler.go b/server/internal/http/legacy/handler.go index 65a0349b..8199ee76 100644 --- a/server/internal/http/legacy/handler.go +++ b/server/internal/http/legacy/handler.go @@ -47,12 +47,12 @@ type LegacyHandler struct { sessionIPs map[string]string } -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), } diff --git a/server/internal/http/manager.go b/server/internal/http/manager.go index 8aadd086..b5753811 100644 --- a/server/internal/http/manager.go +++ b/server/internal/http/manager.go @@ -58,7 +58,7 @@ func New(WebSocketManager types.WebSocketManager, ApiManager types.ApiManager, c // Legacy handler if viper.GetBool("legacy") { - legacy.New().Route(router) + legacy.New(config.Bind).Route(router) } batch := batchHandler{ From 68e23fa8e705639dbdae62a6773d2311988dda3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 3 Apr 2025 14:17:59 +0200 Subject: [PATCH 46/91] fix server dev. --- server/dev/build | 4 ++-- server/dev/fmt | 6 +++--- server/dev/go | 6 +++--- server/dev/lint | 6 +++--- server/dev/rebuild | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/dev/build b/server/dev/build index faddfc0b..0522631c 100755 --- a/server/dev/build +++ b/server/dev/build @@ -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 diff --git a/server/dev/fmt b/server/dev/fmt index f8b3130e..a294473e 100755 --- a/server/dev/fmt +++ b/server/dev/fmt @@ -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 ./... diff --git a/server/dev/go b/server/dev/go index 29a62e91..229a9365 100755 --- a/server/dev/go +++ b/server/dev/go @@ -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" diff --git a/server/dev/lint b/server/dev/lint index c0f0b5bb..0c9e9bed 100755 --- a/server/dev/lint +++ b/server/dev/lint @@ -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'; diff --git a/server/dev/rebuild b/server/dev/rebuild index d2132fe7..696af7aa 100755 --- a/server/dev/rebuild +++ b/server/dev/rebuild @@ -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 From 1f7d12b388e77fddcabc51a1d95f43501ae42e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 3 Apr 2025 21:44:54 +0200 Subject: [PATCH 47/91] add `net.m1k1o.neko.api-version` label to image. --- Dockerfile.tmpl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile.tmpl b/Dockerfile.tmpl index 1bb6322f..100cd3bf 100644 --- a/Dockerfile.tmpl +++ b/Dockerfile.tmpl @@ -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 From 9eb4c365962073adcab1e81100cf08e52b5229d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 3 Apr 2025 21:46:52 +0200 Subject: [PATCH 48/91] dockerhub use github.actor. --- .github/workflows/dockerhub.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 7500bc5f..69335ed4 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -42,7 +42,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 @@ -106,7 +106,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 From 1c63b56b943834fa6a8d3ed882a14b6909a57d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 3 Apr 2025 21:50:09 +0200 Subject: [PATCH 49/91] legacyhandler: key/button action log only in trace level. --- server/internal/webrtc/legacyhandler.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/internal/webrtc/legacyhandler.go b/server/internal/webrtc/legacyhandler.go index e7d05c98..1185e6e0 100644 --- a/server/internal/webrtc/legacyhandler.go +++ b/server/internal/webrtc/legacyhandler.go @@ -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 From a75424d2f3e47dbffd7adb25f50c3bc2ef0215ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 08:59:45 +0200 Subject: [PATCH 50/91] generate legacy pipelines when specifying codec. --- server/internal/config/capture.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/server/internal/config/capture.go b/server/internal/config/capture.go index 3d8025e0..5894aa65 100644 --- a/server/internal/config/capture.go +++ b/server/internal/config/capture.go @@ -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") From 4d6ad8e17de34921dc3dc492a1ad2eb3b452318d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 21:28:05 +0200 Subject: [PATCH 51/91] update docs for v2 migration. --- webpage/docs/migration-from-v2/README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/webpage/docs/migration-from-v2/README.md b/webpage/docs/migration-from-v2/README.md index 2037ab62..25a69e69 100644 --- a/webpage/docs/migration-from-v2/README.md +++ b/webpage/docs/migration-from-v2/README.md @@ -4,13 +4,11 @@ Currently, Neko is in compatibility mode, meaning that as soon as a single V2 co The legacy mode 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 **sill used by the client**. Feel free 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)). When the new client will be 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} @@ -58,9 +56,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.pipeline) instead | +| `NEKO_HWENC` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.pipeline) instead | +| `NEKO_MAX_FPS` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.pipeline) instead | :::warning Limitation From da35b05c9cff8825fbc3ea900a75a8df8b2fc9be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 22:02:04 +0200 Subject: [PATCH 52/91] update docker images and include versioning in naming convention. --- webpage/docs/installation/docker-images.md | 111 +++++++++++++-------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/webpage/docs/installation/docker-images.md b/webpage/docs/installation/docker-images.md index ea9b1d21..0441ac41 100644 --- a/webpage/docs/installation/docker-images.md +++ b/webpage/docs/installation/docker-images.md @@ -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/[-]: ``` -- `` is the optional flavor of the image, see [Available Flavors](#flavors) for more information. -- `` is the application name or base image, see [Available Applications](#apps) for more information. -- `` 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. +- `` is the optional flavor of the image. See [Available Flavors](#flavors) for more information. +- `` is the application name or base image. See [Available Applications](#apps) for more information. +- `` 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: ``` +- `` 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} @@ -162,42 +191,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 +243,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 | ✅ | ✅ \* | ✅ \* | - | +| 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. +::: From 8b49fb7ca92dca837e86ae692ab286ab393cb3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 22:12:56 +0200 Subject: [PATCH 53/91] dockerhub ignore changes in webpage. --- .github/workflows/dockerhub.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 69335ed4..fb56a43d 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -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. From a032c9f42e7f6f7f63223af2e3b12a49e52eca89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 22:15:53 +0200 Subject: [PATCH 54/91] dockerhub: allow only one workflow to run at a time. --- .github/workflows/dockerhub.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index fb56a43d..d8489096 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -13,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 From 1e91dcd7d91a9a03cf61b7fc1aefac9c4a36927f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 22:22:10 +0200 Subject: [PATCH 55/91] add links to Availability Matrix. --- webpage/docs/installation/docker-images.md | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/webpage/docs/installation/docker-images.md b/webpage/docs/installation/docker-images.md index 0441ac41..6f5855fc 100644 --- a/webpage/docs/installation/docker-images.md +++ b/webpage/docs/installation/docker-images.md @@ -255,22 +255,22 @@ Neko Docker images are built with docker buildx and are available for multiple a 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 | ✅ | ✅ | ✅ | - | +| 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. From 936794da3176054fbd500eb28c60b8dff682cc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 4 Apr 2025 22:57:58 +0200 Subject: [PATCH 56/91] include filetransfer in migration guide. --- webpage/docs/migration-from-v2/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webpage/docs/migration-from-v2/README.md b/webpage/docs/migration-from-v2/README.md index 25a69e69..7dd8114d 100644 --- a/webpage/docs/migration-from-v2/README.md +++ b/webpage/docs/migration-from-v2/README.md @@ -40,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} From e3a1929f7f10ae73bb83b12498fc60e3b3d4a630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 5 Apr 2025 11:01:39 +0200 Subject: [PATCH 57/91] reword implicit hosting option #501. --- webpage/docs/configuration/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpage/docs/configuration/README.md b/webpage/docs/configuration/README.md index a813bf0a..9dd1195b 100644 --- a/webpage/docs/configuration/README.md +++ b/webpage/docs/configuration/README.md @@ -307,8 +307,8 @@ session: - whether logins are locked for users, admins can still login. - whether controls are locked for users, admins can still control. - users can gain control only if at least one admin is in the room. -- allows switching control implicitly without the need for explicit control request before -- whether to show inactive cursors server-wide (only for users that have it enabled in their profile) +- automatically grants control to a user when they click on the screen, unless an admin has locked the controls. +- whether to show inactive cursors server-wide (only for users that have it enabled in their profile). - 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. - 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. From 0e3bcedcd4102a501f97727777a82e3c944cd9e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 5 Apr 2025 16:53:37 +0200 Subject: [PATCH 58/91] update config options naming, move scripts to own folder. --- server/internal/config/capture.go | 4 +- server/internal/config/member.go | 16 +++--- server/internal/config/webrtc.go | 12 ++-- webpage/docs/configuration/help.json | 55 +++++++++++-------- webpage/docs/configuration/help.txt | 23 ++++---- .../developer-guide/repository-structure.md | 1 + webpage/package.json | 4 +- webpage/{ => scripts}/gen-api-docs.sh | 1 + webpage/scripts/gen-config.sh | 22 ++++++++ .../src/components/Configuration/generate.js | 10 ++++ 10 files changed, 96 insertions(+), 52 deletions(-) rename webpage/{ => scripts}/gen-api-docs.sh (95%) create mode 100755 webpage/scripts/gen-config.sh diff --git a/server/internal/config/capture.go b/server/internal/config/capture.go index 5894aa65..8b19a28f 100644 --- a/server/internal/config/capture.go +++ b/server/internal/config/capture.go @@ -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 } diff --git a/server/internal/config/member.go b/server/internal/config/member.go index c3a7d242..17486e72 100644 --- a/server/internal/config/member.go +++ b/server/internal/config/member.go @@ -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 } diff --git a/server/internal/config/webrtc.go b/server/internal/config/webrtc.go index c28593df..3178135a 100644 --- a/server/internal/config/webrtc.go +++ b/server/internal/config/webrtc.go @@ -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") } diff --git a/webpage/docs/configuration/help.json b/webpage/docs/configuration/help.json index e449cf8e..8320f7e9 100644 --- a/webpage/docs/configuration/help.json +++ b/webpage/docs/configuration/help.json @@ -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": [ diff --git a/webpage/docs/configuration/help.txt b/webpage/docs/configuration/help.txt index 9ca36858..89ef9120 100644 --- a/webpage/docs/configuration/help.txt +++ b/webpage/docs/configuration/help.txt @@ -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 diff --git a/webpage/docs/developer-guide/repository-structure.md b/webpage/docs/developer-guide/repository-structure.md index e0798e91..b8b6db78 100644 --- a/webpage/docs/developer-guide/repository-structure.md +++ b/webpage/docs/developer-guide/repository-structure.md @@ -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/). \ No newline at end of file diff --git a/webpage/package.json b/webpage/package.json index 2fd52f5b..4d4c4dce 100644 --- a/webpage/package.json +++ b/webpage/package.json @@ -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/v2-migration/help.txt ./docs/v2-migration/help.json", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "gen-api-docs": "./gen-api-docs.sh", diff --git a/webpage/gen-api-docs.sh b/webpage/scripts/gen-api-docs.sh similarity index 95% rename from webpage/gen-api-docs.sh rename to webpage/scripts/gen-api-docs.sh index 443d9737..55fb7d23 100755 --- a/webpage/gen-api-docs.sh +++ b/webpage/scripts/gen-api-docs.sh @@ -1,4 +1,5 @@ #!/bin/bash +cd "$(dirname "$0")/.." # Clean the API docs docusaurus clean-api-docs all diff --git a/webpage/scripts/gen-config.sh b/webpage/scripts/gen-config.sh new file mode 100755 index 00000000..3212d208 --- /dev/null +++ b/webpage/scripts/gen-config.sh @@ -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 diff --git a/webpage/src/components/Configuration/generate.js b/webpage/src/components/Configuration/generate.js index 1695c2e7..ecc02341 100644 --- a/webpage/src/components/Configuration/generate.js +++ b/webpage/src/components/Configuration/generate.js @@ -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; From 3a8a5c30ef41739055d39831201136bbd94966a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 5 Apr 2025 18:25:04 +0200 Subject: [PATCH 59/91] docs: use ConfigurationTab that allows switching between yaml, env and cmd. --- webpage/docs/configuration/README.md | 66 ++-- webpage/docs/configuration/authentication.md | 118 ++++--- webpage/docs/configuration/capture.md | 97 +++--- webpage/docs/configuration/desktop.md | 44 +-- webpage/docs/configuration/plugins.md | 36 +- webpage/docs/configuration/webrtc.md | 121 ++----- webpage/docs/migration-from-v2/README.md | 10 +- .../src/components/Configuration/index.tsx | 328 ++++++++++++------ 8 files changed, 430 insertions(+), 390 deletions(-) diff --git a/webpage/docs/configuration/README.md b/webpage/docs/configuration/README.md index 9dd1195b..f5127104 100644 --- a/webpage/docs/configuration/README.md +++ b/webpage/docs/configuration/README.md @@ -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,17 +293,16 @@ 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 -``` + - whether private mode is enabled, users do not receive the room video or audio. - whether logins are locked for users, admins can still login. @@ -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" -``` + - 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. - and 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: - json: true - level: "info" - nocolor: true - time: "unix" -``` + - directory to store logs. If empty, logs are written to stdout. This is useful when running neko in a container. - 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'; - - + ## Next Steps {#next} diff --git a/webpage/docs/configuration/authentication.md b/webpage/docs/configuration/authentication.md index 3fd1f758..a0c9962c 100644 --- a/webpage/docs/configuration/authentication.md +++ b/webpage/docs/configuration/authentication.md @@ -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 @@ -86,21 +88,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: - ... -``` +
See example configuration @@ -152,8 +158,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 +167,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 -``` + 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 +247,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: - ... -``` +
See example configuration @@ -296,10 +299,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 -``` + :::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 +313,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 -``` + :::info In the future, we plan to add more session providers, such as Redis, PostgreSQL, etc. So the Configuration Options may change. @@ -324,10 +325,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" -``` +', +}} 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 +347,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: "" -``` + - - Whether the cookies are enabled or not. - - Name of the cookie used to store the session. diff --git a/webpage/docs/configuration/capture.md b/webpage/docs/configuration/capture.md index 2778c0d5..63949fb5 100644 --- a/webpage/docs/configuration/capture.md +++ b/webpage/docs/configuration/capture.md @@ -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 " - codec: "vp8" # default video codec - ids: [ , , ... ] - pipelines: - : - : - ... -``` + - 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. - 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. - is a list of pipeline ids that are defined in the section. The first pipeline in the list will be the default pipeline. -- 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). +- is a shorthand for defining [Gstreamer pipeline description](#video.gst_pipeline) for a single pipeline. This is option is ignored if is defined. +- 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';
-### 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 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 ! ! appsink name=appsink" +ximagesrc display-name={display} show-pointer=true use-damage=false ! ! 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. @@ -258,13 +257,11 @@ Only one audio pipeline can be defined in neko. The audio pipeline is used to ca 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: "" -``` + - 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. - 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 +290,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: "" - url: "rtmp:////" - autostart: true -``` + 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 parameter. - and are the bitrate settings for the default audio and video encoders expressed in kilobits per second. - 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). - 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. -- 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. +- is the URL of the RTMP server where the broadcast will be sent e.g. `rtmp:////`. This can be set later using the API if the URL is not known at the time of configuration or is expected to change. - is a boolean value that determines whether the broadcast should start automatically when neko starts, works only if the URL is set.
@@ -380,14 +375,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: "" -``` + - is a boolean value that determines whether the screencast is enabled or not. - 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 +415,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 -``` + - is a boolean value that determines whether the webcam capture is enabled or not. - 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 +454,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" -``` + - is a boolean value that determines whether the microphone capture is enabled or not. - is the name of the [pulseaudio device](https://wiki.archlinux.org/title/PulseAudio/Examples) that will be used as a virtual microphone. diff --git a/webpage/docs/configuration/desktop.md b/webpage/docs/configuration/desktop.md index d1a3f47f..b492e9ce 100644 --- a/webpage/docs/configuration/desktop.md +++ b/webpage/docs/configuration/desktop.md @@ -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: "" - screen: "1280x720@30" # default -``` + - 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. - refers to the screen resolution and refresh rate. The format is `x@`. 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 -``` + - enables the input device support. If not specified, the default is `false`. - 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 -``` + ## 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 -``` + ## 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 -``` + diff --git a/webpage/docs/configuration/plugins.md b/webpage/docs/configuration/plugins.md index 9d6fc85a..5f6d5e38 100644 --- a/webpage/docs/configuration/plugins.md +++ b/webpage/docs/configuration/plugins.md @@ -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" -``` + - enables the plugin support. If set to `false`, the plugins are not loaded. - 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 -``` + - 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 -``` + + - enables the file transfer support. If set to `false`, the file transfer is disabled. - 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. - diff --git a/webpage/docs/configuration/webrtc.md b/webpage/docs/configuration/webrtc.md index 47c5face..6606480f 100644 --- a/webpage/docs/configuration/webrtc.md +++ b/webpage/docs/configuration/webrtc.md @@ -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 -``` + ### 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 -``` + :::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" -``` + - - ICE servers that are sent to the client and used to establish a connection between the client and the server. - - 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" -``` + 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 -``` + - - The port used for UDP connections. - - 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} + -```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" -``` + + 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 -``` + diff --git a/webpage/docs/migration-from-v2/README.md b/webpage/docs/migration-from-v2/README.md index 7dd8114d..d8f10d53 100644 --- a/webpage/docs/migration-from-v2/README.md +++ b/webpage/docs/migration-from-v2/README.md @@ -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](/docs/v3/configuration/capture#video.pipeline) instead | -| `NEKO_HWENC` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.pipeline) instead | -| `NEKO_MAX_FPS` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.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'; - + See the full [V3 configuration reference](/docs/v3/configuration/#full) for more details. diff --git a/webpage/src/components/Configuration/index.tsx b/webpage/src/components/Configuration/index.tsx index 1497cd87..79f2422c 100644 --- a/webpage/src/components/Configuration/index.tsx +++ b/webpage/src/components/Configuration/index.tsx @@ -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 = ({ 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 ( - - {code} - - ); +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): 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}"` : ''}`; + case 'strings': + return ''; + case 'object': + return ''; + case 'array': + return ''; + 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 ( - - {code} - - ); + 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 ( + + {code} + + ); +} + +export function CommandLineArguments({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) { + if (typeof comments === 'undefined') { + comments = true; } - const yamlFile = () => { - const final = Symbol('final'); - - 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 + ' '); + 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`; + }); + + return ( + + {code} + + ); +} + +export function YamlFileContent({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) { + if (typeof comments === 'undefined') { + comments = true; + } + + const final = Symbol('final'); + + const buildYaml = (obj: Record, 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}" ]` : '[ ]'; + } 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(', ') : ''} ]`; - break; - case 'duration': - case 'string': - val = `${value.defaultValue ? `"${value.defaultValue}"` : ''}`; - 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 ( - - {yamlCode} - - ); + return ( + + {yamlCode} + + ); +} + +type ConfigurationTabProps = { + options?: ConfigOption[] | Record; + heading?: boolean; + comments?: boolean; + filter?: string | string[] | Record; +}; + +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 ( -
- - + + + {heading && (

You can set the following environment variables in your docker-compose.yaml file or in your shell environment.

- {environmentVariables()} -
- + )} + {EnvironmentVariables({ options: configOptions, comments })} + + + {heading && (

You can list the following command line arguments using neko serve --help.

- {cmdArguments()} -
- + )} + {CommandLineArguments({ options: configOptions, comments })} + + + {heading && (

You can create a /etc/neko/neko.yaml file with the following configuration options.

- {yamlFile()} -
-
-
+ )} + {YamlFileContent({ options: configOptions, comments })} + + ); -}; - -export default ConfigurationTab; +} From 972d16031eb49cf81cbcc2cd1b99586524ec754b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 5 Apr 2025 18:25:28 +0200 Subject: [PATCH 60/91] docs: add missing filetransfer config to v2. --- webpage/docs/migration-from-v2/help.json | 183 ++++++++++++----------- webpage/docs/migration-from-v2/help.txt | 36 +++-- webpage/package.json | 2 +- 3 files changed, 120 insertions(+), 101 deletions(-) diff --git a/webpage/docs/migration-from-v2/help.json b/webpage/docs/migration-from-v2/help.json index a0c4aa84..0110d40f 100644 --- a/webpage/docs/migration-from-v2/help.json +++ b/webpage/docs/migration-from-v2/help.json @@ -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": [ diff --git a/webpage/docs/migration-from-v2/help.txt b/webpage/docs/migration-from-v2/help.txt index 84d0db36..097f114f 100644 --- a/webpage/docs/migration-from-v2/help.txt +++ b/webpage/docs/migration-from-v2/help.txt @@ -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 diff --git a/webpage/package.json b/webpage/package.json index 4d4c4dce..1eddf2a6 100644 --- a/webpage/package.json +++ b/webpage/package.json @@ -11,7 +11,7 @@ "clear": "docusaurus clear", "serve": "docusaurus serve", "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/v2-migration/help.txt ./docs/v2-migration/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", From 81c259cdc9e2c5536f99ea53fd5f6d9915730273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 5 Apr 2025 21:45:19 +0200 Subject: [PATCH 61/91] docs: add nvidis gpu examples. #502 --- webpage/docs/configuration/capture.md | 39 +++++++- webpage/docs/installation/examples.md | 138 +++++++++++++++++++++----- 2 files changed, 150 insertions(+), 27 deletions(-) diff --git a/webpage/docs/configuration/capture.md b/webpage/docs/configuration/capture.md index 63949fb5..c0c95118 100644 --- a/webpage/docs/configuration/capture.md +++ b/webpage/docs/configuration/capture.md @@ -188,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 @@ -206,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 @@ -235,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 @@ -247,6 +250,36 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio ! appsink name=appsink ``` + + + ```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. + +
diff --git a/webpage/docs/installation/examples.md b/webpage/docs/installation/examples.md index fca12b31..ee229cae 100644 --- a/webpage/docs/installation/examples.md +++ b/webpage/docs/installation/examples.md @@ -76,30 +76,11 @@ services: NEKO_WEBRTC_NAT1TO1: ``` -## 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] +``` From b783a9adbefe125441c726c0eb50c09871098309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 5 Apr 2025 22:37:56 +0200 Subject: [PATCH 62/91] docs: add overview of available encoders. --- webpage/docs/configuration/capture.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/webpage/docs/configuration/capture.md b/webpage/docs/configuration/capture.md index c0c95118..be998e71 100644 --- a/webpage/docs/configuration/capture.md +++ b/webpage/docs/configuration/capture.md @@ -284,6 +284,17 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
+Overview of available encoders for each codec is shown in the table below. The encoder name is used in the 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. From 6c5cd1260d208f6f0e7362399a343df0bcbef2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 6 Apr 2025 16:16:45 +0200 Subject: [PATCH 63/91] websocket: fix unwrap err. --- server/internal/websocket/manager.go | 4 +++- server/internal/websocket/peer.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/server/internal/websocket/manager.go b/server/internal/websocket/manager.go index 0f7baafd..9fc21f60 100644 --- a/server/internal/websocket/manager.go +++ b/server/internal/websocket/manager.go @@ -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 diff --git a/server/internal/websocket/peer.go b/server/internal/websocket/peer.go index 3861e3a0..0c2fd553 100644 --- a/server/internal/websocket/peer.go +++ b/server/internal/websocket/peer.go @@ -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 } From b8bfcaf4bf2ff6afac9120765830e8dd12339770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 6 Apr 2025 16:19:40 +0200 Subject: [PATCH 64/91] legacy: fix logging. --- server/internal/http/legacy/handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/internal/http/legacy/handler.go b/server/internal/http/legacy/handler.go index 8199ee76..27917895 100644 --- a/server/internal/http/legacy/handler.go +++ b/server/internal/http/legacy/handler.go @@ -181,9 +181,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 { From 3c787baa40d01b2a79ec1839e5db54b849c2393d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 6 Apr 2025 17:10:22 +0200 Subject: [PATCH 65/91] legacy: forward ws ping messages #506. --- server/internal/http/legacy/handler.go | 12 ++++++++++-- webpage/docs/reverse-proxy-setup.md | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/server/internal/http/legacy/handler.go b/server/internal/http/legacy/handler.go index 27917895..ca3dde56 100644 --- a/server/internal/http/legacy/handler.go +++ b/server/internal/http/legacy/handler.go @@ -142,7 +142,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 +163,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 + } + } } } diff --git a/webpage/docs/reverse-proxy-setup.md b/webpage/docs/reverse-proxy-setup.md index e2a5bf95..95bb13fb 100644 --- a/webpage/docs/reverse-proxy-setup.md +++ b/webpage/docs/reverse-proxy-setup.md @@ -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. From 007b55a32be2d6e10e91a1f058256b673e1d65dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 6 Apr 2025 17:22:34 +0200 Subject: [PATCH 66/91] add link to docs in V2 configuration warning message. --- server/internal/config/capture.go | 2 +- server/internal/config/desktop.go | 2 +- server/internal/config/member.go | 2 +- server/internal/config/root.go | 2 +- server/internal/config/server.go | 2 +- server/internal/config/session.go | 2 +- server/internal/config/webrtc.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/internal/config/capture.go b/server/internal/config/capture.go index 8b19a28f..298e69cd 100644 --- a/server/internal/config/capture.go +++ b/server/internal/config/capture.go @@ -616,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) } } diff --git a/server/internal/config/desktop.go b/server/internal/config/desktop.go index 2814b697..8331035e 100644 --- a/server/internal/config/desktop.go +++ b/server/internal/config/desktop.go @@ -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) } } diff --git a/server/internal/config/member.go b/server/internal/config/member.go index 17486e72..db9c5daa 100644 --- a/server/internal/config/member.go +++ b/server/internal/config/member.go @@ -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) } } diff --git a/server/internal/config/root.go b/server/internal/config/root.go index ab6ddc8b..c8902087 100644 --- a/server/internal/config/root.go +++ b/server/internal/config/root.go @@ -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) } } diff --git a/server/internal/config/server.go b/server/internal/config/server.go index 8491d43c..99be15f4 100644 --- a/server/internal/config/server.go +++ b/server/internal/config/server.go @@ -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) } } diff --git a/server/internal/config/session.go b/server/internal/config/session.go index 4349ee9c..a38d05aa 100644 --- a/server/internal/config/session.go +++ b/server/internal/config/session.go @@ -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) } } diff --git a/server/internal/config/webrtc.go b/server/internal/config/webrtc.go index 3178135a..bc202d70 100644 --- a/server/internal/config/webrtc.go +++ b/server/internal/config/webrtc.go @@ -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) } } From 46e9b19e0977e55a943fdacc32e739af438edd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 6 Apr 2025 17:29:56 +0200 Subject: [PATCH 67/91] docs: legacy mode explained. --- webpage/docs/migration-from-v2/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webpage/docs/migration-from-v2/README.md b/webpage/docs/migration-from-v2/README.md index d8f10d53..b8f9bded 100644 --- a/webpage/docs/migration-from-v2/README.md +++ b/webpage/docs/migration-from-v2/README.md @@ -2,10 +2,10 @@ 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`. :::warning -The legacy mode is **sill used by the client**. Feel free 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)). When the new client will be released, the legacy mode will be automatically removed from the server. +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. ::: 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. @@ -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. From 4eec843ed20007beb4ebfd0b01d7154f5b3c4948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 6 Apr 2025 19:55:35 +0200 Subject: [PATCH 68/91] https: if we have legacy mode, we need to start local http server too. (#507) --- server/internal/http/manager.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/server/internal/http/manager.go b/server/internal/http/manager.go index b5753811..acea79db 100644 --- a/server/internal/http/manager.go +++ b/server/internal/http/manager.go @@ -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(config.Bind).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) + } } } From 2ec35d9d0c07168f48885147c03c3c7c415829ab Mon Sep 17 00:00:00 2001 From: Sean Ezrol Date: Sun, 6 Apr 2025 17:33:52 -0400 Subject: [PATCH 69/91] Adds an https condition to the healthcheck (#503) * Adds an https condition to the healthcheck * Fix V2 to V3 naming change * Update other dockerfiles --- runtime/Dockerfile | 4 +++- runtime/Dockerfile.bookworm | 5 +++-- runtime/Dockerfile.intel | 5 +++-- runtime/Dockerfile.nvidia | 4 +++- runtime/Dockerfile.nvidia.bookworm | 4 +++- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/runtime/Dockerfile b/runtime/Dockerfile index 880781be..b4c165ef 100644 --- a/runtime/Dockerfile +++ b/runtime/Dockerfile @@ -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 diff --git a/runtime/Dockerfile.bookworm b/runtime/Dockerfile.bookworm index 07e8e6b9..b53e1ad0 100644 --- a/runtime/Dockerfile.bookworm +++ b/runtime/Dockerfile.bookworm @@ -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"] diff --git a/runtime/Dockerfile.intel b/runtime/Dockerfile.intel index 657cd8af..bb44c189 100644 --- a/runtime/Dockerfile.intel +++ b/runtime/Dockerfile.intel @@ -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"] diff --git a/runtime/Dockerfile.nvidia b/runtime/Dockerfile.nvidia index 88f1c1e0..e6d03a38 100644 --- a/runtime/Dockerfile.nvidia +++ b/runtime/Dockerfile.nvidia @@ -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 diff --git a/runtime/Dockerfile.nvidia.bookworm b/runtime/Dockerfile.nvidia.bookworm index 7798c6ff..23dd8e63 100644 --- a/runtime/Dockerfile.nvidia.bookworm +++ b/runtime/Dockerfile.nvidia.bookworm @@ -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 From b2219396ddba20fd0b199fad25566d07449d980d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 7 Apr 2025 19:33:58 +0200 Subject: [PATCH 70/91] disable proxy for local requests, #509. --- server/internal/http/legacy/handler.go | 11 +++++++---- server/internal/http/legacy/session.go | 9 +++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/server/internal/http/legacy/handler.go b/server/internal/http/legacy/handler.go index ca3dde56..dc128ade 100644 --- a/server/internal/http/legacy/handler.go +++ b/server/internal/http/legacy/handler.go @@ -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,6 +43,7 @@ type LegacyHandler struct { serverAddr string bannedIPs map[string]struct{} sessionIPs map[string]string + wsDialer *websocket.Dialer } func New(serverAddr string) *LegacyHandler { @@ -55,6 +54,10 @@ func New(serverAddr string) *LegacyHandler { 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") diff --git a/server/internal/http/legacy/session.go b/server/internal/http/legacy/session.go index bd28aa03..02f45ec0 100644 --- a/server/internal/http/legacy/session.go +++ b/server/internal/http/legacy/session.go @@ -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), } } From a46cb0536b9e70d6c27e5edfb7bccf550c2a0932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 7 Apr 2025 22:43:08 +0200 Subject: [PATCH 71/91] update user agent for waterfox. they seem to block `Wget/` user agents. --- apps/waterfox/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/waterfox/Dockerfile b/apps/waterfox/Dockerfile index 8d59e214..7dbf9d80 100644 --- a/apps/waterfox/Dockerfile +++ b/apps/waterfox/Dockerfile @@ -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; \ From c1ccae4ac445732a3e8d67ffa5bf5acc46cbc285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 7 Apr 2025 22:44:59 +0200 Subject: [PATCH 72/91] docs: info about supporting only one provider at a time. #505 --- webpage/docs/configuration/authentication.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webpage/docs/configuration/authentication.md b/webpage/docs/configuration/authentication.md index a0c9962c..8bf73322 100644 --- a/webpage/docs/configuration/authentication.md +++ b/webpage/docs/configuration/authentication.md @@ -80,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. From c9ca4e7144b2dc19eb6db42f4bf896c170503bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 12 Apr 2025 23:38:52 +0200 Subject: [PATCH 73/91] docs firefox: guide on how to find extension ID. --- webpage/docs/customization/browsers.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webpage/docs/customization/browsers.md b/webpage/docs/customization/browsers.md index 10dbdc37..6b3c5c0c 100644 --- a/webpage/docs/customization/browsers.md +++ b/webpage/docs/customization/browsers.md @@ -158,6 +158,20 @@ By default, the browsers in Neko do not allow installing extensions except for t } ``` +
+ How to find the extension ID? + + 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 (F12) 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. +
+ ### 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. From a8cc365e0fa93c8688aad49008e355d6a8ef6a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 12 Apr 2025 23:41:07 +0200 Subject: [PATCH 74/91] firefox: comment out xpinstall prefs. #512 --- apps/firefox/neko.js | 4 ++-- apps/waterfox/neko.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/firefox/neko.js b/apps/firefox/neko.js index a55e1e51..0552774a 100644 --- a/apps/firefox/neko.js +++ b/apps/firefox/neko.js @@ -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); diff --git a/apps/waterfox/neko.js b/apps/waterfox/neko.js index a55e1e51..0552774a 100644 --- a/apps/waterfox/neko.js +++ b/apps/waterfox/neko.js @@ -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); From fc3b3a4dc6f6ba0b36fc3ae75aec204730c5d8fe Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 18 Apr 2025 16:56:57 +1000 Subject: [PATCH 75/91] fix: link to Examples in docs/v3 (#514) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad5c7b1c..33aace77 100644 --- a/README.md +++ b/README.md @@ -158,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) From 01112c5e8f41f002fdf88b97ae817e6726a77d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 19 Apr 2025 10:23:16 +0200 Subject: [PATCH 76/91] clipboard: use UTF8_STRING. #517 --- server/internal/desktop/clipboard.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server/internal/desktop/clipboard.go b/server/internal/desktop/clipboard.go index 49c2f00f..b67ecff0 100644 --- a/server/internal/desktop/clipboard.go +++ b/server/internal/desktop/clipboard.go @@ -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) { From 73e61de52e8ceeec39ab9ac08592cd65639d3c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 21 Apr 2025 16:03:51 +0200 Subject: [PATCH 77/91] add SECURITY.md file. --- SECURITY.md | 19 +++++++++++++++++++ webpage/src/pages/contact.md | 8 ++++++++ webpage/static/.well-known/security.txt | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 SECURITY.md create mode 100644 webpage/static/.well-known/security.txt diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..7db0c453 --- /dev/null +++ b/SECURITY.md @@ -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. diff --git a/webpage/src/pages/contact.md b/webpage/src/pages/contact.md index fec9db6a..42ea917f 100644 --- a/webpage/src/pages/contact.md +++ b/webpage/src/pages/contact.md @@ -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 neko@m1k1o.net. + +**Security** + +If you discover a security vulnerability in Neko, please report it to us directly at security@m1k1o.net. 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. diff --git a/webpage/static/.well-known/security.txt b/webpage/static/.well-known/security.txt new file mode 100644 index 00000000..15c7ba55 --- /dev/null +++ b/webpage/static/.well-known/security.txt @@ -0,0 +1,3 @@ +Contact: mailto:security@m1k1o.net +Policy: https://github.com/m1k1o/neko/security +Expires: 2030-04-01T00:00:00z From 11cef143a30b6651fce49a14dfa18aa6311d2dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 21 Apr 2025 16:11:46 +0200 Subject: [PATCH 78/91] temporarily disable waterfox build due to Cloudflare blocked download link. --- .github/workflows/dockerhub.yml | 3 ++- .github/workflows/ghcr.yml | 5 +++-- .github/workflows/ghcr_intel.yml | 3 ++- webpage/docs/installation/docker-images.md | 4 ++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index d8489096..1d3964fb 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -80,7 +80,8 @@ jobs: matrix: tag: - firefox - - waterfox + # Temporarily disabled due to Cloudflare blocked download link + #- waterfox - chromium - google-chrome - ungoogled-chromium diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 49b0e3cb..2ab82986 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -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 diff --git a/.github/workflows/ghcr_intel.yml b/.github/workflows/ghcr_intel.yml index 1bc93a31..a6ee46b0 100644 --- a/.github/workflows/ghcr_intel.yml +++ b/.github/workflows/ghcr_intel.yml @@ -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 diff --git a/webpage/docs/installation/docker-images.md b/webpage/docs/installation/docker-images.md index 6f5855fc..2385d4b5 100644 --- a/webpage/docs/installation/docker-images.md +++ b/webpage/docs/installation/docker-images.md @@ -75,6 +75,10 @@ In comparison to Chromium-based browsers, Firefox-based browsers do not require | | [Tor Browser](https://www.torproject.org/)
A browser designed to access the Tor network for enhanced privacy. | [`ghcr.io/m1k1o/neko/tor-browser`](https://ghcr.io/m1k1o/neko/tor-browser) | | | [Waterfox](https://www.waterfox.net/)
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} From 33ce337cd34cb57d3be0841486f3909747d1c69a Mon Sep 17 00:00:00 2001 From: Xiuming Chen Date: Tue, 22 Apr 2025 00:35:09 -0700 Subject: [PATCH 79/91] Fix docker volume mount error in build script. (#519) --- build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build b/build index 5b84d043..c5551c55 100755 --- a/build +++ b/build @@ -313,7 +313,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 \ From 23820f62557eb340063469d6c9de6c346e3efe89 Mon Sep 17 00:00:00 2001 From: Xiuming Chen Date: Tue, 22 Apr 2025 11:19:20 -0700 Subject: [PATCH 80/91] Fix build script for Apple Silicon MacOS. (#520) --- build | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build b/build index c5551c55..9c311608 100755 --- a/build +++ b/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 From f145bd58c9d767307a285f2c1eb4e498ba4c6cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 26 Apr 2025 11:55:55 +0200 Subject: [PATCH 81/91] Fix mobile keyboard behavior (#522) * keep only openMobileKeyboard, do not focus when touch device. * do not test for hover when checking touch device. --- client/src/components/video.vue | 71 ++++++--------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 19dad1a2..229eff57 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -65,7 +65,7 @@
  • @@ -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() } } From 0765352abd3bd07220b8bfab7df481ac5dd4e53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 27 Apr 2025 09:43:50 +0200 Subject: [PATCH 82/91] fix regression for #522. #523 --- client/src/components/video.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 229eff57..a420c974 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -822,7 +822,7 @@ @Watch('locked') onFocus() { // focus opens the keyboard on mobile - if (!this.is_touch_device) { + if (this.is_touch_device) { return } From 3dfd89ec39001806071d353d82f3c0110128bbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Wed, 30 Apr 2025 21:34:42 +0200 Subject: [PATCH 83/91] forward pong messages, #510. --- server/internal/http/legacy/handler.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/server/internal/http/legacy/handler.go b/server/internal/http/legacy/handler.go index dc128ade..bfd29f3a 100644 --- a/server/internal/http/legacy/handler.go +++ b/server/internal/http/legacy/handler.go @@ -149,6 +149,8 @@ func (h *LegacyHandler) Route(r types.Router) { dst.WriteMessage(websocket.CloseMessage, m) break } + + // handle text messages if msgType == websocket.TextMessage { err = rewriteTextMessage(msg) @@ -165,20 +167,26 @@ func (h *LegacyHandler) Route(r types.Router) { Message: strings.ReplaceAll(err.Error(), ErrBackendRespone.Error()+": ", ""), }) continue - } else if errors.Is(err, ErrWebsocketSend) { + } + + if errors.Is(err, ErrWebsocketSend) { errc <- fmt.Errorf("dst write message error: %w", err) break - } else { - h.logger.Error().Err(err).Msg("couldn't rewrite text message") } + + h.logger.Error().Err(err).Msg("couldn't rewrite text message") + continue } - // forward ping messages - if msgType == websocket.PingMessage { - err = dst.WriteMessage(websocket.PingMessage, nil) + + // forward ping pong messages + if msgType == websocket.PingMessage || + msgType == websocket.PongMessage { + err = dst.WriteMessage(msgType, msg) if err != nil { errc <- err break } + continue } } } From 725d5396c0593735bab13c63742efb224ee7118f Mon Sep 17 00:00:00 2001 From: camerony Date: Fri, 16 May 2025 01:52:54 -0400 Subject: [PATCH 84/91] Fix typo in quick-start guide for local network setup (#533) --- webpage/docs/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpage/docs/quick-start.md b/webpage/docs/quick-start.md index 2b4a15f7..3216c0f3 100644 --- a/webpage/docs/quick-start.md +++ b/webpage/docs/quick-start.md @@ -46,7 +46,7 @@ Neko is easy to use and requires no technical expertise to get started. All you ``` :::note - If you want to run Neko on your local network, you have to add `NEKO_NAT1TO1=` to the `docker-compose.yaml` file. + If you want to run Neko on your local network, you have to add `NEKO_NAT1TO1: ` to the `docker-compose.yaml` file. ::: 6. Visit the server's IP address in your browser and log in, the default password is `neko`. From 875a61f10cbd415b3744ce32619367f971e2e317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Wed, 28 May 2025 20:14:39 +0200 Subject: [PATCH 85/91] Update VirtualGL to a more recent version (#538) Fixes #536 Fixes #537 Fixes #279 thanks to @TobyColeman --- apps/brave/supervisord.nvidia.conf | 4 ++- apps/chromium/supervisord.nvidia.conf | 4 ++- apps/google-chrome/supervisord.nvidia.conf | 4 ++- apps/microsoft-edge/supervisord.nvidia.conf | 4 ++- runtime/Dockerfile.nvidia | 33 ++++++++++----------- runtime/Dockerfile.nvidia.bookworm | 33 ++++++++++----------- 6 files changed, 44 insertions(+), 38 deletions(-) diff --git a/apps/brave/supervisord.nvidia.conf b/apps/brave/supervisord.nvidia.conf index 995886a9..ef25d415 100644 --- a/apps/brave/supervisord.nvidia.conf +++ b/apps/brave/supervisord.nvidia.conf @@ -12,9 +12,11 @@ command=/bin/entrypoint.sh /usr/bin/brave-browser --enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization --ignore-gpu-blocklist --disable-seccomp-filter-sandbox - --use-gl=egl + --use-angle=vulkan --disable-software-rasterizer --disable-dev-shm-usage + --disable-vulkan-surface + --enable-unsafe-webgpu stopsignal=INT autorestart=true priority=800 diff --git a/apps/chromium/supervisord.nvidia.conf b/apps/chromium/supervisord.nvidia.conf index 7eb6785d..2f83368d 100644 --- a/apps/chromium/supervisord.nvidia.conf +++ b/apps/chromium/supervisord.nvidia.conf @@ -12,9 +12,11 @@ command=/bin/entrypoint.sh /usr/bin/chromium --enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization --ignore-gpu-blocklist --disable-seccomp-filter-sandbox - --use-gl=egl + --use-angle=vulkan --disable-software-rasterizer --disable-dev-shm-usage + --disable-vulkan-surface + --enable-unsafe-webgpu stopsignal=INT autorestart=true priority=800 diff --git a/apps/google-chrome/supervisord.nvidia.conf b/apps/google-chrome/supervisord.nvidia.conf index f3821fd0..1e6ca2ed 100644 --- a/apps/google-chrome/supervisord.nvidia.conf +++ b/apps/google-chrome/supervisord.nvidia.conf @@ -12,9 +12,11 @@ command=/bin/entrypoint.sh /usr/bin/google-chrome --enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization --ignore-gpu-blocklist --disable-seccomp-filter-sandbox - --use-gl=egl + --use-angle=vulkan --disable-software-rasterizer --disable-dev-shm-usage + --disable-vulkan-surface + --enable-unsafe-webgpu stopsignal=INT autorestart=true priority=800 diff --git a/apps/microsoft-edge/supervisord.nvidia.conf b/apps/microsoft-edge/supervisord.nvidia.conf index 971bb3b7..beb2df4d 100644 --- a/apps/microsoft-edge/supervisord.nvidia.conf +++ b/apps/microsoft-edge/supervisord.nvidia.conf @@ -12,9 +12,11 @@ command=/bin/entrypoint.sh /usr/bin/microsoft-edge --enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization --ignore-gpu-blocklist --disable-seccomp-filter-sandbox - --use-gl=egl + --use-angle=vulkan --disable-software-rasterizer --disable-dev-shm-usage + --disable-vulkan-surface + --enable-unsafe-webgpu stopsignal=INT autorestart=true priority=800 diff --git a/runtime/Dockerfile.nvidia b/runtime/Dockerfile.nvidia index e6d03a38..1cf652c5 100644 --- a/runtime/Dockerfile.nvidia +++ b/runtime/Dockerfile.nvidia @@ -1,6 +1,6 @@ ARG UBUNTU_RELEASE=20.04 ARG CUDA_VERSION=11.4.3 -ARG VIRTUALGL_VERSION=3.1 +ARG VIRTUALGL_VERSION=3.1.3-20250409 ARG GSTREAMER_VERSION=1.20 # @@ -61,13 +61,13 @@ ARG UBUNTU_RELEASE ARG VIRTUALGL_VERSION # Make all NVIDIA GPUs visible by default -ENV NVIDIA_VISIBLE_DEVICES all +ENV NVIDIA_VISIBLE_DEVICES=all # All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work -ENV NVIDIA_DRIVER_CAPABILITIES all +ENV NVIDIA_DRIVER_CAPABILITIES=all # # set vgl-display to headless 3d gpu card/// correct values are egl[n] or /dev/dri/card0:if this is passed into container -ENV VGL_DISPLAY egl +ENV VGL_DISPLAY=egl # # set custom user @@ -205,23 +205,22 @@ RUN VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | gr }" > /etc/vulkan/icd.d/nvidia_icd.json # -# install VirtualGL and make libraries available for preload -RUN set -eux; \ +# install an up-to-date version of VirtualGL +RUN apt-get update; \ + apt-get install -y --no-install-recommends wget gpg ca-certificates; \ + # Add VirtualGL GPG key + wget -q -O- https://packagecloud.io/dcommander/virtualgl/gpgkey | \ + gpg --dearmor >/etc/apt/trusted.gpg.d/VirtualGL.gpg; \ + # Download the official VirtualGL.list file + wget -q -O /etc/apt/sources.list.d/VirtualGL.list \ + https://raw.githubusercontent.com/VirtualGL/repo/main/VirtualGL.list; \ + # Install packages apt-get update; \ - wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \ - wget "https://sourceforge.net/projects/virtualgl/files/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \ - apt-get install -y --no-install-recommends ./virtualgl_${VIRTUALGL_VERSION}_amd64.deb ./virtualgl32_${VIRTUALGL_VERSION}_amd64.deb; \ - rm -f "virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \ - chmod u+s /usr/lib/libvglfaker.so; \ - chmod u+s /usr/lib/libdlfaker.so; \ - chmod u+s /usr/lib32/libvglfaker.so; \ - chmod u+s /usr/lib32/libdlfaker.so; \ - chmod u+s /usr/lib/i386-linux-gnu/libvglfaker.so; \ - chmod u+s /usr/lib/i386-linux-gnu/libdlfaker.so; \ + apt-get install -y --no-install-recommends virtualgl=${VIRTUALGL_VERSION}; \ # # clean up apt-get clean -y; \ - rm -rf /var/lib/apt/lists/* /var/cache/apt/*; + rm -rf /var/lib/apt/lists/* /var/cache/apt/* # # copy runtime configs diff --git a/runtime/Dockerfile.nvidia.bookworm b/runtime/Dockerfile.nvidia.bookworm index 23dd8e63..0e3c3c1d 100644 --- a/runtime/Dockerfile.nvidia.bookworm +++ b/runtime/Dockerfile.nvidia.bookworm @@ -1,6 +1,6 @@ ARG UBUNTU_RELEASE=22.04 ARG CUDA_VERSION=12.2.0 -ARG VIRTUALGL_VERSION=3.1 +ARG VIRTUALGL_VERSION=3.1.3-20250409 ARG GSTREAMER_VERSION=1.22 # @@ -61,13 +61,13 @@ ARG UBUNTU_RELEASE ARG VIRTUALGL_VERSION # Make all NVIDIA GPUs visible by default -ENV NVIDIA_VISIBLE_DEVICES all +ENV NVIDIA_VISIBLE_DEVICES=all # All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work -ENV NVIDIA_DRIVER_CAPABILITIES all +ENV NVIDIA_DRIVER_CAPABILITIES=all # # set vgl-display to headless 3d gpu card/// correct values are egl[n] or /dev/dri/card0:if this is passed into container -ENV VGL_DISPLAY egl +ENV VGL_DISPLAY=egl # # set custom user @@ -199,23 +199,22 @@ RUN VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | gr }" > /etc/vulkan/icd.d/nvidia_icd.json # -# install VirtualGL and make libraries available for preload -RUN set -eux; \ +# install an up-to-date version of VirtualGL +RUN apt-get update; \ + apt-get install -y --no-install-recommends wget gpg ca-certificates; \ + # Add VirtualGL GPG key + wget -q -O- https://packagecloud.io/dcommander/virtualgl/gpgkey | \ + gpg --dearmor >/etc/apt/trusted.gpg.d/VirtualGL.gpg; \ + # Download the official VirtualGL.list file + wget -q -O /etc/apt/sources.list.d/VirtualGL.list \ + https://raw.githubusercontent.com/VirtualGL/repo/main/VirtualGL.list; \ + # Install packages apt-get update; \ - wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \ - wget "https://sourceforge.net/projects/virtualgl/files/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \ - apt-get install -y --no-install-recommends ./virtualgl_${VIRTUALGL_VERSION}_amd64.deb ./virtualgl32_${VIRTUALGL_VERSION}_amd64.deb; \ - rm -f "virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \ - chmod u+s /usr/lib/libvglfaker.so; \ - chmod u+s /usr/lib/libdlfaker.so; \ - chmod u+s /usr/lib32/libvglfaker.so; \ - chmod u+s /usr/lib32/libdlfaker.so; \ - chmod u+s /usr/lib/i386-linux-gnu/libvglfaker.so; \ - chmod u+s /usr/lib/i386-linux-gnu/libdlfaker.so; \ + apt-get install -y --no-install-recommends virtualgl=${VIRTUALGL_VERSION}; \ # # clean up apt-get clean -y; \ - rm -rf /var/lib/apt/lists/* /var/cache/apt/*; + rm -rf /var/lib/apt/lists/* /var/cache/apt/* # # copy runtime configs From d530b759f55557080783767103e73fb178ad6309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Wed, 28 May 2025 20:16:27 +0200 Subject: [PATCH 86/91] update docs. --- webpage/docs/installation/docker-images.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/webpage/docs/installation/docker-images.md b/webpage/docs/installation/docker-images.md index 2385d4b5..44b4d903 100644 --- a/webpage/docs/installation/docker-images.md +++ b/webpage/docs/installation/docker-images.md @@ -243,10 +243,6 @@ For images with Nvidia GPU hardware acceleration using EGL use: The base image is available at [`ghcr.io/m1k1o/neko/nvidia-base`](https://ghcr.io/m1k1o/neko/nvidia-base). -:::danger -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: From 84aa78bfcb9f66dd9ddb01ac89636f6206605873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Wed, 28 May 2025 21:30:58 +0200 Subject: [PATCH 87/91] desktop: add clipboard command replacement. (#539) we always keep running the last clipboard function as that is supposed to contain the clipboard data being pasted. as soon as a new one is spawn, the old one is stutdown. --- server/internal/desktop/clipboard.go | 41 +++++++++++++++++++++++++--- server/internal/desktop/manager.go | 9 ++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/server/internal/desktop/clipboard.go b/server/internal/desktop/clipboard.go index b67ecff0..fff20d8b 100644 --- a/server/internal/desktop/clipboard.go +++ b/server/internal/desktop/clipboard.go @@ -58,6 +58,23 @@ func (manager *DesktopManagerCtx) ClipboardGetBinary(mime string) ([]byte, error return stdout.Bytes(), nil } +func (manager *DesktopManagerCtx) replaceClipboardCommand(newCmd *exec.Cmd) { + // Swap the current clipboard command with the new one. + oldCmd := manager.clipboardCommand.Swap(newCmd) + + // If the command is already running, we need to shutdown it properly. + if oldCmd == nil || oldCmd.ProcessState != nil { + return + } + + // If there is a previous command running and it was not stopped yet, we need to kill it. + if err := oldCmd.Process.Kill(); err != nil { + manager.logger.Err(err).Msg("failed to kill previous clipboard command") + } else { + manager.logger.Debug().Msg("killed previous clipboard command") + } +} + func (manager *DesktopManagerCtx) ClipboardSetBinary(mime string, data []byte) error { cmd := exec.Command("xclip", "-selection", "clipboard", "-in", "-target", mime) @@ -69,7 +86,9 @@ func (manager *DesktopManagerCtx) ClipboardSetBinary(mime string, data []byte) e return err } - // TODO: Refactor. + // Shutdown previous command if it exists and replace it with the new one. + manager.replaceClipboardCommand(cmd) + // We need to wait until the data came to the clipboard. wait := make(chan struct{}) xevent.Emmiter.Once("clipboard-updated", func(payload ...any) { @@ -89,9 +108,23 @@ func (manager *DesktopManagerCtx) ClipboardSetBinary(mime string, data []byte) e stdin.Close() - // TODO: Refactor. - // cmd.Wait() - <-wait + select { + case <-manager.shutdown: + return fmt.Errorf("clipboard manager is shutting down") + case <-wait: + } + + manager.wg.Add(1) + go func() { + defer manager.wg.Done() + + if err := cmd.Wait(); err != nil { + msg := strings.TrimSpace(stderr.String()) + manager.logger.Err(err).Msgf("clipboard command finished with error: %s", msg) + } else { + manager.logger.Debug().Msg("clipboard command finished successfully") + } + }() return nil } diff --git a/server/internal/desktop/manager.go b/server/internal/desktop/manager.go index 0ffadd73..aca5fe1a 100644 --- a/server/internal/desktop/manager.go +++ b/server/internal/desktop/manager.go @@ -1,7 +1,9 @@ package desktop import ( + "os/exec" "sync" + "sync/atomic" "time" "github.com/kataras/go-events" @@ -25,6 +27,11 @@ type DesktopManagerCtx struct { config *config.Desktop screenSize types.ScreenSize // cached screen size input xinput.Driver + + // Clipboard process holding the most recent clipboard data. + // It must remain running to allow pasting clipboard data. + // The last command is kept running until it is replaced or shutdown. + clipboardCommand atomic.Pointer[exec.Cmd] } func New(config *config.Desktop) *DesktopManagerCtx { @@ -131,6 +138,8 @@ func (manager *DesktopManagerCtx) Shutdown() error { manager.logger.Info().Msgf("shutdown") close(manager.shutdown) + + manager.replaceClipboardCommand(nil) manager.wg.Wait() xorg.DisplayClose() From f53810327812d5663f67ca6fcaafb3e2addb5c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 29 May 2025 00:43:18 +0200 Subject: [PATCH 88/91] fix vivaldi install. --- apps/vivaldi/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/vivaldi/Dockerfile b/apps/vivaldi/Dockerfile index 60a7f62c..6ce74a6b 100644 --- a/apps/vivaldi/Dockerfile +++ b/apps/vivaldi/Dockerfile @@ -7,7 +7,8 @@ SHELL ["/bin/bash", "-c"] RUN set -eux; apt-get update; \ ARCH=$(dpkg --print-architecture); \ wget -O /tmp/vivaldi.deb "https://downloads.vivaldi.com/stable/vivaldi-stable_${ARCH}.deb"; \ - apt-get install -y --no-install-recommends wget unzip xz-utils jq openbox /tmp/vivaldi.deb; \ + apt-get install -y --no-install-recommends wget unzip xz-utils jq openbox; \ + apt install -y --no-install-recommends /tmp/vivaldi.deb; \ # # install latest version of uBlock Origin and SponsorBlock for YouTube EXTENSIONS_DIR="/usr/share/chromium/extensions"; \ From 0b4bf9e224a1744995c4787cf2f29aa5cb600dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 29 May 2025 01:56:22 +0200 Subject: [PATCH 89/91] update release notes. --- webpage/docs/release-notes.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/webpage/docs/release-notes.md b/webpage/docs/release-notes.md index 93416233..092a0bde 100644 --- a/webpage/docs/release-notes.md +++ b/webpage/docs/release-notes.md @@ -1,5 +1,17 @@ # Release Notes +## master {#master} + +### New Features {#master-feats} +- Scroll to chat on mobile ([#496](https://github.com/m1k1o/neko/pull/496)) +- Added mobile keyboard icon to open the keyboard on mobile devices ([#497](https://github.com/m1k1o/neko/pull/497)) + +### Fixes {#master-fixes} +- Fixed various bugs related to the legacy client and migration. + +### Misc {#master-misc} +- Added an https condition to the healthcheck ([#503](https://github.com/m1k1o/neko/pull/503), by @Garrulousbrevity). + ## [n.eko v3.0.0](https://github.com/m1k1o/neko/releases/tag/v3.0.0) {#v3.0.0} ### Repository Changes {#v3.0.0-repo} From cb3d02fbb69310133cf99010ba5a21ee5e71d212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 29 May 2025 01:56:56 +0200 Subject: [PATCH 90/91] update release notes #537. --- webpage/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/webpage/docs/release-notes.md b/webpage/docs/release-notes.md index 092a0bde..9b3f29f9 100644 --- a/webpage/docs/release-notes.md +++ b/webpage/docs/release-notes.md @@ -8,6 +8,7 @@ ### Fixes {#master-fixes} - Fixed various bugs related to the legacy client and migration. +- Fixed long standing issue [#279](https://github.com/m1k1o/neko/pull/279) where Google Chrome GPU acceleration did not work with Nvidia GPUs, thanks to [@TobyColeman](https://github.com/TobyColeman), [@alexbakerdev](https://github.com/alexbakerdev) and [@samstefan](https://github.com/samstefan) from [@wearewildcards](https://github.com/wearewildcards). ### Misc {#master-misc} - Added an https condition to the healthcheck ([#503](https://github.com/m1k1o/neko/pull/503), by @Garrulousbrevity). From b714663299f02194a2e82e413c84ea36b3200e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 31 May 2025 16:43:25 +0200 Subject: [PATCH 91/91] implicit hosting: request control prior interacting with the screen. #499 (#540) --- client/src/components/video.vue | 60 ++++++++++++++++++++++++++++++--- client/src/store/remote.ts | 5 ++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index a420c974..7ee57864 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -263,6 +263,10 @@ return this.$accessor.connecting } + get controlling() { + return this.$accessor.remote.controlling + } + get hosting() { return this.$accessor.remote.hosting } @@ -752,12 +756,17 @@ first.target.dispatchEvent(simulatedEvent) } + isMouseDown = false + onMouseDown(e: MouseEvent) { - if (!this.hosting) { - this.$emit('control-attempt', e) + this.isMouseDown = true + + if (this.locked) { + return } - if (!this.hosting || this.locked) { + if (!this.controlling) { + this.implicitHostingRequest(e) return } @@ -766,7 +775,16 @@ } onMouseUp(e: MouseEvent) { - if (!this.hosting || this.locked) { + // only if we are the one who started the mouse down + if (!this.isMouseDown) return + this.isMouseDown = false + + if (this.locked) { + return + } + + if (!this.controlling) { + this.implicitHostingRequest(e) return } @@ -774,6 +792,40 @@ this.$client.sendData('mouseup', { key: e.button + 1 }) } + private reqMouseDown: MouseEvent | null = null + private reqMouseUp: MouseEvent | null = null + + @Watch('controlling') + onControlChange(controlling: boolean) { + if (controlling && this.reqMouseDown) { + this.onMouseDown(this.reqMouseDown) + } + + if (controlling && this.reqMouseUp) { + this.onMouseUp(this.reqMouseUp) + } + + this.reqMouseDown = null + this.reqMouseUp = null + } + + implicitHostingRequest(e: MouseEvent) { + if (this.implicitHosting) { + if (e.type === 'mousedown') { + this.reqMouseDown = e + this.reqMouseUp = null + this.$accessor.remote.request() + } else if (e.type === 'mouseup') { + this.reqMouseUp = e + } + return + } + + if (e.type === 'mousedown') { + this.$emit('control-attempt', e) + } + } + onMouseMove(e: MouseEvent) { if (!this.hosting || this.locked) { return diff --git a/client/src/store/remote.ts b/client/src/store/remote.ts index c8e9d282..5cd40cd0 100644 --- a/client/src/store/remote.ts +++ b/client/src/store/remote.ts @@ -18,6 +18,9 @@ export const state = () => ({ }) export const getters = getterTree(state, { + controlling: (state, getters, root) => { + return root.user.id === state.id + }, hosting: (state, getters, root) => { return root.user.id === state.id || state.implicitHosting }, @@ -89,7 +92,7 @@ export const actions = actionTree( }, request({ getters }) { - if (!accessor.connected || getters.hosting) { + if (!accessor.connected || getters.controlling) { return }