Compare commits

...

39 commits

Author SHA1 Message Date
Miroslav Šedivý
0765352abd fix regression for #522. #523 2025-04-27 09:43:50 +02:00
Miroslav Šedivý
f145bd58c9
Fix mobile keyboard behavior (#522)
* keep only openMobileKeyboard, do not focus when touch device.

* do not test for hover when checking touch device.
2025-04-26 11:55:55 +02:00
Xiuming Chen
23820f6255
Fix build script for Apple Silicon MacOS. (#520) 2025-04-22 20:19:20 +02:00
Xiuming Chen
33ce337cd3
Fix docker volume mount error in build script. (#519) 2025-04-22 09:35:09 +02:00
Miroslav Šedivý
11cef143a3 temporarily disable waterfox build due to Cloudflare blocked download link. 2025-04-21 16:11:46 +02:00
Miroslav Šedivý
73e61de52e add SECURITY.md file. 2025-04-21 16:03:51 +02:00
Miroslav Šedivý
01112c5e8f clipboard: use UTF8_STRING. #517 2025-04-19 10:23:16 +02:00
James Clark
fc3b3a4dc6
fix: link to Examples in docs/v3 (#514) 2025-04-18 08:56:57 +02:00
Miroslav Šedivý
a8cc365e0f firefox: comment out xpinstall prefs. #512 2025-04-12 23:41:07 +02:00
Miroslav Šedivý
c9ca4e7144 docs firefox: guide on how to find extension ID. 2025-04-12 23:38:52 +02:00
Miroslav Šedivý
c1ccae4ac4 docs: info about supporting only one provider at a time. #505 2025-04-07 22:45:42 +02:00
Miroslav Šedivý
a46cb0536b update user agent for waterfox. they seem to block Wget/ user agents. 2025-04-07 22:43:08 +02:00
Miroslav Šedivý
b2219396dd disable proxy for local requests, #509. 2025-04-07 19:33:58 +02:00
Sean Ezrol
2ec35d9d0c
Adds an https condition to the healthcheck (#503)
* Adds an https condition to the healthcheck

* Fix V2 to V3 naming change

* Update other dockerfiles
2025-04-06 23:33:52 +02:00
Miroslav Šedivý
4eec843ed2
https: if we have legacy mode, we need to start local http server too. (#507) 2025-04-06 19:55:35 +02:00
Miroslav Šedivý
46e9b19e09 docs: legacy mode explained. 2025-04-06 17:29:56 +02:00
Miroslav Šedivý
007b55a32b add link to docs in V2 configuration warning message. 2025-04-06 17:22:34 +02:00
Miroslav Šedivý
3c787baa40 legacy: forward ws ping messages #506. 2025-04-06 17:19:22 +02:00
Miroslav Šedivý
b8bfcaf4bf legacy: fix logging. 2025-04-06 16:19:40 +02:00
Miroslav Šedivý
6c5cd1260d websocket: fix unwrap err. 2025-04-06 16:16:45 +02:00
Miroslav Šedivý
b783a9adbe docs: add overview of available encoders. 2025-04-05 22:37:56 +02:00
Miroslav Šedivý
81c259cdc9 docs: add nvidis gpu examples. #502 2025-04-05 21:45:19 +02:00
Miroslav Šedivý
972d16031e docs: add missing filetransfer config to v2. 2025-04-05 18:27:38 +02:00
Miroslav Šedivý
3a8a5c30ef docs: use ConfigurationTab that allows switching between yaml, env and cmd. 2025-04-05 18:25:04 +02:00
Miroslav Šedivý
0e3bcedcd4 update config options naming, move scripts to own folder. 2025-04-05 18:24:23 +02:00
Miroslav Šedivý
e3a1929f7f reword implicit hosting option #501. 2025-04-05 11:01:39 +02:00
Miroslav Šedivý
936794da31 include filetransfer in migration guide. 2025-04-04 22:57:58 +02:00
Miroslav Šedivý
1e91dcd7d9 add links to Availability Matrix. 2025-04-04 22:22:10 +02:00
Miroslav Šedivý
a032c9f42e dockerhub: allow only one workflow to run at a time. 2025-04-04 22:15:53 +02:00
Miroslav Šedivý
8b49fb7ca9 dockerhub ignore changes in webpage. 2025-04-04 22:12:56 +02:00
Miroslav Šedivý
da35b05c9c update docker images and include versioning in naming convention. 2025-04-04 22:02:04 +02:00
Miroslav Šedivý
4d6ad8e17d update docs for v2 migration. 2025-04-04 21:28:05 +02:00
Miroslav Šedivý
a75424d2f3 generate legacy pipelines when specifying codec. 2025-04-04 08:59:45 +02:00
Miroslav Šedivý
1c63b56b94 legacyhandler: key/button action log only in trace level. 2025-04-03 21:50:09 +02:00
Miroslav Šedivý
9eb4c36596 dockerhub use github.actor. 2025-04-03 21:46:52 +02:00
Miroslav Šedivý
1f7d12b388 add net.m1k1o.neko.api-version label to image. 2025-04-03 21:44:54 +02:00
Miroslav Šedivý
68e23fa8e7 fix server dev. 2025-04-03 14:17:59 +02:00
Miroslav Šedivý
ec44bf0e04 legacy handler set server bind. 2025-04-03 14:17:47 +02:00
Miroslav Šedivý
b383e1e24d add HelloGitHub Badge #419. 2025-04-02 22:36:12 +02:00
58 changed files with 1108 additions and 751 deletions

View file

@ -4,6 +4,8 @@ on:
push:
branches:
- master
paths-ignore:
- 'webpage/**'
#
# Run this action periodically to keep browsers up-to-date
# even if there is no activity in this repo.
@ -11,6 +13,12 @@ on:
schedule:
- cron: "43 2 * * 1"
# allow only one workflow to run at a time
# and cancel in-progress jobs if a new one is triggered
concurrency:
group: "dockerhub"
cancel-in-progress: true
env:
DOCKER_IMAGE: m1k1o/neko
@ -42,7 +50,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
username: ${{ github.actor }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Generate base Dockerfile
@ -72,7 +80,8 @@ jobs:
matrix:
tag:
- firefox
- waterfox
# Temporarily disabled due to Cloudflare blocked download link
#- waterfox
- chromium
- google-chrome
- ungoogled-chromium
@ -106,7 +115,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
username: ${{ github.actor }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -21,6 +21,9 @@
<a href="https://discord.gg/3U6hWpC">
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
</a>
<a href="https://hellogithub.com/repository/4536d4546af24196af3f08a023dfa007" target="_blank">
<img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=4536d4546af24196af3f08a023dfa007&claim_uid=0x19e4dJwD83aW2&theme=small" alt="FeaturedHelloGitHub" />
</a>
<a href="https://github.com/m1k1o/neko/actions">
<img src="https://github.com/m1k1o/neko/actions/workflows/ghcr.yml/badge.svg" alt="build">
</a>
@ -155,7 +158,7 @@ Full documentation is available at [neko.m1k1o.net](https://neko.m1k1o.net/). Ke
- [Migration from V2](https://neko.m1k1o.net/docs/v3/migration-from-v2)
- [Getting Started](https://neko.m1k1o.net/docs/v3/quick-start)
- [Installation](https://neko.m1k1o.net/docs/v3/installation)
- [Examples](https://neko.m1k1o.net/v3/installation/examples)
- [Examples](https://neko.m1k1o.net/docs/v3/installation/examples)
- [Configuration](https://neko.m1k1o.net/docs/v3/configuration)
- [Frequently Asked Questions](https://neko.m1k1o.net/docs/v3/faq)
- [Troubleshooting](https://neko.m1k1o.net/docs/v3/troubleshooting)

19
SECURITY.md Normal file
View file

@ -0,0 +1,19 @@
# Security Policy
## Reporting a Vulnerability
If there are any vulnerabilities in **m1k1o/neko**, don't hesitate to _report them_.
1. Send an email to `security@m1k1o.net`.
2. Describe the vulnerability.
If you have a fix, that is most welcome -- please attach or summarize it in your message!
3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report.
Please **do not disclose the vulnerability publicly** until a fix is released!
4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it.
We appreciate your help in keeping Neko secure.

View file

@ -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);

View file

@ -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; \

View file

@ -15,8 +15,8 @@ lockPref("plugins.hide_infobar_for_missing_plugin", true);
lockPref("profile.allow_automigration", false);
lockPref("signon.prefillForms", false);
lockPref("signon.rememberSignons", false);
lockPref("xpinstall.enabled", false);
lockPref("xpinstall.whitelist.required", true);
//lockPref("xpinstall.enabled", false);
//lockPref("xpinstall.whitelist.required", true);
lockPref("browser.download.manager.retention", 0);
lockPref("browser.download.folderList", 2);
lockPref("browser.download.forbid_open_with", true);

11
build
View file

@ -211,14 +211,15 @@ fi
if [ -z "$PLATFORM" ]; then
# use system architecture if not specified
if [ "$(uname -m)" == "x86_64" ]; then
UNAME="$(uname -m)"
if [ "$UNAME" == "x86_64" ]; then
PLATFORM="linux/amd64"
elif [ "$(uname -m)" == "aarch64" ]; then
elif [ "$UNAME" == "aarch64" ] || [ "$UNAME" == "arm64" ]; then
PLATFORM="linux/arm64"
elif [ "$(uname -m)" == "armv7l" ]; then
elif [ "$UNAME" == "armv7l" ]; then
PLATFORM="linux/arm/v7"
else
log "Unknown architecture: $(uname -m)"
log "Unknown architecture: $UNAME"
exit 1
fi
fi
@ -313,7 +314,7 @@ fi
log "Building base image: $BASE_IMAGE"
docker run --rm -i \
-v ./:/src \
-v "$(pwd)":/src \
-e "RUNTIME_DOCKERFILE=$RUNTIME_DOCKERFILE" \
--workdir /src \
--entrypoint go \

View file

@ -65,7 +65,7 @@
<li
v-if="hosting && is_touch_device"
:class="extraControls || 'extra-control'"
@click.stop.prevent="toggleMobileKeyboard"
@click.stop.prevent="openMobileKeyboard"
>
<i class="fas fa-keyboard" />
</li>
@ -367,12 +367,11 @@
get is_touch_device() {
return (
// check if the device has a touch screen
// detect if the device has touch support
('ontouchstart' in window || navigator.maxTouchPoints > 0) &&
// we also check if the device has a pointer
!window.matchMedia('(pointer:fine)').matches &&
// and is capable of hover, then it probably has a mouse
!window.matchMedia('(hover:hover)').matches
// the primary input mechanism includes a pointing device of
// limited accuracy, such as a finger on a touchscreen.
window.matchMedia('(pointer: coarse)').matches
)
}
@ -822,64 +821,20 @@
@Watch('hosting')
@Watch('locked')
onFocus() {
// focus opens the keyboard on mobile
if (this.is_touch_device) {
return
}
// in order to capture key events, overlay must be focused
if (this.focused && this.hosting && !this.locked) {
this._overlay.focus()
}
}
//
// mobile keyboard
//
kbdShow = false
kbdOpen = false
showMobileKeyboard() {
// skip if not a touch device
if (!this.is_touch_device) return
this.kbdShow = true
this.kbdOpen = false
const overlay = this.$refs.overlay as HTMLTextAreaElement
overlay.focus()
window.visualViewport?.addEventListener('resize', this.onVisualViewportResize)
}
hideMobileKeyboard() {
// skip if not a touch device
if (!this.is_touch_device) return
this.kbdShow = false
this.kbdOpen = false
const overlay = this.$refs.overlay as HTMLTextAreaElement
window.visualViewport?.removeEventListener('resize', this.onVisualViewportResize)
overlay.blur()
}
toggleMobileKeyboard() {
// skip if not a touch device
if (!this.is_touch_device) return
if (this.kbdShow) {
this.hideMobileKeyboard()
} else {
this.showMobileKeyboard()
}
}
// visual viewport resize event is fired when keyboard is opened or closed
// android does not blur textarea when keyboard is closed, so we need to do it manually
onVisualViewportResize() {
if (!this.kbdShow) return
if (!this.kbdOpen) {
this.kbdOpen = true
} else {
this.hideMobileKeyboard()
}
openMobileKeyboard() {
// focus opens the keyboard on mobile
this._overlay.focus()
}
}
</script>

View file

@ -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

View file

@ -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"]

View file

@ -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"]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 ./...

View file

@ -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"

View file

@ -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';

View file

@ -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

View file

@ -90,12 +90,12 @@ func (Capture) Init(cmd *cobra.Command) error {
return err
}
cmd.PersistentFlags().String("capture.video.pipelines", "[]", "pipelines config in JSON used for video streaming")
cmd.PersistentFlags().String("capture.video.pipelines", "{}", "pipelines config used for video streaming")
if err := viper.BindPFlag("capture.video.pipelines", cmd.PersistentFlags().Lookup("capture.video.pipelines")); err != nil {
return err
}
cmd.PersistentFlags().String("capture.video.pipeline", "", "gstreamer pipeline used for video streaming; shortcut for having only a single video pipeline instead of multiple, ignored if capture.video.pipelines is set")
cmd.PersistentFlags().String("capture.video.pipeline", "", "shortcut for configuring only a single gstreamer pipeline, ignored if pipelines is set")
if err := viper.BindPFlag("capture.video.pipeline", cmd.PersistentFlags().Lookup("capture.video.pipeline")); err != nil {
return err
}
@ -451,6 +451,7 @@ func (s *Capture) SetV2() {
enableLegacy = true
}
modifiedVideoCodec := false
if videoCodec := viper.GetString("video_codec"); videoCodec != "" {
s.VideoCodec, ok = codec.ParseStr(videoCodec)
if !ok || s.VideoCodec.Type != webrtc.RTPCodecTypeVideo {
@ -459,24 +460,29 @@ func (s *Capture) SetV2() {
}
log.Warn().Msg("you are using v2 configuration 'NEKO_VIDEO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_VIDEO_CODEC' instead")
enableLegacy = true
modifiedVideoCodec = true
}
if viper.GetBool("vp8") {
s.VideoCodec = codec.VP8()
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP8=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp8' instead")
enableLegacy = true
modifiedVideoCodec = true
} else if viper.GetBool("vp9") {
s.VideoCodec = codec.VP9()
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP9=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp9' instead")
enableLegacy = true
modifiedVideoCodec = true
} else if viper.GetBool("h264") {
s.VideoCodec = codec.H264()
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_CAPTURE_VIDEO_CODEC=h264' instead")
enableLegacy = true
modifiedVideoCodec = true
} else if viper.GetBool("av1") {
s.VideoCodec = codec.AV1()
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_CAPTURE_VIDEO_CODEC=av1' instead")
enableLegacy = true
modifiedVideoCodec = true
}
videoHWEnc := HwEncUnset
@ -498,7 +504,7 @@ func (s *Capture) SetV2() {
videoPipeline := viper.GetString("video")
// video pipeline
if videoHWEnc != HwEncUnset || videoBitrate != 0 || videoMaxFPS != 0 || videoPipeline != "" {
if modifiedVideoCodec || videoHWEnc != HwEncUnset || videoBitrate != 0 || videoMaxFPS != 0 || videoPipeline != "" {
pipeline, err := NewVideoPipeline(s.VideoCodec, s.Display, videoPipeline, videoMaxFPS, videoBitrate, videoHWEnc)
if err != nil {
log.Warn().Err(err).Msg("unable to create video pipeline, using default")
@ -534,6 +540,7 @@ func (s *Capture) SetV2() {
enableLegacy = true
}
modifiedAudioCodec := false
if audioCodec := viper.GetString("audio_codec"); audioCodec != "" {
s.AudioCodec, ok = codec.ParseStr(audioCodec)
if !ok || s.AudioCodec.Type != webrtc.RTPCodecTypeAudio {
@ -542,31 +549,36 @@ func (s *Capture) SetV2() {
}
log.Warn().Msg("you are using v2 configuration 'NEKO_AUDIO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_AUDIO_CODEC' instead")
enableLegacy = true
modifiedAudioCodec = true
}
if viper.GetBool("opus") {
s.AudioCodec = codec.Opus()
log.Warn().Msg("you are using deprecated config setting 'NEKO_OPUS=true', use 'NEKO_CAPTURE_AUDIO_CODEC=opus' instead")
enableLegacy = true
modifiedAudioCodec = true
} else if viper.GetBool("g722") {
s.AudioCodec = codec.G722()
log.Warn().Msg("you are using deprecated config setting 'NEKO_G722=true', use 'NEKO_CAPTURE_AUDIO_CODEC=g722' instead")
enableLegacy = true
modifiedAudioCodec = true
} else if viper.GetBool("pcmu") {
s.AudioCodec = codec.PCMU()
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMU=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcmu' instead")
enableLegacy = true
modifiedAudioCodec = true
} else if viper.GetBool("pcma") {
s.AudioCodec = codec.PCMA()
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMA=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcma' instead")
enableLegacy = true
modifiedAudioCodec = true
}
audioBitrate := viper.GetUint("audio_bitrate")
audioPipeline := viper.GetString("audio")
// audio pipeline
if audioBitrate != 0 || audioPipeline != "" {
if modifiedAudioCodec || audioBitrate != 0 || audioPipeline != "" {
pipeline, err := NewAudioPipeline(s.AudioCodec, s.AudioDevice, audioPipeline, audioBitrate)
if err != nil {
log.Warn().Err(err).Msg("unable to create audio pipeline, using default")
@ -604,7 +616,7 @@ func (s *Capture) SetV2() {
// set legacy flag if any V2 configuration was used
if !viper.IsSet("legacy") && enableLegacy {
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
viper.Set("legacy", true)
}
}

View file

@ -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)
}
}

View file

@ -22,45 +22,45 @@ type Member struct {
}
func (Member) Init(cmd *cobra.Command) error {
cmd.PersistentFlags().String("member.provider", "multiuser", "choose member provider")
cmd.PersistentFlags().String("member.provider", "multiuser", "selected member provider")
if err := viper.BindPFlag("member.provider", cmd.PersistentFlags().Lookup("member.provider")); err != nil {
return err
}
// file provider
cmd.PersistentFlags().String("member.file.path", "", "member file provider: storage path")
cmd.PersistentFlags().String("member.file.path", "", "member file provider: path to the file containing the users and their passwords")
if err := viper.BindPFlag("member.file.path", cmd.PersistentFlags().Lookup("member.file.path")); err != nil {
return err
}
cmd.PersistentFlags().Bool("member.file.hash", true, "member file provider: whether to hash passwords using sha256 (recommended)")
cmd.PersistentFlags().Bool("member.file.hash", true, "member file provider: whether the passwords are hashed using sha256 or not (recommended)")
if err := viper.BindPFlag("member.file.hash", cmd.PersistentFlags().Lookup("member.file.hash")); err != nil {
return err
}
// object provider
cmd.PersistentFlags().String("member.object.users", "[]", "member object provider: users in JSON format")
cmd.PersistentFlags().String("member.object.users", "[]", "member object provider: list of users with their passwords and profiles")
if err := viper.BindPFlag("member.object.users", cmd.PersistentFlags().Lookup("member.object.users")); err != nil {
return err
}
// multiuser provider
cmd.PersistentFlags().String("member.multiuser.user_password", "neko", "member multiuser provider: user password")
cmd.PersistentFlags().String("member.multiuser.user_password", "neko", "member multiuser provider: password for regular users")
if err := viper.BindPFlag("member.multiuser.user_password", cmd.PersistentFlags().Lookup("member.multiuser.user_password")); err != nil {
return err
}
cmd.PersistentFlags().String("member.multiuser.admin_password", "admin", "member multiuser provider: admin password")
cmd.PersistentFlags().String("member.multiuser.admin_password", "admin", "member multiuser provider: password for admin users")
if err := viper.BindPFlag("member.multiuser.admin_password", cmd.PersistentFlags().Lookup("member.multiuser.admin_password")); err != nil {
return err
}
cmd.PersistentFlags().String("member.multiuser.user_profile", "{}", "member multiuser provider: user profile in JSON format")
cmd.PersistentFlags().String("member.multiuser.user_profile", "{}", "member multiuser provider: profile template for regular users")
if err := viper.BindPFlag("member.multiuser.user_profile", cmd.PersistentFlags().Lookup("member.multiuser.user_profile")); err != nil {
return err
}
cmd.PersistentFlags().String("member.multiuser.admin_profile", "{}", "member multiuser provider: admin profile in JSON format")
cmd.PersistentFlags().String("member.multiuser.admin_profile", "{}", "member multiuser provider: profile template for admin users")
if err := viper.BindPFlag("member.multiuser.admin_profile", cmd.PersistentFlags().Lookup("member.multiuser.admin_profile")); err != nil {
return err
}
@ -162,7 +162,7 @@ func (s *Member) SetV2() {
// set legacy flag if any V2 configuration was used
if !viper.IsSet("legacy") && enableLegacy {
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
viper.Set("legacy", true)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -67,17 +67,17 @@ func (WebRTC) Init(cmd *cobra.Command) error {
}
// Looks like this is conflicting with the frontend and backend ICE servers since latest versions
//cmd.PersistentFlags().String("webrtc.iceservers", "[]", "Global STUN and TURN servers in JSON format with `urls`, `username` and `credential` keys")
//cmd.PersistentFlags().String("webrtc.iceservers", "[]", "STUN and TURN servers used by the ICE agent")
//if err := viper.BindPFlag("webrtc.iceservers", cmd.PersistentFlags().Lookup("webrtc.iceservers")); err != nil {
// return err
//}
cmd.PersistentFlags().String("webrtc.iceservers.frontend", "[]", "Frontend only STUN and TURN servers in JSON format with `urls`, `username` and `credential` keys")
cmd.PersistentFlags().String("webrtc.iceservers.frontend", "[]", "STUN and TURN servers used by the frontend")
if err := viper.BindPFlag("webrtc.iceservers.frontend", cmd.PersistentFlags().Lookup("webrtc.iceservers.frontend")); err != nil {
return err
}
cmd.PersistentFlags().String("webrtc.iceservers.backend", "[]", "Backend only STUN and TURN servers in JSON format with `urls`, `username` and `credential` keys")
cmd.PersistentFlags().String("webrtc.iceservers.backend", "[]", "STUN and TURN servers used by the backend")
if err := viper.BindPFlag("webrtc.iceservers.backend", cmd.PersistentFlags().Lookup("webrtc.iceservers.backend")); err != nil {
return err
}
@ -217,14 +217,14 @@ func (s *WebRTC) Set() {
// parse frontend ice servers
if err := viper.UnmarshalKey("webrtc.iceservers.frontend", &s.ICEServersFrontend, viper.DecodeHook(
utils.JsonStringAutoDecode([]types.ICEServer{}),
utils.JsonStringAutoDecode(s.ICEServersFrontend),
)); err != nil {
log.Warn().Err(err).Msgf("unable to parse frontend ICE servers")
}
// parse backend ice servers
if err := viper.UnmarshalKey("webrtc.iceservers.backend", &s.ICEServersBackend, viper.DecodeHook(
utils.JsonStringAutoDecode([]types.ICEServer{}),
utils.JsonStringAutoDecode(s.ICEServersBackend),
)); err != nil {
log.Warn().Err(err).Msgf("unable to parse backend ICE servers")
}
@ -238,7 +238,7 @@ func (s *WebRTC) Set() {
// parse global ice servers
var iceServers []types.ICEServer
if err := viper.UnmarshalKey("webrtc.iceservers", &iceServers, viper.DecodeHook(
utils.JsonStringAutoDecode([]types.ICEServer{}),
utils.JsonStringAutoDecode(iceServers),
)); err != nil {
log.Warn().Err(err).Msgf("unable to parse global ICE servers")
}
@ -409,7 +409,7 @@ func (s *WebRTC) SetV2() {
// set legacy flag if any V2 configuration was used
if !viper.IsSet("legacy") && enableLegacy {
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, or set 'NEKO_LEGACY=true' to acknowledge this message")
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
viper.Set("legacy", true)
}
}

View file

@ -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) {

View file

@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"strings"
"time"
"github.com/m1k1o/neko/server/internal/api"
oldEvent "github.com/m1k1o/neko/server/internal/http/legacy/event"
@ -35,9 +36,6 @@ var (
return true
},
}
// DefaultDialer is a dialer with all fields set to the default zero values.
DefaultDialer = websocket.DefaultDialer
)
type LegacyHandler struct {
@ -45,16 +43,21 @@ type LegacyHandler struct {
serverAddr string
bannedIPs map[string]struct{}
sessionIPs map[string]string
wsDialer *websocket.Dialer
}
func New() *LegacyHandler {
func New(serverAddr string) *LegacyHandler {
// Init
return &LegacyHandler{
logger: log.With().Str("module", "legacy").Logger(),
serverAddr: "127.0.0.1:8080",
serverAddr: serverAddr,
bannedIPs: make(map[string]struct{}),
sessionIPs: make(map[string]string),
wsDialer: &websocket.Dialer{
Proxy: nil, // disable proxy for local requests
HandshakeTimeout: 45 * time.Second,
},
}
}
@ -99,7 +102,7 @@ func (h *LegacyHandler) Route(r types.Router) {
defer s.destroy()
// dial to the remote backend
connBackend, _, err := DefaultDialer.Dial("ws://"+h.serverAddr+"/api/ws?token="+url.QueryEscape(s.token), nil)
connBackend, _, err := h.wsDialer.Dial("ws://"+h.serverAddr+"/api/ws?token="+url.QueryEscape(s.token), nil)
if err != nil {
h.logger.Error().Err(err).Msg("couldn't dial to the remote backend")
@ -142,7 +145,7 @@ func (h *LegacyHandler) Route(r types.Router) {
m = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
errc <- err
errc <- fmt.Errorf("src read message error: %w", err)
dst.WriteMessage(websocket.CloseMessage, m)
break
}
@ -163,12 +166,20 @@ func (h *LegacyHandler) Route(r types.Router) {
})
continue
} else if errors.Is(err, ErrWebsocketSend) {
errc <- err
errc <- fmt.Errorf("dst write message error: %w", err)
break
} else {
h.logger.Error().Err(err).Msg("couldn't rewrite text message")
}
}
// forward ping messages
if msgType == websocket.PingMessage {
err = dst.WriteMessage(websocket.PingMessage, nil)
if err != nil {
errc <- err
break
}
}
}
}
@ -181,9 +192,9 @@ func (h *LegacyHandler) Route(r types.Router) {
var message string
select {
case err = <-errClient:
message = "websocketproxy: Error when copying from backend to client: %v"
message = "websocketproxy: Error when copying from backend to client"
case err = <-errBackend:
message = "websocketproxy: Error when copying from client to backend: %v"
message = "websocketproxy: Error when copying from client to backend"
}
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {

View file

@ -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),
}
}

View file

@ -2,6 +2,7 @@ package http
import (
"context"
"net"
"net/http"
"os"
@ -56,11 +57,6 @@ func New(WebSocketManager types.WebSocketManager, ApiManager types.ApiManager, c
return config.AllowOrigin(r.Header.Get("Origin"))
}))
// Legacy handler
if viper.GetBool("legacy") {
legacy.New().Route(router)
}
batch := batchHandler{
Router: router,
PathPrefix: "/api",
@ -122,6 +118,24 @@ func (manager *HttpManagerCtx) Start() {
}
}()
manager.logger.Info().Msgf("https listening on %s", manager.http.Addr)
// if we have legacy mode, we need to start local http server too
if viper.GetBool("legacy") {
// create a listener for the API server with a random port
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
manager.logger.Panic().Err(err).Msg("unable to start legacy http proxy")
}
go func() {
if err := http.Serve(listener, manager.router); err != http.ErrServerClosed {
manager.logger.Panic().Err(err).Msg("unable to start http server")
}
}()
manager.logger.Info().Msgf("legacy proxy listening on %s", listener.Addr().String())
legacy.New(listener.Addr().String()).Route(manager.router)
}
} else {
go func() {
if err := manager.http.ListenAndServe(); err != http.ErrServerClosed {
@ -129,6 +143,11 @@ func (manager *HttpManagerCtx) Start() {
}
}()
manager.logger.Info().Msgf("http listening on %s", manager.http.Addr)
// start legacy proxy if enabled
if viper.GetBool("legacy") {
legacy.New(manager.http.Addr).Route(manager.router)
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -1,4 +1,6 @@
import { Def, Opt } from '@site/src/components/Anchor';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
# Configuration
@ -291,24 +293,23 @@ import TabItem from '@theme/TabItem';
This is the initial configuration of the room that can be modified by an admin in real-time.
```yaml title="config.yaml"
session:
private_mode: false
locked_logins: false
locked_controls: false
control_protection: false
implicit_hosting: true
inactive_cursors: false
merciful_reconnect: true
heartbeat_interval: 120
```
<ConfigurationTab options={configOptions} filter={[
'session.private_mode',
'session.locked_logins',
'session.locked_controls',
'session.control_protection',
'session.implicit_hosting',
'session.inactive_cursors',
'session.merciful_reconnect',
'session.heartbeat_interval',
]} comments={false} />
- <Def id="session.private_mode" /> whether private mode is enabled, users do not receive the room video or audio.
- <Def id="session.locked_logins" /> whether logins are locked for users, admins can still login.
- <Def id="session.locked_controls" /> whether controls are locked for users, admins can still control.
- <Def id="session.control_protection" /> users can gain control only if at least one admin is in the room.
- <Def id="session.implicit_hosting" /> allows switching control implicitly without the need for explicit control request before
- <Def id="session.inactive_cursors" /> whether to show inactive cursors server-wide (only for users that have it enabled in their profile)
- <Def id="session.implicit_hosting" /> automatically grants control to a user when they click on the screen, unless an admin has locked the controls.
- <Def id="session.inactive_cursors" /> whether to show inactive cursors server-wide (only for users that have it enabled in their profile).
- <Def id="session.merciful_reconnect" /> whether to allow reconnecting to the websocket even if the previous connection was not closed. This means that a new login can kick out the previous one.
- <Def id="session.heartbeat_interval" /> interval in seconds for sending a heartbeat message to the server. This is used to keep the connection alive and to detect when the connection is lost.
@ -316,18 +317,17 @@ session:
This is the configuration of the neko server.
```yaml title="config.yaml"
server:
bind: "127.0.0.1:8080"
cert: "/path/to/cert.pem"
key: "/path/to/key.pem"
cors: [ "*" ]
metrics: true
path_prefix: "/neko"
pprof: true
proxy: true
static: "/var/www/neko"
```
<ConfigurationTab options={configOptions} filter={[
'server.bind',
'server.cert',
'server.key',
'server.cors',
'server.metrics',
'server.path_prefix',
'server.pprof',
'server.proxy',
'server.static',
]} comments={false} />
- <Def id="server.bind" /> address/port/socket to serve neko. For docker you might want to bind to `0.0.0.0` to allow connections from outside the container.
- <Def id="server.cert" /> and <Def id="server.key" /> paths to the SSL cert and key used to secure the neko server. If both are empty, the server will run in plain HTTP.
@ -345,14 +345,13 @@ server:
This is the configuration of the logging system.
```yaml title="config.yaml"
log:
dir: <string>
json: true
level: "info"
nocolor: true
time: "unix"
```
<ConfigurationTab options={configOptions} filter={[
'log.dir',
'log.json',
'log.level',
'log.nocolor',
'log.time',
]} comments={false} />
- <Def id="log.dir" /> directory to store logs. If empty, logs are written to stdout. This is useful when running neko in a container.
- <Def id="log.json" /> when true, logs are written in JSON format.
@ -368,10 +367,7 @@ Shortcut environment variable to enable DEBUG mode: `NEKO_DEBUG=true`
Here is a full configuration with default values as shown in the help command. Please refer to the sub-sections for more details.
import Configuration from '@site/src/components/Configuration';
import configOptions from './help.json';
<Configuration configOptions={configOptions} />
<ConfigurationTab options={configOptions} heading={true} />
## Next Steps {#next}

View file

@ -3,6 +3,8 @@ description: Configuration related to the Authentication and Sessions in Neko.
---
import { Def, Opt } from '@site/src/components/Anchor';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
# Authentication
@ -78,6 +80,10 @@ import TabItem from '@theme/TabItem';
Member providers are responsible for deciding whether given credentials are valid or not. This validation can either be done against a local database or an external system.
:::info
Currently, Neko supports configuring only one authentication provider at a time. This means you must choose a single provider that best fits your deployment needs.
:::
### Multi-User Provider {#member.multiuser}
This is the **default provider** that works exactly like the authentication used to work in v2 of neko.
@ -86,21 +92,25 @@ This provider allows you to define two types of users: **regular** users and **a
Profiles for regular users and admins are optional, if not provided, the default profiles are used (see below in the example configuration).
```yaml title="config.yaml"
member:
provider: multiuser
multiuser:
# Password for admins, in plain text.
admin_password: "adminPassword"
# Profile fields as described above
admin_profile:
...
# Password for regular users, in plain text.
user_password: "userPassword"
# Profile fields as described above
user_profile:
...
```
<ConfigurationTab options={{
"member.provider": 'multiuser',
"member.multiuser.admin_password": {
defaultValue: "admin",
description: "Password for admins, in plain text.",
},
"member.multiuser.admin_profile": {
defaultValue: {},
description: "Profile fields as described above",
},
"member.multiuser.user_password": {
defaultValue: "neko",
description: "Password for regular users, in plain text.",
},
"member.multiuser.user_profile": {
defaultValue: {},
description: "Profile fields as described above",
},
}} />
<details>
<summary>See example configuration</summary>
@ -152,8 +162,8 @@ For easier configuration, you can specify only passwords using environment varia
```yaml title="docker-compose.yaml"
environment:
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "neko"
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: "admin"
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "neko"
```
:::
@ -161,15 +171,17 @@ environment:
This provider reads the user's credentials from a file. It is useful for small deployments where you don't want to set up a database or LDAP server and still want to have persistent users.
```yaml title="config.yaml"
member:
provider: file
file:
# Absolute path to the file containing the users and their passwords.
path: /opt/neko/members.json
# Whether the passwords are hashed using sha256 or not.
hash: true
```
<ConfigurationTab options={{
"member.provider": 'file',
"member.file.path": {
defaultValue: "/opt/neko/members.json",
description: "Absolute path to the file containing the users and their passwords.",
},
"member.file.hash": {
defaultValue: false,
description: "Whether the passwords are hashed using sha256 or not.",
},
}} />
It allows you to store the user's credentials in a JSON file. The JSON structure maps user logins to their passwords and profiles.
@ -239,18 +251,13 @@ You can leave the file empty and add users later using the HTTP API.
This provider is the same as the file provider, but it saves the users only in memory. That means that the users are lost when the server is restarted. However, the default users can be set in the configuration file. The difference from the multi-user provider is that the users are not generated on demand and we define exactly which users with their passwords and profiles are allowed to log in. They cannot be logged in twice with the same username.
```yaml title="config.yaml"
member:
provider: object
object:
# List of users with their passwords and profiles
- username: "admin"
# Password in plain text
password: "admin"
# Profile fields as described above
profile:
...
```
<ConfigurationTab options={{
"member.provider": 'object',
"member.object.users": {
defaultValue: [],
description: "List of users with their passwords and profiles.",
},
}} />
<details>
<summary>See example configuration</summary>
@ -296,10 +303,9 @@ member:
This provider allows any user to log in without any authentication. It is useful for testing and development purposes.
```yaml title="config.yaml"
member:
provider: noauth
```
<ConfigurationTab options={configOptions} filter={{
"member.provider": 'noauth',
}} comments={false} />
:::danger
Do not use this provider in production environments unless you know exactly what you are doing. It allows anyone to log in and control neko as an admin.
@ -311,10 +317,9 @@ Currently, there are only two providers available for sessions: **memory** and *
Simply by specifying the `session.file` to a file path, the session provider will store the sessions in a file. Otherwise, the sessions are stored in memory and are lost when the server is restarted.
```yaml title="config.yaml"
session:
file: /opt/neko/sessions.json
```
<ConfigurationTab options={configOptions} filter={{
"session.file": '/opt/neko/sessions.json',
}} comments={false} />
:::info
In the future, we plan to add more session providers, such as Redis, PostgreSQL, etc. So the Configuration Options may change.
@ -324,10 +329,9 @@ In the future, we plan to add more session providers, such as Redis, PostgreSQL,
The API User is a special user that is used to authenticate the HTTP API requests. It cannot connect to the room, but it can perform administrative tasks. The API User does not have a password but only a token that is used to authenticate the requests. If the token is not set, the API User is disabled.
```yaml title="config.yaml"
session:
api_token: "apiToken"
```
<ConfigurationTab options={configOptions} filter={{
"session.api_token": '<secret_token>',
}} comments={false} />
:::tip
This user is useful in some situations when the rooms are generated by the server and the token is guaranteed to be random every time a short-lived room is run. It is not a good idea to define this token for long-lived rooms, as it can be stolen and used to perform administrative tasks.
@ -347,17 +351,15 @@ The authentication between the client and the server can be done using cookies o
If you disable the cookies, the token will be sent to the client in the login response and saved in local storage. This is less secure than using cookies, as the token **can be stolen using XSS attacks**. Therefore, it is recommended to use cookies.
:::
```yaml title="config.yaml"
session:
cookie:
enabled: true
name: "NEKO_SESSION"
expiration: "24h"
secure: true
http_only: true
domain: ""
path: ""
```
<ConfigurationTab options={configOptions} filter={[
'session.cookie.enabled',
'session.cookie.name',
'session.cookie.expiration',
'session.cookie.secure',
'session.cookie.http_only',
'session.cookie.domain',
'session.cookie.path'
]} comments={false} />
- <Def id="session.cookie.enabled" /> - Whether the cookies are enabled or not.
- <Def id="session.cookie.name" /> - Name of the cookie used to store the session.

View file

@ -3,6 +3,8 @@ description: Configuration related to Gstreamer capture in Neko.
---
import { Def, Opt } from '@site/src/components/Anchor';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
# Audio & Video Capture
@ -27,22 +29,19 @@ All video pipelines must use the same video codec (defined in the <Opt id="video
The Gstreamer pipeline is started when the first client requests the video stream and is stopped after the last client disconnects.
```yaml title="config.yaml"
capture:
video:
display: "<display_name>"
codec: "vp8" # default video codec
ids: [ <pipeline_id1>, <pipeline_id2>, ... ]
pipelines:
<pipeline_id1>: <pipeline_config>
<pipeline_id2>: <pipeline_config>
...
```
<ConfigurationTab options={configOptions} filter={[
"capture.video.display",
"capture.video.codec",
"capture.video.ids",
"capture.video.pipeline",
"capture.video.pipelines",
]} comments={false} />
- <Def id="video.display" /> is the name of the [X display](https://www.x.org/wiki/) that you want to capture. If not specified, the environment variable `DISPLAY` will be used.
- <Def id="video.codec" /> available codecs are `vp8`, `vp9`, `av1`, `h264`. [Supported video codecs](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/WebRTC_codecs#supported_video_codecs) are dependent on the WebRTC implementation used by the client, `vp8` and `h264` are supported by all WebRTC implementations.
- <Def id="video.ids" /> is a list of pipeline ids that are defined in the <Opt id="video.pipelines" /> section. The first pipeline in the list will be the default pipeline.
- <Def id="video.pipelines" /> is a dictionary of pipeline configurations. Each pipeline configuration is defined by a unique pipeline id. They can be defined in two ways: either by building the pipeline dynamically using [Expression-Driven Configuration](#video.expression) or by defining the pipeline using a [Gstreamer Pipeline Description](#video.pipeline).
- <Def id="video.pipeline" /> is a shorthand for defining [Gstreamer pipeline description](#video.gst_pipeline) for a single pipeline. This is option is ignored if <Opt id="video.pipelines" /> is defined.
- <Def id="video.pipelines" /> is a dictionary of pipeline configurations. Each pipeline configuration is defined by a unique pipeline id. They can be defined in two ways: either by building the pipeline dynamically using [Expression-Driven Configuration](#video.expression) or by defining the pipeline using a [Gstreamer Pipeline Description](#video.gst_pipeline).
### Expression-Driven Configuration {#video.expression}
@ -151,7 +150,7 @@ import TabItem from '@theme/TabItem';
</details>
### Gstreamer Pipeline Description {#video.pipeline}
### Gstreamer Pipeline Description {#video.gst_pipeline}
If you want to define the pipeline using a [Gstreamer pipeline description](https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description), you can do so by setting the <Def id="video.pipelines.gst_pipeline" /> parameter.
@ -169,7 +168,7 @@ Since now you have to define the whole pipeline, you need to specify the src ele
Your typical pipeline string would look like this:
```
ximagesrc display-name={display} show-pointer=true use-damage=false ! <your_elements> ! appsink name=appsink"
ximagesrc display-name={display} show-pointer=true use-damage=false ! <your_elements> ! appsink name=appsink
```
See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentation/ximagesrc/index.html) and [appsink](https://gstreamer.freedesktop.org/documentation/app/appsink.html) for more information.
@ -189,8 +188,9 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
hq:
gst_pipeline: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! videoconvert
! videoconvert ! queue
! vp8enc
name=encoder
target-bitrate=3072000
cpu-used=4
end-usage=cbr
@ -207,8 +207,9 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
lq:
gst_pipeline: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! videoconvert
! videoconvert ! queue
! vp8enc
name=encoder
target-bitrate=1024000
cpu-used=4
end-usage=cbr
@ -236,8 +237,9 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
main:
gst_pipeline: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! videoconvert
! videoconvert ! queue
! x264enc
name=encoder
threads=4
bitrate=4096
key-int-max=15
@ -248,23 +250,62 @@ See documentation for [ximagesrc](https://gstreamer.freedesktop.org/documentatio
! appsink name=appsink
```
</TabItem>
<TabItem value="nvh264enc" label="NVENC H264 configuration">
```yaml title="config.yaml"
capture:
video:
codec: h264
ids: [ main ]
pipelines:
main:
gst_pipeline: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! videoconvert ! queue
! video/x-raw,format=NV12
! nvh264enc
name=encoder
preset=2
gop-size=25
spatial-aq=true
temporal-aq=true
bitrate=4096
vbv-buffer-size=4096
rc-mode=6
! h264parse config-interval=-1
! video/x-h264,stream-format=byte-stream
! appsink name=appsink
```
This configuration requires [Nvidia GPU](https://developer.nvidia.com/cuda-gpus) with [NVENC](https://developer.nvidia.com/nvidia-video-codec-sdk) support.
</TabItem>
</Tabs>
</details>
Overview of available encoders for each codec is shown in the table below. The encoder name is used in the <Opt id="video.pipelines.gst_encoder" /> parameter. The parameters for each encoder are different and you can find the documentation for each encoder in the links below.
| codec | encoder | vaapi encoder | nvenc encoder |
| ----- | ------- | ------------- | ------------- |
| VP8 | [vp8enc](https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html?gi-language=c) | [vaapivp8enc](https://github.com/GStreamer/gstreamer-vaapi/blob/master/gst/vaapi/gstvaapiencode_vp8.c) | ? |
| VP9 | [vp9enc](https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html?gi-language=c) | [vaapivp9enc](https://github.com/GStreamer/gstreamer-vaapi/blob/master/gst/vaapi/gstvaapiencode_vp9.c) | ? |
| AV1 | [av1enc](https://gstreamer.freedesktop.org/documentation/aom/av1enc.html?gi-language=c) | ? | [nvav1enc](https://gstreamer.freedesktop.org/documentation/nvcodec/nvav1enc.html?gi-language=c) |
| H264 | [x264enc](https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c) | [vaapih264enc](https://gstreamer.freedesktop.org/documentation/vaapi/vaapih264enc.html?gi-language=c) | [nvh264enc](https://gstreamer.freedesktop.org/documentation/nvcodec/nvh264enc.html?gi-language=c) |
| H265 | [x265enc](https://gstreamer.freedesktop.org/documentation/x265/index.html?gi-language=c) | [vaapih265enc](https://gstreamer.freedesktop.org/documentation/vaapi/vaapih265enc.html?gi-language=c) | [nvh265enc](https://gstreamer.freedesktop.org/documentation/nvcodec/nvh265enc.html?gi-language=c) |
## WebRTC Audio {#audio}
Only one audio pipeline can be defined in neko. The audio pipeline is used to capture and encode audio, similar to the video pipeline. The encoded audio is then sent to the client using WebRTC.
The Gstreamer pipeline is started when the first client requests the video stream and is stopped after the last client disconnects.
```yaml title="config.yaml"
capture:
audio:
device: "audio_output.monitor" # default audio device
codec: "opus" # default audio codec
pipeline: "<gstreamer_pipeline>"
```
<ConfigurationTab options={configOptions} filter={[
"capture.audio.device",
"capture.audio.codec",
"capture.audio.pipeline",
]} comments={false} />
- <Def id="audio.device" /> is the name of the [pulseaudio device](https://wiki.archlinux.org/title/PulseAudio/Examples) that you want to capture. If not specified, the default audio device will be used.
- <Def id="audio.codec" /> available codecs are `opus`, `g722`, `pcmu`, `pcma`. [Supported audio codecs](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/WebRTC_codecs#supported_audio_codecs) are dependent on the WebRTC implementation used by the client, `opus` is supported by all WebRTC implementations.
@ -293,23 +334,21 @@ Neko allows you to broadcast out-of-the-box the display and audio capture to a t
The Gstreamer pipeline is started when the broadcast is started and is stopped when the broadcast is stopped regardless of the clients connected.
```yaml title="config.yaml"
capture:
broadcast:
audio_bitrate: 128 # in KB/s
video_bitrate: 4096 # in KB/s
preset: "veryfast"
pipeline: "<gstreamer_pipeline>"
url: "rtmp://<server>/<application>/<stream_key>"
autostart: true
```
<ConfigurationTab options={configOptions} filter={[
"capture.broadcast.audio_bitrate",
"capture.broadcast.video_bitrate",
"capture.broadcast.preset",
"capture.broadcast.pipeline",
"capture.broadcast.url",
"capture.broadcast.autostart",
]} comments={false} />
The default encoder uses `h264` for video and `aac` for audio, muxed in the `flv` container and sent over the `rtmp` protocol. You can change the encoder settings by setting a custom Gstreamer pipeline description in the <Opt id="broadcast.pipeline" /> parameter.
- <Def id="broadcast.audio_bitrate" /> and <Def id="broadcast.video_bitrate" /> are the bitrate settings for the default audio and video encoders expressed in kilobits per second.
- <Def id="broadcast.preset" /> is the encoding speed preset for the default video encoder. See available presets [here](https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c#GstX264EncPreset).
- <Def id="broadcast.pipeline" /> when set, encoder settings above are ignored and the custom Gstreamer pipeline description is used. In the pipeline, you can use `{display}`, `{device}` and `{url}` as placeholders for the X display name, pulseaudio audio device name, and broadcast URL respectively.
- <Def id="broadcast.url" /> is the URL of the RTMP server where the broadcast will be sent. This can be set later using the API if the URL is not known at the time of configuration or is expected to change.
- <Def id="broadcast.url" /> is the URL of the RTMP server where the broadcast will be sent e.g. `rtmp://<server>/<application>/<stream_key>`. This can be set later using the API if the URL is not known at the time of configuration or is expected to change.
- <Def id="broadcast.autostart" /> is a boolean value that determines whether the broadcast should start automatically when neko starts, works only if the URL is set.
<details>
@ -380,14 +419,12 @@ This is a fallback mechanism and should not be used as a primary video stream be
The Gstreamer pipeline is started in the background when the first client requests the screencast and is stopped after a period of inactivity.
```yaml title="config.yaml"
capture:
screencast:
enabled: true
rate: "10/1"
quality: 60
pipeline: "<gstreamer_pipeline>"
```
<ConfigurationTab options={configOptions} filter={[
"capture.screencast.enabled",
"capture.screencast.rate",
"capture.screencast.quality",
"capture.screencast.pipeline",
]} comments={false} />
- <Def id="screencast.enabled" /> is a boolean value that determines whether the screencast is enabled or not.
- <Def id="screencast.rate" /> is the framerate of the screencast. It is expressed as a fraction of frames per second, for example, `10/1` means 10 frames per second.
@ -422,14 +459,12 @@ Neko allows you to capture the webcam on the client machine and send it to the s
The Gstreamer pipeline is started when the client shares their webcam and is stopped when the client stops sharing the webcam. Maximum one webcam pipeline can be active at a time.
```yaml title="config.yaml"
capture:
webcam:
enabled: true
device: "/dev/video0" # default webcam device
width: 640
height: 480
```
<ConfigurationTab options={configOptions} filter={[
"capture.webcam.enabled",
"capture.webcam.device",
"capture.webcam.width",
"capture.webcam.height",
]} comments={false} />
- <Def id="webcam.enabled" /> is a boolean value that determines whether the webcam capture is enabled or not.
- <Def id="webcam.device" /> is the name of the [video4linux device](https://www.kernel.org/doc/html/v4.12/media/v4l-drivers/index.html) that will be used as a virtual webcam.
@ -463,12 +498,10 @@ Neko allows you to capture the microphone on the client machine and send it to t
The Gstreamer pipeline is started when the client shares their microphone and is stopped when the client stops sharing the microphone. Maximum one microphone pipeline can be active at a time.
```yaml title="config.yaml"
capture:
microphone:
enabled: true
device: "audio_input"
```
<ConfigurationTab options={configOptions} filter={[
"capture.microphone.enabled",
"capture.microphone.device",
]} comments={false} />
- <Def id="microphone.enabled" /> is a boolean value that determines whether the microphone capture is enabled or not.
- <Def id="microphone.device" /> is the name of the [pulseaudio device](https://wiki.archlinux.org/title/PulseAudio/Examples) that will be used as a virtual microphone.

View file

@ -3,6 +3,8 @@ description: Configuration related to the Desktop Environment in Neko.
---
import { Def, Opt } from '@site/src/components/Anchor';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
# Desktop Environment
@ -10,18 +12,15 @@ This section describes how to configure the desktop environment inside neko.
Neko uses the [X Server](https://www.x.org/archive/X11R7.6/doc/man/man1/Xserver.1.xhtml) as the display server with [Openbox](http://openbox.org/wiki/Main_Page) as the default window manager. For audio, [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) is used.
```yaml title="config.yaml"
desktop:
display: "<display>"
screen: "1280x720@30" # default
```
<ConfigurationTab options={configOptions} filter={[
'desktop.display',
'desktop.screen'
]} comments={false} />
- <Def id="display" /> refers to the X server that is running on the system. If it is not specified, the environment variable `DISPLAY` is used. The same display is referred to in the [Capture](capture#video.display) configuration to capture the screen. In most cases, we want to use the same display for both.
- <Def id="screen" /> refers to the screen resolution and refresh rate. The format is `<width>x<height>@<refresh rate>`. If not specified, the default is `1280x720@30`.
:::tip
You can specify the screen resolution using the environment variable `NEKO_DESKTOP_SCREEN`.
Admin can change the resolution in the GUI.
:::
@ -33,12 +32,10 @@ Neko uses the [XTEST Extension Library](https://www.x.org/releases/X11R7.7/doc/l
Currently, only touchscreens are supported through the custom driver.
:::
```yaml title="config.yaml"
desktop:
input:
enabled: true # default
socket: "/tmp/xf86-input-neko.sock" # default
```
<ConfigurationTab options={configOptions} filter={[
'desktop.input.enabled',
'desktop.input.socket'
]} comments={false} />
- <Def id="input.enabled" /> enables the input device support. If not specified, the default is `false`.
- <Def id="input.socket" /> refers to the socket file that the custom driver creates. If not specified, the default is `/tmp/xf86-input-neko.sock`.
@ -51,10 +48,9 @@ When using Docker, the custom driver is already included in the image and the so
Most of the time, only a single application is used in the minimal desktop environment without any taskbar or desktop icons. It could happen that the user accidentally minimizes the application and then it is not possible to restore it. To prevent this, we can use the `unminimize` feature that simply listens for the minimize event and restores the window back to the original state.
```yaml title="config.yaml"
desktop:
unminimize: true # default
```
<ConfigurationTab options={configOptions} filter={[
'desktop.unminimize'
]} comments={false} />
## Upload Drop {#upload_drop}
@ -62,10 +58,9 @@ The upload drop is a feature that allows the user to upload files to the applica
The current approach is to catch the drag and drop events on the client side, upload them to the server along with the coordinates of the drop event, and then open an invisible overlay window on the server that has set the file path to the uploaded file and allows it to be dragged and dropped into the application. Then the mouse events are simulated to drag the file from the overlay window to the application window.
```yaml title="config.yaml"
desktop:
upload_drop: true # default
```
<ConfigurationTab options={configOptions} filter={[
'desktop.upload_drop'
]} comments={false} />
## File Chooser Dialog {#file_chooser_dialog}
@ -77,7 +72,6 @@ The file chooser dialog is a feature that allows handling the file chooser dialo
The current approach is to put the file chooser dialog in the background as soon as it is displayed, prompt the user to upload the file, and then select this file in the file chooser dialog by simulating the keyboard events to navigate to the file and press the open button. **This is very error-prone and may not work as expected.**
```yaml title="config.yaml"
desktop:
file_chooser_dialog: false # default
```
<ConfigurationTab options={configOptions} filter={[
'desktop.file_chooser_dialog'
]} comments={false} />

View file

@ -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": [

View file

@ -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

View file

@ -3,17 +3,18 @@ description: Configuration related to the Neko plugins.
---
import { Def, Opt } from '@site/src/components/Anchor';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
# Plugins Configuration
Neko allows you to extend its functionality by using [plugins](https://pkg.go.dev/plugin). Go plugins come with a lot of benefits as well as some limitations. The main advantage is that you can extend the functionality of the application without recompiling the main application. But the main limitation is that you need to use the same Go version and all dependencies with the same version as the main application.
```yaml title="config.yaml"
plugins:
enabled: true
required: true
dir: "./bin/plugins"
```
<ConfigurationTab options={configOptions} filter={[
'plugins.enabled',
'plugins.required',
'plugins.dir',
]} comments={false} />
- <Def id="enabled" /> enables the plugin support. If set to `false`, the plugins are not loaded.
- <Def id="required" /> makes the plugin loading mandatory, meaning that if a plugin fails to load, the application will not start.
@ -29,10 +30,9 @@ There exist a few pre-loaded internal plugins that are shipped with Neko:
The chat plugin is a simple pre-loaded internal plugin that allows you to chat with other users in the same session. The chat messages are sent to the server and then broadcasted to all users in the same session.
```yaml title="config.yaml"
chat:
enabled: true
```
<ConfigurationTab options={{
'chat.enabled': true
}} />
- <Def id="chat.enabled" /> enables the chat support. If set to `false`, the chat is disabled.
@ -51,12 +51,15 @@ plugins:
The file transfer plugin is a simple pre-loaded internal plugin that allows you to transfer files between the client and the server. The files are uploaded to the server and then downloaded by the client.
```yaml title="config.yaml"
filetransfer:
enabled: true
dir: "./uploads"
refresh_interval: 30s
```
<ConfigurationTab options={{
'filetransfer.enabled': true,
'filetransfer.dir': './uploads',
'filetransfer.refresh_interval': {
type: 'duration',
defaultValue: '30s',
},
}} />
- <Def id="filetransfer.enabled" /> enables the file transfer support. If set to `false`, the file transfer is disabled.
- <Def id="filetransfer.dir" /> refers to the directory where the files are stored.
@ -70,4 +73,3 @@ plugins:
```
- `filetransfer.enabled` in the room settings context controls whether the file transfer is enabled for any user in the room, and in the user's profile context controls whether the user can transfer files.

View file

@ -3,6 +3,8 @@ description: Configuration related to the WebRTC and Networking in Neko.
---
import { Def, Opt } from '@site/src/components/Anchor';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
# WebRTC Configuration
@ -18,19 +20,17 @@ ICE, which stands for Interactive Connectivity Establishment, is a protocol used
ICE Trickle is a feature that allows ICE candidates to be sent as they are discovered, rather than waiting for all candidates to be discovered before sending them. It means that the ICE connection can be established faster as the server can start connecting to the client as soon as it has a few ICE candidates and doesn't have to wait for all of them to be discovered.
```yaml title="config.yaml"
webrtc:
icetrickle: false
```
<ConfigurationTab options={configOptions} filter={[
'webrtc.icetrickle'
]} comments={false} />
### ICE Lite {#icelite}
ICE Lite is a minimal implementation of the ICE protocol intended for servers running on a public IP address. It is not enabled by default to allow more complex ICE configurations out of the box.
```yaml title="config.yaml"
webrtc:
icelite: false
```
<ConfigurationTab options={configOptions} filter={[
'webrtc.icelite'
]} comments={false} />
:::info
When using ICE Servers, ICE Lite must be disabled.
@ -100,16 +100,10 @@ import TabItem from '@theme/TabItem';
The ICE servers are divided into two groups:
```yaml title="config.yaml"
webrtc:
iceservers:
frontend:
# List of ICE Server configurations as described above
- urls: "stun:stun.l.google.com:19302"
backend:
# List of ICE Server configurations as described above
- urls: "stun:stun.l.google.com:19302"
```
<ConfigurationTab options={configOptions} filter={[
'webrtc.iceservers.frontend',
'webrtc.iceservers.backend'
]} />
- <Def id="iceservers.frontend" /> - ICE servers that are sent to the client and used to establish a connection between the client and the server.
- <Def id="iceservers.backend" /> - ICE servers that are used by the server to gather ICE candidates. They might contain private IP addresses or other sensitive information that should not be sent to the client.
@ -162,15 +156,14 @@ There exist two types of connections:
The ephemeral UDP port range can be configured using the following configuration:
```yaml title="config.yaml"
webrtc:
epr: "59000-59100"
```
<ConfigurationTab options={configOptions} filter={{
'webrtc.epr': "59000-59100"
}} comments={false} />
The range `59000-59100` contains 101 ports, which should be open on the server's firewall. The server uses these ports to establish a connection with the client. You can specify a different range of ports if needed, with fewer or more ports, depending on the number of simultaneous connections you expect.
:::tip
You can specify the ephemeral UDP port range as an environment variable in the `docker-compose.yaml` file using the `NEKO_WEBRTC_EPR` environment variable. When using docker, make sure to expose the ports in the `docker-compose.yaml`.
:::tip Make sure
When specifying the ephemeral UDP port range in `docker-compose.yaml`, make sure to use the same range for ports **as UDP**.
```yaml title="docker-compose.yaml"
environment:
@ -186,19 +179,18 @@ It is important to expose the same ports to the host machine, without any remapp
The UDP/TCP multiplexing port can be configured using the following configuration:
```yaml title="config.yaml"
webrtc:
udpmux: 59000
tcpmux: 59000
```
<ConfigurationTab options={configOptions} filter={{
'webrtc.udpmux': 59000,
'webrtc.tcpmux': 59000
}} comments={false} />
- <Def id="udpmux" /> - The port used for UDP connections.
- <Def id="tcpmux" /> - The port used for TCP connections.
The server uses only port `59000` for both UDP and TCP connections. This port should be open on the server's firewall. You can specify a different port if needed, or specify only one of the two protocols. UDP is generally better for latency, but some networks block UDP so it is good to have TCP available as a fallback.
:::tip
You can specify the UDP/TCP multiplexing port as an environment variable in the `docker-compose.yaml` file using the `NEKO_WEBRTC_TCPMUX` and `NEKO_WEBRTC_UDPMUX` environment variables. When using docker, make sure to expose the ports in the `docker-compose.yaml`.
:::tip Make sure
When specifying the UDP/TCP multiplexing port in `docker-compose.yaml`, make sure to correctly specify the protocol in the ports section.
```yaml title="docker-compose.yaml"
environment:
@ -217,42 +209,20 @@ It is important to expose the same ports to the host machine, without any remapp
The server IP address is sent to the client in ICE candidates so that the client can establish a connection with the server. By default, the server IP address is automatically resolved by the server to the public IP address of the server. If the server is behind a NAT, you want to specify a different IP address or use neko only in a local network, you can specify the server IP address manually.
#### NAT 1-to-1 {#nat1to1}
<ConfigurationTab options={configOptions} filter={{
'webrtc.nat1to1': '10.10.0.5'
}} comments={false} />
```yaml title="config.yaml"
webrtc:
nat1to1:
# IPv4 address of the server
- 10.10.0.5
# IPv6 address of the server
- 2001:db8:85a3::8a2e:370:7334
```
Currently, only one IPv4 and one IPv6 address can be specified. Therefore if you want to access your instance from both local and public networks, your router must support [NAT loopback (hairpinning)](https://en.wikipedia.org/wiki/Network_address_translation#NAT_hairpinning).
:::tip
You can specify the server IP address as an environment variable in the `docker-compose.yaml` file using the `NEKO_WEBRTC_NAT1TO1` environment variable.
```yaml title="docker-compose.yaml"
environment:
NEKO_WEBRTC_NAT1TO1: "10.10.0.5"
```
If you want to specify also an IPv6 address, use whitespace to separate the addresses.
```yaml title="docker-compose.yaml"
environment:
NEKO_WEBRTC_NAT1TO1: "10.10.0.5 2001:db8:85a3::8a2e:370:7334"
```
:::
Currently, only one address can be specified. Therefore if you want to access your instance from both local and public networks, your router must support [NAT loopback (hairpinning)](https://en.wikipedia.org/wiki/Network_address_translation#NAT_hairpinning).
#### IP Retrieval URL {#ip_retrieval_url}
If you do not specify the server IP address, the server will try to resolve the public IP address of the server automatically.
```yaml title="config.yaml"
webrtc:
ip_retrieval_url: "https://checkip.amazonaws.com"
```
<ConfigurationTab options={configOptions} filter={[
'webrtc.ip_retrieval_url'
]} comments={false} />
The server will send an HTTP GET request to the specified URL to retrieve the public IP address of the server.
## Bandwidth Estimator {#estimator}
@ -263,29 +233,6 @@ The bandwidth estimator is an experimental feature and might not work as expecte
The bandwidth estimator is a feature that allows the server to estimate the available bandwidth between the client and the server. It is used to switch between different video qualities based on the available bandwidth. The bandwidth estimator is disabled by default.
```yaml title="config.yaml"
webrtc:
estimator:
# Whether to enable the bandwidth estimator
enabled: false
# Whether the bandwidth estimator is passive - only used for logging and not for actual decisions
passive: false
# Enable debug logging for the bandwidth estimator (will print the current state and decisions)
debug: false
# Initial bitrate for the bandwidth estimator to start with (in bps)
initial_bitrate: 1000000
# How often to read and process bandwidth estimation reports
read_interval: "2s"
# How long to wait for a stable connection (upward or neutral trend) before upgrading
stable_duration: "12s"
# How long to wait for a stalled connection (neutral trend with low bandwidth) before downgrading
unstable_duration: "6s"
# How long to wait for stalled bandwidth estimation before downgrading
stalled_duration: "24s"
# How long to wait before downgrading again after the previous downgrade
downgrade_backoff: "10s"
# How long to wait before upgrading again after the previous upgrade
upgrade_backoff: "5s"
# How much bigger the difference between estimated and stream bitrate must be to trigger a change
diff_threshold: 0.15
```
<ConfigurationTab options={configOptions} filter={[
'webrtc.estimator'
]} comments={true} />

View file

@ -158,6 +158,20 @@ By default, the browsers in Neko do not allow installing extensions except for t
}
```
<details>
<summary>How to find the extension ID?</summary>
Extension IDs for Firefox are not available in the URL like in Chrome. You can find the extension ID by navigating to the `about:debugging#/runtime/this-firefox` page and clicking on the extension you want to install. The extension ID will be displayed in the URL.
Another way is to find the extension on the [Official Add-ons Webpage](https://addons.mozilla.org/en-US/firefox/), then open DevTools (<code>F12</code>) and go to the `Console` tab. Enter the following command:
```javascript
Object.keys(JSON.parse(document.getElementById('redux-store-state').textContent).addons.byGUID)[0]
```
This will return the ID of the first extension on the page.
</details>
### Chromium-based Browsers {#chromium-based}
The full configuration options for the Chromium-based policy JSON file can be found in the [Chrome Enterprise](https://chromeenterprise.google/policies) documentation.

View file

@ -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/).

View file

@ -12,24 +12,53 @@ The base image is available as multi-arch image at [`ghcr.io/m1k1o/neko/base`](h
## Naming Convention {#naming}
Neko Docker images are available on [GitHub Container Registry (GHCR)](https://github.com/m1k1o?tab=packages&repo_name=neko). The naming convention for Neko Docker images is as follows:
Neko images are available on two public registries. The [GitHub Container Registry (GHCR)](#ghcr.io) hosts stable releases with all flavors and architectures. The latest development version of the Neko image for the AMD64 architecture is available on [Docker Hub](#docker.io).
:::info
You should always prefer the GHCR registry, as it supports flavors and specific versions, unless you want to test the latest development version.
:::
### GitHub Container Registry (GHCR) {#ghcr.io}
Neko Docker images are available on the [GitHub Container Registry (GHCR)](https://github.com/m1k1o?tab=packages&repo_name=neko). The naming convention for Neko Docker images is as follows:
```
ghcr.io/m1k1o/neko/[<flavor>-]<application>:<version>
```
- `<flavor>` is the optional flavor of the image, see [Available Flavors](#flavors) for more information.
- `<application>` is the application name or base image, see [Available Applications](#apps) for more information.
- `<version>` is the [semantic version](https://semver.org/) of the image from the [GitHub tags](https://github.com/m1k1o/neko/tags). There is always a `latest` tag available.
- `<flavor>` is the optional flavor of the image. See [Available Flavors](#flavors) for more information.
- `<application>` is the application name or base image. See [Available Applications](#apps) for more information.
- `<version>` is the version of the image. See [Versioning](#ghcr.io-versioning) for more information.
An alternative registry is also available on [Docker Hub](https://hub.docker.com/r/m1k1o/neko), however, only images without flavor and with the latest version are available there.
#### Versioning scheme {#ghcr.io-versioning}
The versioning scheme follows the [Semantic Versioning 2.0.0](https://semver.org/) specification. The following tags are available for each image:
- `latest` - Points to the most recent stable release.
- `MAJOR` - Tracks the latest release within the specified major version.
- `MAJOR.MINOR` - Tracks the latest release within the specified major and minor version.
- `MAJOR.MINOR.PATCH` - Refers to a specific release.
For example:
- `ghcr.io/m1k1o/neko/firefox:latest` - Latest stable version.
- `ghcr.io/m1k1o/neko/firefox:3` - Latest release in the 3.x.x series.
- `ghcr.io/m1k1o/neko/firefox:3.0` - Latest release in the 3.0.x series.
- `ghcr.io/m1k1o/neko/firefox:3.0.0` - Specific version 3.0.0.
A full list of published versions can be found in the [GitHub tags](https://github.com/m1k1o/neko/tags).
### Docker Hub {#docker.io}
An alternative registry is available on [Docker Hub](https://hub.docker.com/r/m1k1o/neko). This registry hosts images built from the latest code in the [master branch](https://github.com/m1k1o/neko/tree/master). However, it only includes images without flavors and supports the AMD64 architecture. The naming convention for these images is as follows:
```
m1k1o/neko:<application>
```
- `<application>` is the application name or base image. See [Available Applications](#apps) for more information.
:::info
You should always prefer the GHCR registry with the ability to use flavors and specific versions.
`m1k1o/neko:latest` is an alias for `m1k1o/neko:firefox` due to historical reasons. It is recommended to use the `ghcr.io/m1k1o/neko/firefox:latest` image instead.
:::
## Available Applications {#apps}
@ -46,6 +75,10 @@ In comparison to Chromium-based browsers, Firefox-based browsers do not require
| <AppIcon id="tor-browser" /> | [Tor Browser](https://www.torproject.org/) <br /> A browser designed to access the Tor network for enhanced privacy. | [`ghcr.io/m1k1o/neko/tor-browser`](https://ghcr.io/m1k1o/neko/tor-browser) |
| <AppIcon id="waterfox" /> | [Waterfox](https://www.waterfox.net/) <br /> A privacy-focused browser based on Firefox. | [`ghcr.io/m1k1o/neko/waterfox`](https://ghcr.io/m1k1o/neko/waterfox) |
:::warning
**Waterfox** is currently not built automatically, because Cloudflare blocks the download and therefore github actions are failing. You can build it manually to get the latest version.
:::
Check the [Firefox-based browsers customization guide](/docs/v3/customization/browsers#firefox-based) for more information on how to customize Firefox-based browsers (configuring profile, installing extensions, etc.).
### Chromium-based browsers {#chromium-based-browsers}
@ -162,42 +195,6 @@ docker run \
See [neko-apps](https://github.com/m1k1o/neko-apps) repository for more applications.
:::
## Supported Architectures {#arch}
Neko Docker images are built with docker buildx and are available for multiple architectures. The following architectures are supported by the base image:
- `linux/amd64` - 64-bit Intel/AMD architecture (most common).
- `linux/arm64` - 64-bit ARM architecture (e.g., Raspberry Pi 4, Apple M1/M2).
- `linux/arm/v7` - 32-bit ARM architecture (e.g., Raspberry Pi 3, Raspberry Pi Zero).
### Availability Matrix {#availability}
The availability of applications for ARM architecture is limited due to the lack of support for some applications. The following table shows the availability of each application for each architecture. The `✅` symbol indicates that the application is available for that architecture, while the `❌` symbol indicates that it is not available.
| Application | AMD64 | ARM64 | ARMv7 | Reference |
| ----------------- | ----- | ----- | ----- | --------- |
| Firefox | ✅ | ✅ \* | ✅ \* | - |
| Waterfox | ✅ | ❌ | ❌ | [Github Issue](https://github.com/BrowserWorks/Waterfox/issues/1506), [Reddit](https://www.reddit.com/r/waterfox/comments/jpqsds/are_there_any_builds_for_arm64/) |
| Chromium | ✅ | ✅ \* | ✅ \* | - |
| Google Chrome | ✅ | ❌ | ❌ | [Community Post](https://askubuntu.com/a/1383791) |
| Ungoogled Chromium| ✅ | ❌ | ❌ | [Downloads Page](https://ungoogled-software.github.io/ungoogled-chromium-binaries/) |
| Microsoft Edge | ✅ | ❌ | ❌ | [Community Post](https://techcommunity.microsoft.com/discussions/edgeinsiderdiscussions/edge-for-linuxarm64/1532272) |
| Brave | ✅ | ✅ \* | ❌ | [Requirements Page](https://support.brave.com/hc/en-us/articles/360021357112-What-are-the-system-requirements-to-install-Brave) |
| Vivaldi | ✅ | ✅ \* | ✅ \* | - |
| Opera | ✅ | ❌ | ❌ | [Forum Post](https://forums.opera.com/topic/52811/opera-do-not-support-arm64-on-linux) |
| Tor Browser | ✅ | ❌ | ❌ | [Forum Post](https://forum.torproject.org/t/tor-browser-for-arm-linux/5240) |
| Remmina | ✅ | ✅ | ✅ | - |
| VLC | ✅ | ✅ | ✅ | - |
| Xfce | ✅ | ✅ | ✅ | - |
| KDE | ✅ | ✅ | ✅ | - |
\* No DRM support.
:::tip
[Oracle Cloud ARM free tier](https://www.oracle.com/cloud/free/) is a great way to test Neko on ARM architecture for free. You can use the `ghcr.io/m1k1o/neko/xfce` image to run a full desktop environment with Xfce and test the applications.
:::
## Available Flavors {#flavors}
:::danger Keep in Mind
@ -250,3 +247,37 @@ The base image is available at [`ghcr.io/m1k1o/neko/nvidia-base`](https://ghcr.i
There is a known issue with EGL and Chromium-based browsers, see [m1k1o/neko #279](https://github.com/m1k1o/neko/issues/279).
:::
## Supported Architectures {#arch}
Neko Docker images are built with docker buildx and are available for multiple architectures. The following architectures are supported by the base image:
- `linux/amd64` - 64-bit Intel/AMD architecture (most common).
- `linux/arm64` - 64-bit ARM architecture (e.g., Raspberry Pi 4, Apple M1/M2).
- `linux/arm/v7` - 32-bit ARM architecture (e.g., Raspberry Pi 3, Raspberry Pi Zero).
### Availability Matrix {#availability}
The availability of applications for ARM architecture is limited due to the lack of support for some applications. The following table shows the availability of each application for each architecture. The `✅` symbol indicates that the application is available for that architecture, while the `❌` symbol indicates that it is not available.
| Application | AMD64 | ARM64 | ARMv7 | Reference |
| ----------------------------------------- | ----- | ----- | ----- | --------- |
| [Firefox](#firefox) | ✅ | ✅ \* | ✅ \* | - |
| [Tor Browser](#tor-browser) | ✅ | ❌ | ❌ | [Forum Post](https://forum.torproject.org/t/tor-browser-for-arm-linux/5240) |
| [Waterfox](#waterfox) | ✅ | ❌ | ❌ | [Github Issue](https://github.com/BrowserWorks/Waterfox/issues/1506), [Reddit](https://www.reddit.com/r/waterfox/comments/jpqsds/are_there_any_builds_for_arm64/) |
| [Chromium](#chromium) | ✅ | ✅ \* | ✅ \* | - |
| [Google Chrome](#google-chrome) | ✅ | ❌ | ❌ | [Community Post](https://askubuntu.com/a/1383791) |
| [Ungoogled Chromium](#ungoogled-chromium) | ✅ | ❌ | ❌ | [Downloads Page](https://ungoogled-software.github.io/ungoogled-chromium-binaries/) |
| [Microsoft Edge](#microsoft-edge) | ✅ | ❌ | ❌ | [Community Post](https://techcommunity.microsoft.com/discussions/edgeinsiderdiscussions/edge-for-linuxarm64/1532272) |
| [Brave](#brave) | ✅ | ✅ \* | ❌ | [Requirements Page](https://support.brave.com/hc/en-us/articles/360021357112-What-are-the-system-requirements-to-install-Brave) |
| [Vivaldi](#vivaldi) | ✅ | ✅ \* | ✅ \* | - |
| [Opera](#opera) | ✅ | ❌ | ❌ | [Forum Post](https://forums.opera.com/topic/52811/opera-do-not-support-arm64-on-linux) |
| [Xfce](#xfce) | ✅ | ✅ | ✅ | - |
| [KDE](#kde) | ✅ | ✅ | ✅ | - |
| [Remmina](#remmina) | ✅ | ✅ | ✅ | - |
| [VLC](#vlc) | ✅ | ✅ | ✅ | - |
\* No DRM support.
:::tip
[Oracle Cloud ARM free tier](https://www.oracle.com/cloud/free/) is a great way to test Neko on ARM architecture for free. You can use the `ghcr.io/m1k1o/neko/xfce` image to run a full desktop environment with Xfce and test the applications.
:::

View file

@ -76,30 +76,11 @@ services:
NEKO_WEBRTC_NAT1TO1: <your-IP>
```
## Raspberry Pi {#raspberry-pi}
```yaml title="config.yaml"
capture:
video:
codec: h264
ids: [ main ]
pipelines:
main:
gst_pipeline: |
ximagesrc display-name=%s use-damage=0 show-pointer=true use-damage=false
! video/x-raw,framerate=30/1
! videoconvert
! queue
! video/x-raw,framerate=30/1,format=NV12
! v4l2h264enc extra-controls="controls,h264_profile=1,video_bitrate=1250000;"
! h264parse config-interval=3
! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
```
## Raspberry Pi GPU Acceleration {#raspberry-pi}
```yaml title="docker-compose.yaml"
services:
neko:
# see docs for more variants
image: "ghcr.io/m1k1o/neko/chromium:latest"
restart: "unless-stopped"
# increase on rpi's with more then 1gb ram.
@ -110,12 +91,121 @@ services:
# note: this is important since we need a GPU for hardware acceleration alternatively
# mount the devices into the docker.
privileged: true
volumes:
- "./config.yaml:/etc/neko/neko.yaml"
environment:
NEKO_CAPTURE_VIDEO_PIPELINE: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! video/x-raw,framerate=25/1
! videoconvert ! queue
! video/x-raw,format=NV12
! v4l2h264enc
name=encoder
extra-controls="controls,h264_profile=1,video_bitrate=1250000;"
! h264parse config-interval=-1
! video/x-h264,stream-format=byte-stream
! appsink name=appsink
NEKO_CAPTURE_VIDEO_CODEC: "h264"
NEKO_DESKTOP_SCREEN: '1280x720@30'
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: 'neko'
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: 'admin'
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: neko
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: admin
NEKO_WEBRTC_EPR: 52000-52100
NEKO_WEBRTC_ICELITE: 1
```
## Nvidia GPU Acceleration {#nvidia}
Neko supports hardware acceleration using Nvidia GPUs. To use this feature, you need to have the Nvidia Container Toolkit installed on your system. You can find the installation instructions [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html).
This example shows how to accelerate video encoding and as well the browser rendering using the GPU. You can test if the GPU is used by running `nvidia-smi`, which should show the GPU usage of both the browser and neko. In the browser, you can run the [WebGL Aquarium Demo](https://webglsamples.org/aquarium/aquarium.html) to test the GPU usage.
```yaml title="docker-compose.yaml"
services:
neko:
image: "ghcr.io/m1k1o/neko/nvidia-firefox:latest"
restart: "unless-stopped"
shm_size: "2gb"
ports:
- "8080:8080"
- "52000-52100:52000-52100/udp"
environment:
NEKO_CAPTURE_VIDEO_PIPELINE: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! video/x-raw,framerate=25/1
! videoconvert ! queue
! video/x-raw,format=NV12
! nvh264enc
name=encoder
preset=2
gop-size=25
spatial-aq=true
temporal-aq=true
bitrate=4096
vbv-buffer-size=4096
rc-mode=6
! h264parse config-interval=-1
! video/x-h264,stream-format=byte-stream
! appsink name=appsink
NEKO_CAPTURE_VIDEO_CODEC: "h264"
NEKO_DESKTOP_SCREEN: 1920x1080@30
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: neko
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: admin
NEKO_WEBRTC_EPR: 52000-52100
NEKO_WEBRTC_ICELITE: 1
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```
See available [Nvidia Docker Images](/docs/v3/installation/docker-images#nvidia).
If you only want to accelerate the encoding, **not the browser rendering**, you can use the default image with additional environment variables:
```yaml title="docker-compose.yaml"
services:
neko:
# highlight-next-line
image: "ghcr.io/m1k1o/neko/firefox:latest"
restart: "unless-stopped"
shm_size: "2gb"
ports:
- "8080:8080"
- "52000-52100:52000-52100/udp"
environment:
# highlight-start
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: all
# highlight-end
NEKO_CAPTURE_VIDEO_PIPELINE: |
ximagesrc display-name={display} show-pointer=true use-damage=false
! video/x-raw,framerate=25/1
! videoconvert ! queue
! video/x-raw,format=NV12
! nvh264enc
name=encoder
preset=2
gop-size=25
spatial-aq=true
temporal-aq=true
bitrate=4096
vbv-buffer-size=4096
rc-mode=6
! h264parse config-interval=-1
! video/x-h264,stream-format=byte-stream
! appsink name=appsink
NEKO_CAPTURE_VIDEO_CODEC: "h264"
NEKO_DESKTOP_SCREEN: 1920x1080@30
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: neko
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: admin
NEKO_WEBRTC_EPR: 52000-52100
NEKO_WEBRTC_ICELITE: 1
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```

View file

@ -2,15 +2,13 @@
Currently, Neko is in compatibility mode, meaning that as soon as a single V2 configuration option is set, the legacy mode is enabled. This approach allows for a smooth transition from V2 to V3, where it does not expose the V2 API for new users but still allows existing users who use the old configuration to continue using it as before.
The legacy mode can be explicitly enabled or disabled by setting the `NEKO_LEGACY` environment variable to `true` or `false`.
The legacy mode includes a compatibility layer that allows V2 clients to connect to V3. Currently, the **client is not compatible with V3**, so the legacy mode is enabled by default. It can be explicitly enabled or disabled by setting the `NEKO_LEGACY` environment variable to `true` or `false`.
:::tip
You can migrate to a new configuration even if you are using a V2 client. Just make sure to set the `NEKO_LEGACY` environment variable to `true`.
:::warning
The legacy mode is **still used by the client**. It is recommended to migrate to the new configuration options, but do not disable the legacy mode unless you are using a new client that is compatible with V3 (e.g., [demodesk/neko-client](https://github.com/demodesk/neko-client)). Once the new client is released, the legacy mode will be automatically removed from the server.
:::
:::info Built-in Client
When using Neko in a container with a built-in client, the client will always be compatible with the server regardless of what configuration is used.
:::
If you set both V3 and V2 configuration options, the V2 configuration options will take precedence over the V3 configuration options. This is to ensure that the legacy mode works as expected and does not break existing configurations.
## Docker Images {#docker}
@ -42,8 +40,10 @@ In order to migrate from V2 to V3, you need to update the configuration to the n
| `NEKO_IMPLICIT_CONTROL` | `NEKO_SESSION_IMPLICIT_HOSTING` |
| `NEKO_CONTROL_PROTECTION` | `NEKO_SESSION_CONTROL_PROTECTION` |
| `NEKO_HEARTBEAT_INTERVAL` | `NEKO_SESSION_HEARTBEAT_INTERVAL` |
| `NEKO_FILE_TRANSFER_ENABLED` | `NEKO_FILETRANSFER_ENABLED` |
| `NEKO_FILE_TRANSFER_PATH` | `NEKO_FILETRANSFER_DIR` |
See the V3 [configuration options](/docs/v3/configuration).
See the V3 [configuration options](/docs/v3/configuration). For file transfer, see the [File Transfer Plugin](/docs/v3/configuration/plugins#filetransfer).
### WebRTC Video {#config.video}
@ -58,9 +58,9 @@ See the V3 configuration options for the [WebRTC Video](/docs/v3/configuration/c
| `NEKO_VP8=true` *deprecated* | `NEKO_CAPTURE_VIDEO_CODEC=vp8` |
| `NEKO_VP9=true` *deprecated* | `NEKO_CAPTURE_VIDEO_CODEC=vp9` |
| `NEKO_VIDEO` | `NEKO_CAPTURE_VIDEO_PIPELINE`, V3 allows multiple video pipelines |
| `NEKO_VIDEO_BITRATE` | **removed**, use custom pipeline instead |
| `NEKO_HWENC` | **removed**, use custom pipeline instead |
| `NEKO_MAX_FPS` | **removed**, use custom pipeline instead |
| `NEKO_VIDEO_BITRATE` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.gst_pipeline) instead |
| `NEKO_HWENC` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.gst_pipeline) instead |
| `NEKO_MAX_FPS` | **removed**, use [custom pipeline](/docs/v3/configuration/capture#video.gst_pipeline) instead |
:::warning Limitation
@ -133,10 +133,10 @@ See the V3 configuration options for the [WebRTC](/docs/v3/configuration/webrtc)
Here is a full list of all the configuration options available in Neko V2 that are still available in Neko V3 with legacy support enabled.
import Configuration from '@site/src/components/Configuration';
import { ConfigurationTab } from '@site/src/components/Configuration';
import configOptions from './help.json';
<Configuration configOptions={configOptions} />
<ConfigurationTab options={configOptions} heading={true} />
See the full [V3 configuration reference](/docs/v3/configuration/#full) for more details.
@ -160,6 +160,8 @@ Only the [`multiuser`](/docs/v3/configuration/authentication#member.multiuser) p
Since WebSocket messages are not user-facing API, there exists no migration guide for them. When the legacy API is enabled, the user connects to the `/ws` endpoint and is handled by the compatibility layer V2 API. The V3 API is available at the `/api/ws` endpoint.
V2 used to send WebSocket ping messages every 60 seconds, whereas V3 sends them every 10 seconds and additionally uses a heartbeat mechanism to verify if the connection is still active.
### WebRTC API {#api.webrtc}
Since the WebRTC API is not user-facing API, there exists no migration guide for it. It has been changed to Big Endian format (previously Little Endian) to allow easier manipulation on the client side.

View file

@ -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": [

View file

@ -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

View file

@ -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.

View file

@ -10,8 +10,8 @@
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"gen-help": "node ./src/components/Configuration/generate.js ./docs/configuration/help.txt ./docs/configuration/help.json",
"gen-help:v2": "node ./src/components/Configuration/generate.js ./docs/v2-migration/help.txt ./docs/v2-migration/help.json",
"gen-config": "node ./src/components/Configuration/generate.js ./docs/configuration/help.txt ./docs/configuration/help.json",
"gen-config:v2": "node ./src/components/Configuration/generate.js ./docs/migration-from-v2/help.txt ./docs/migration-from-v2/help.json",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"gen-api-docs": "./gen-api-docs.sh",

View file

@ -1,4 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")/.."
# Clean the API docs
docusaurus clean-api-docs all

22
webpage/scripts/gen-config.sh Executable file
View file

@ -0,0 +1,22 @@
#!/bin/bash
cd "$(dirname "$0")/.."
HELP_FILE="$(realpath -m docs/configuration/help.txt)"
pushd ../server
go run cmd/neko/main.go serve --help > $HELP_FILE
popd
# remove all lines with " V2: "
sed -i '/ V2: /d' $HELP_FILE
# remove all lines with " V2 DEPRECATED: "
sed -i '/ V2 DEPRECATED: /d' $HELP_FILE
# remove --legacy
sed -i '/--legacy/d' $HELP_FILE
# remove evething until first "Flags:"
sed -i '1,/Flags:/d' $HELP_FILE
# remove --help
sed -i '/--help/d' $HELP_FILE
npm run gen-config

View file

@ -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;

View file

@ -3,130 +3,244 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
interface ConfigOption {
key: string[];
description: string;
defaultValue?: string;
interface ConfigOptionValue {
type?: string;
description?: string;
defaultValue?: string;
}
interface ConfigurationTabProps {
configOptions: ConfigOption[];
interface ConfigOption extends ConfigOptionValue {
key: string[];
}
const ConfigurationTab: React.FC<ConfigurationTabProps> = ({ configOptions }) => {
const environmentVariables = () => {
let code = '';
configOptions.forEach(option => {
let value = ""
if (option.defaultValue) {
value = `"${option.defaultValue}"`
} else if (option.type) {
value = `<${option.type}>`
}
code += `# ${option.description}\n`;
code += `NEKO_${option.key.join('_').toUpperCase()}: ${value}\n`;
});
return (
<CodeBlock language="yaml">
{code}
</CodeBlock>
);
function configKey(key: string | string[], value: ConfigOptionValue | any): ConfigOption {
if (typeof key === 'string') {
key = key.split('.');
}
if (typeof value === 'object') {
return {
key,
type: value.type || getType(value.defaultValue),
description: value.description,
defaultValue: value.defaultValue,
}
} else {
return {
key,
type: getType(value),
defaultValue: value,
}
}
}
function configKeys(optValues: Record<string, ConfigOptionValue | any>): ConfigOption[] {
let options: ConfigOption[] = [];
Object.entries(optValues).forEach(([key, value]) => {
options.push(configKey(key, value));
});
return options;
}
function filterKeys(options: ConfigOption[], filter: string): ConfigOption[] {
return options.filter(option => {
const key = option.key.join('.');
return key.startsWith(filter)
});
}
function defaultValue(value: ConfigOptionValue): string {
switch (value.type) {
case 'boolean':
return `${value.defaultValue || false}`;
case 'int':
case 'float':
case 'number':
return `${value.defaultValue || 0}`;
case 'duration':
case 'string':
return `${value.defaultValue ? `"${value.defaultValue}"` : '<string>'}`;
case 'strings':
return '<comma-separated list of strings>';
case 'object':
return '<json encoded object>';
case 'array':
return '<json encoded array>';
default:
return value.type ? `<${value.type}>` : '';
}
}
function getType(value: any): string {
if (Array.isArray(value)) {
return 'array';
}
return typeof value;
}
export function EnvironmentVariables({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) {
if (typeof comments === 'undefined') {
comments = true;
}
const cmdArguments = () => {
let code = '';
configOptions.forEach(option => {
code += `# ${option.description}\n`;
code += `--${option.key.join('.')}`;
if (option.type) {
code += ` <${option.type}>`;
}
code += '\n';
});
return (
<CodeBlock language="shell">
{code}
</CodeBlock>
);
let code = '';
options.forEach(option => {
const description = option.description ? option.description : '';
const type = option.type ? ` (${option.type})` : '';
if (comments && description) {
code += `# ${description}${type}\n`;
}
code += `NEKO_${option.key.join('_').toUpperCase()}=${defaultValue(option)}\n`;
});
return (
<CodeBlock language="shell" {...props}>
{code}
</CodeBlock>
);
}
export function CommandLineArguments({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) {
if (typeof comments === 'undefined') {
comments = true;
}
const yamlFile = () => {
const final = Symbol('final');
let code = '';
options.forEach(option => {
const description = option.description ? option.description : '';
const type = option.type ? ` (${option.type})` : '';
if (comments && description) {
code += `# ${description}${type}\n`;
}
code += `--${option.key.join('.')} ${defaultValue(option)}\n`;
});
const buildYaml = (obj, prefix = '') => {
let code = '';
Object.keys(obj).forEach(key => {
const value = obj[key];
if (typeof value === 'object' && !Array.isArray(value) && !value[final]) {
code += prefix+`${key}:\n`;
code += buildYaml(value, prefix + ' ');
return (
<CodeBlock language="shell" {...props}>
{code}
</CodeBlock>
);
}
export function YamlFileContent({ options, comments, ...props }: { options: ConfigOption[], comments?: boolean }) {
if (typeof comments === 'undefined') {
comments = true;
}
const final = Symbol('final');
const buildYaml = (obj: Record<string, any>, prefix = '') => {
let code = '';
Object.entries(obj).forEach(([key, option]) => {
if (typeof option === 'object' && !Array.isArray(option) && !option[final]) {
code += prefix+`${key}:\n`;
code += buildYaml(option, prefix + ' ');
} else {
const description = option.description ? option.description : '';
const type = option.type ? ` (${option.type})` : '';
if (comments && description) {
code += `${prefix}# ${description}${type}\n`;
}
let value: string;
if (option.type === 'strings') {
value = option.defaultValue ? `[ "${option.defaultValue}" ]` : '[ <string> ]';
} else if (option.type === 'object') {
value = "{}"
} else if (option.type === 'array') {
value = "[]"
} else {
let val = '';
switch (value.type) {
case 'boolean':
val = `${value.defaultValue || false}`;
break;
case 'int':
case 'float':
val = `${value.defaultValue || 0}`;
break;
case 'strings':
val = `[ ${value.defaultValue ? value.defaultValue.map(v => `"${v}"`).join(', ') : '<string>'} ]`;
break;
case 'duration':
case 'string':
val = `${value.defaultValue ? `"${value.defaultValue}"` : '<string>'}`;
break;
default:
val = `<${value.type}>`;
break;
}
code += prefix+`# ${value.description || ''}\n`;
code += prefix+`${key}: ${val}\n`;
value = defaultValue(option);
}
});
return code;
};
code += `${prefix}${key}: ${value}\n`;
}
});
return code;
};
const yamlCode = buildYaml(configOptions.reduce((acc, option) => {
const keys = option.key;
let current = acc;
keys.forEach((key, index) => {
if (!current[key]) {
current[key] = index === keys.length - 1 ? option : {};
}
current = current[key];
});
current[final] = true;
return acc;
}, {}));
const yamlCode = buildYaml(options.reduce((acc, option) => {
const keys = option.key;
let current = acc;
keys.forEach((key, index) => {
if (!current[key]) {
current[key] = index === keys.length - 1 ? option : {};
}
current = current[key];
});
current[final] = true;
return acc;
}, {}));
return (
<CodeBlock language="yaml">
{yamlCode}
</CodeBlock>
);
return (
<CodeBlock language="yaml" {...props}>
{yamlCode}
</CodeBlock>
);
}
type ConfigurationTabProps = {
options?: ConfigOption[] | Record<string, ConfigOptionValue | any>;
heading?: boolean;
comments?: boolean;
filter?: string | string[] | Record<string, ConfigOptionValue | any>;
};
export function ConfigurationTab({ options, heading, comments, filter, ...props }: ConfigurationTabProps) {
var configOptions: ConfigOption[] = [];
if (Array.isArray(options)) {
configOptions = options;
} else {
configOptions = configKeys(options)
}
if (typeof comments === 'undefined') {
comments = true;
}
if (typeof heading === 'undefined') {
heading = false;
}
if (Array.isArray(filter)) {
let filteredOptions: ConfigOption[] = [];
for (const f of filter) {
filteredOptions = [ ...filteredOptions, ...filterKeys(configOptions, f) ];
}
configOptions = filteredOptions;
} else if (typeof filter === 'string') {
configOptions = filterKeys(configOptions, filter);
} else if (typeof filter === 'object') {
let filteredOptions: ConfigOption[] = [];
for (const k in filter) {
let filtered = configOptions.find(option => {
return option.key.join('.') === k;
});
let replaced = configKey(k, filter[k]);
filteredOptions = [ ...filteredOptions, { ...filtered, ...replaced } ];
}
configOptions = filteredOptions;
}
return (
<div>
<Tabs>
<TabItem value="env" label="Environment Variables">
<Tabs groupId="configuration" defaultValue="yaml" values={[
{ label: 'YAML Configuration File', value: 'yaml' },
{ label: 'Environment Variables', value: 'env' },
{ label: 'Command Line Arguments', value: 'args' },
]} {...props}>
<TabItem value="env" label="Environment Variables">
{heading && (
<p>You can set the following environment variables in your <code>docker-compose.yaml</code> file or in your shell environment.</p>
{environmentVariables()}
</TabItem>
<TabItem value="args" label="Command Line Arguments">
)}
{EnvironmentVariables({ options: configOptions, comments })}
</TabItem>
<TabItem value="args" label="Command Line Arguments">
{heading && (
<p>You can list the following command line arguments using <code>neko serve --help</code>.</p>
{cmdArguments()}
</TabItem>
<TabItem value="yaml" label="YAML Configuration File">
)}
{CommandLineArguments({ options: configOptions, comments })}
</TabItem>
<TabItem value="yaml" label="YAML Configuration File">
{heading && (
<p>You can create a <code>/etc/neko/neko.yaml</code> file with the following configuration options.</p>
{yamlFile()}
</TabItem>
</Tabs>
</div>
)}
{YamlFileContent({ options: configOptions, comments })}
</TabItem>
</Tabs>
);
};
export default ConfigurationTab;
}

View file

@ -9,3 +9,11 @@ We are here to assist you with any issues you may face while setting up or using
- For installation or usage questions, [join our Discord](https://discord.gg/3U6hWpC) and post in the [#community-help](https://discord.com/channels/665851821906067466/696222582114091088) channel.
- To report bugs or request features, [open a new issue on GitHub](https://github.com/m1k1o/neko/issues).
- If you find an issue with this documentation, click the `Edit this page` button at the bottom of the page and then the `edit` button on the GitHub page that opens to make edits directly from your browser. (See a [step-by-step guide here](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files)).
**Enterprise Support**
For enterprises or organizations requiring **dedicated, paid support**, custom solutions, or priority assistance, please contact us at <a href="mailto:neko@m1k1o.net">neko@m1k1o.net</a>.
**Security**
If you discover a security vulnerability in Neko, please report it to us directly at <a href="mailto:security@m1k1o.net">security@m1k1o.net</a>. We take security seriously and will work with you to address the issue promptly. Please do not disclose the vulnerability publicly until we have had a chance to address it. We appreciate your help in keeping Neko secure.

View file

@ -0,0 +1,3 @@
Contact: mailto:security@m1k1o.net
Policy: https://github.com/m1k1o/neko/security
Expires: 2030-04-01T00:00:00z