mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1b84c7e7ba | ||
|
21a4b2b797 | ||
|
5e96bca296 | ||
|
c78d797fe7 | ||
|
57596315e9 | ||
|
0d7887e9d2 | ||
|
978fd8977d | ||
|
4ab5901ba9 | ||
|
11a862f101 | ||
|
b938a4e09e | ||
|
e26e4d2004 | ||
|
5f698330fc | ||
|
f32fc989b9 | ||
|
d1f1be4e86 | ||
|
e754e66878 | ||
|
26af1dc7f5 | ||
|
0c9055e4f6 | ||
|
c200326512 | ||
|
b1ce755210 | ||
|
2b13220d63 | ||
|
db6f9c957e | ||
|
798bf579c0 | ||
|
2f9964580f | ||
|
b8881b3a46 | ||
|
355c0eac0d | ||
|
4d023df692 | ||
|
792b1ac111 | ||
|
a03b29ba01 | ||
|
683b750189 | ||
|
3c4d7b9d60 | ||
|
7a9b33706a | ||
|
052a961fd9 | ||
|
8ef9c1aff5 | ||
|
6ed3493aa0 | ||
|
5959d056f3 | ||
|
92ad202bfe | ||
|
cd4acb5eec |
@ -27,7 +27,7 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
|
@ -27,29 +27,32 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:18-bullseye-slim as client
|
||||
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends python2 build-essential
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
COPY client/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Because client builds fail in Github Actions, therefor we build it outside of Docker.
|
||||
#
|
||||
# FROM node:18-bullseye-slim as client
|
||||
#
|
||||
# # install dependencies
|
||||
# RUN set -eux; apt-get update; \
|
||||
# apt-get install -y --no-install-recommends python2 build-essential
|
||||
#
|
||||
# WORKDIR /src
|
||||
#
|
||||
# #
|
||||
# # install dependencies
|
||||
# COPY client/package*.json ./
|
||||
# RUN npm install
|
||||
#
|
||||
# #
|
||||
# # build client
|
||||
# COPY client/ .
|
||||
# RUN npm run build
|
||||
#
|
||||
# build client
|
||||
COPY client/ .
|
||||
RUN npm run build
|
||||
|
||||
#
|
||||
# STAGE 3: RUNTIME
|
||||
#
|
||||
@ -134,7 +137,8 @@ ENV NEKO_BIND=:8080
|
||||
#
|
||||
# copy static files from previous stages
|
||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||
COPY --from=client /src/dist/ /var/www
|
||||
# COPY --from=client /src/dist/ /var/www
|
||||
COPY client/dist/ /var/www
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
|
||||
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
|
||||
|
@ -27,7 +27,7 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
|
@ -82,7 +82,7 @@ RUN set -eux; apt-get update; \
|
||||
#
|
||||
# build server
|
||||
COPY server/ .
|
||||
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
RUN ./build
|
||||
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
|
@ -10,7 +10,7 @@ RUN set -eux; \
|
||||
#
|
||||
# install widevine module
|
||||
CHROMIUM_DIR="/usr/lib/chromium"; \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | sort --version-sort | tail -n 1); \
|
||||
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
|
||||
mkdir -p "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64"; \
|
||||
unzip -p /tmp/widevine.zip LICENSE.txt > "${CHROMIUM_DIR}/WidevineCdm/LICENSE"; \
|
||||
|
@ -14,7 +14,7 @@ RUN set -eux; \
|
||||
#
|
||||
# install widevine module
|
||||
CHROMIUM_DIR="/usr/lib/chromium"; \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | sort --version-sort | tail -n 1); \
|
||||
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
|
||||
mkdir -p "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64"; \
|
||||
unzip -p /tmp/widevine.zip LICENSE.txt > "${CHROMIUM_DIR}/WidevineCdm/LICENSE"; \
|
||||
|
@ -3,7 +3,9 @@ FROM $BASE_IMAGE
|
||||
|
||||
# latest working version with EGL: 111.0.5563.146, revert when resolved
|
||||
# 112.0.5615.49 fails: https://github.com/VirtualGL/virtualgl/issues/229
|
||||
ARG SRC_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_111.0.5563.146-1_amd64.deb"
|
||||
# google does not provide a direct link to the deb file anymore
|
||||
# ARG SRC_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_111.0.5563.146-1_amd64.deb"
|
||||
ARG SRC_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
|
||||
|
||||
#
|
||||
# install google chrome
|
||||
|
@ -8,7 +8,7 @@ ARG API_URL="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edg
|
||||
RUN set -eux; apt-get update; \
|
||||
#
|
||||
# fetch latest release
|
||||
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | tail -1)"; \
|
||||
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | sort --version-sort | tail -1)"; \
|
||||
wget -O /tmp/microsoft-edge.deb "${SRC_URL}"; \
|
||||
apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \
|
||||
#
|
||||
|
@ -8,7 +8,7 @@ ARG API_URL="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edg
|
||||
RUN set -eux; apt-get update; \
|
||||
#
|
||||
# fetch latest release
|
||||
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | tail -1)"; \
|
||||
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | sort --version-sort | tail -1)"; \
|
||||
wget -O /tmp/microsoft-edge.deb "${SRC_URL}"; \
|
||||
apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \
|
||||
#
|
||||
|
@ -9,7 +9,7 @@ ARG LIBFFMPEG_API_URL="https://api.github.com/repos/nwjs-ffmpeg-prebuilt/nwjs-ff
|
||||
RUN set -eux; apt-get update; \
|
||||
#
|
||||
# fetch latest release
|
||||
VERSION="$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"/]*\).*/\1/p' | tail -1)"; \
|
||||
VERSION="$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"/]*\).*/\1/p' | sort --version-sort | tail -1)"; \
|
||||
wget -O /tmp/opera.deb "${API_URL}${VERSION}/linux/opera-stable_${VERSION}_amd64.deb"; \
|
||||
apt-get install -y --no-install-recommends openbox jq unzip /tmp/opera.deb; \
|
||||
#
|
||||
|
@ -21,7 +21,7 @@ RUN set -eux; apt-get update; \
|
||||
chmod 4755 /usr/lib/chromium/chrome-sandbox; \
|
||||
#
|
||||
# install widevine module
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
|
||||
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | sort --version-sort | tail -n 1); \
|
||||
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
|
||||
unzip -p /tmp/widevine.zip libwidevinecdm.so > /usr/lib/chromium/libwidevinecdm.so; \
|
||||
chmod 644 /usr/lib/chromium/libwidevinecdm.so; \
|
||||
|
37
.github/workflows/ghcr-arm.yml
vendored
37
.github/workflows/ghcr-arm.yml
vendored
@ -13,7 +13,7 @@ env:
|
||||
PLATFORMS: linux/arm64,linux/arm/v7
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
build-client:
|
||||
runs-on: ubuntu-latest
|
||||
#
|
||||
# do not run on forks
|
||||
@ -23,6 +23,41 @@ jobs:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
-
|
||||
name: Build client
|
||||
run: |
|
||||
cd client
|
||||
npm install
|
||||
npm run build
|
||||
-
|
||||
name: Upload client dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: client-dist
|
||||
path: client/dist
|
||||
|
||||
build-base:
|
||||
runs-on: ubuntu-latest
|
||||
#
|
||||
# do not run on forks
|
||||
#
|
||||
if: github.repository_owner == 'm1k1o'
|
||||
needs: [ build-client ]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Download client dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: client-dist
|
||||
path: client/dist
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
4
LICENSE
4
LICENSE
@ -186,7 +186,9 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020 Nurdism <nurdism.io@gmail.com>, 2020-2021 m1k1o
|
||||
Copyright (C) 2020 Nurdism <nurdism.io@gmail.com>
|
||||
Copyright (C) 2020-2023 m1k1o
|
||||
All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -204,7 +204,9 @@
|
||||
|
||||
@Watch('volume', { immediate: true })
|
||||
onVolume(volume: number) {
|
||||
this.$accessor.video.setVolume(volume)
|
||||
if (new URL(location.href).searchParams.has('volume')) {
|
||||
this.$accessor.video.setVolume(volume)
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('hideControls', { immediate: true })
|
||||
|
@ -76,5 +76,20 @@
|
||||
about() {
|
||||
this.$accessor.client.toggleAbout()
|
||||
}
|
||||
|
||||
mounted() {
|
||||
const default_lang = new URL(location.href).searchParams.get('lang')
|
||||
if (default_lang && this.langs.includes(default_lang)) {
|
||||
this.$i18n.locale = default_lang
|
||||
}
|
||||
const show_side = new URL(location.href).searchParams.get('show_side')
|
||||
if (show_side !== null) {
|
||||
this.$accessor.client.setSide(show_side === '1')
|
||||
}
|
||||
const mute_chat = new URL(location.href).searchParams.get('mute_chat')
|
||||
if (mute_chat !== null) {
|
||||
this.$accessor.settings.setSound(mute_chat !== '1')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -13,6 +13,7 @@
|
||||
class="overlay"
|
||||
tabindex="0"
|
||||
data-gramm="false"
|
||||
:style="{ pointerEvents: hosting ? 'auto' : 'none' }"
|
||||
@click.stop.prevent
|
||||
@contextmenu.stop.prevent
|
||||
@wheel.stop.prevent="onWheel"
|
||||
@ -36,7 +37,7 @@
|
||||
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
|
||||
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
|
||||
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
|
||||
<li v-if="!implicitHosting" :class="extraControls || 'extra-control'">
|
||||
<li v-if="!controlLocked && !implicitHosting" :class="extraControls || 'extra-control'">
|
||||
<i
|
||||
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
||||
@click.stop.prevent="toggleControl"
|
||||
@ -312,7 +313,15 @@
|
||||
}
|
||||
|
||||
get clipboard_read_available() {
|
||||
return 'clipboard' in navigator && typeof navigator.clipboard.readText === 'function'
|
||||
return (
|
||||
'clipboard' in navigator &&
|
||||
typeof navigator.clipboard.readText === 'function' &&
|
||||
// Firefox 122+ incorrectly reports that it can read the clipboard but it can't
|
||||
// instead it hangs when reading clipboard, until user clicks on the page
|
||||
// and the click itself is not handled by the page at all, also the clipboard
|
||||
// reads always fail with "Clipboard read operation is not allowed."
|
||||
navigator.userAgent.indexOf('Firefox') == -1
|
||||
)
|
||||
}
|
||||
|
||||
get clipboard_write_available() {
|
||||
@ -629,7 +638,7 @@
|
||||
}
|
||||
|
||||
async syncClipboard() {
|
||||
if (this.clipboard_read_available) {
|
||||
if (this.clipboard_read_available && window.document.hasFocus()) {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText()
|
||||
if (this.clipboard !== text) {
|
||||
|
@ -9,6 +9,7 @@ import * as ko from './ko-kr'
|
||||
import * as fi from './fi-fi'
|
||||
import * as ru from './ru-ru'
|
||||
import * as cn from './zh-cn'
|
||||
import * as tw from './zh-tw'
|
||||
|
||||
export const messages = {
|
||||
en,
|
||||
@ -22,4 +23,5 @@ export const messages = {
|
||||
fi,
|
||||
ru,
|
||||
cn,
|
||||
tw,
|
||||
}
|
||||
|
127
client/src/locale/zh-tw.ts
Normal file
127
client/src/locale/zh-tw.ts
Normal file
@ -0,0 +1,127 @@
|
||||
export const logout = '登出'
|
||||
export const unsupported = '您的網頁瀏覽器不支援 WebRTC'
|
||||
export const admin_loggedin = '您已經以管理員身份登入'
|
||||
export const you = '您'
|
||||
export const somebody = '某人'
|
||||
export const send_a_message = '傳送訊息'
|
||||
|
||||
export const side = {
|
||||
chat: '聊天',
|
||||
files: '檔案',
|
||||
settings: '設定',
|
||||
}
|
||||
|
||||
export const connect = {
|
||||
login_title: '請登入',
|
||||
invitation_title: '您已被邀請進入此房間',
|
||||
displayname: '輸入您的顯示名稱',
|
||||
password: '密碼',
|
||||
connect: '連線',
|
||||
error: '登入錯誤',
|
||||
empty_displayname: '顯示名稱不能為空。',
|
||||
}
|
||||
|
||||
export const context = {
|
||||
ignore: '忽略',
|
||||
unignore: '取消忽略',
|
||||
mute: '靜音',
|
||||
unmute: '解除靜音',
|
||||
release: '強制釋放控制',
|
||||
take: '強制接管控制',
|
||||
give: '移交控制',
|
||||
kick: '踢出',
|
||||
ban: '封鎖 IP',
|
||||
confirm: {
|
||||
kick_title: '踢出 {name}?',
|
||||
kick_text: '您確定要踢出 {name} 嗎?',
|
||||
ban_title: '封鎖 {name}?',
|
||||
ban_text: '您是否要封鎖 {name}?封鎖後需要重新啟動伺服器才能取消封鎖。',
|
||||
mute_title: '靜音 {name}?',
|
||||
mute_text: '您確定要將 {name} 設為靜音嗎?',
|
||||
unmute_title: '解除靜音 {name}?',
|
||||
unmute_text: '您是否要解除 {name} 的靜音?',
|
||||
button_yes: '是',
|
||||
button_cancel: '取消',
|
||||
},
|
||||
}
|
||||
|
||||
export const controls = {
|
||||
release: '釋放控制',
|
||||
request: '請求控制',
|
||||
lock: '鎖定控制',
|
||||
unlock: '解鎖控制',
|
||||
has: '您擁有控制權',
|
||||
hasnot: '您沒有控制權',
|
||||
}
|
||||
|
||||
export const locks = {
|
||||
control: {
|
||||
lock: '鎖定控制(對使用者)',
|
||||
unlock: '解鎖控制(對使用者)',
|
||||
locked: '已鎖定控制(對使用者)',
|
||||
unlocked: '已解鎖控制(對使用者)',
|
||||
notif_locked: '已鎖定使用者控制',
|
||||
notif_unlocked: '已解鎖使用者控制',
|
||||
},
|
||||
login: {
|
||||
lock: '鎖定房間(對使用者)',
|
||||
unlock: '解鎖房間(對使用者)',
|
||||
locked: '房間已鎖定(對使用者)',
|
||||
unlocked: '房間已解鎖(對使用者)',
|
||||
notif_locked: '已鎖定房間',
|
||||
notif_unlocked: '已解鎖房間',
|
||||
},
|
||||
file_transfer: {
|
||||
lock: '鎖定檔案傳輸(對使用者)',
|
||||
unlock: '解鎖檔案傳輸(對使用者)',
|
||||
locked: '檔案傳輸已鎖定(對使用者)',
|
||||
unlocked: '檔案傳輸已解鎖(對使用者)',
|
||||
notif_locked: '已鎖定檔案傳輸',
|
||||
notif_unlocked: '已解鎖檔案傳輸',
|
||||
},
|
||||
}
|
||||
|
||||
export const setting = {
|
||||
scroll: '滾動靈敏度',
|
||||
scroll_invert: '反向滾動',
|
||||
autoplay: '自動播放影片',
|
||||
ignore_emotes: '忽略表情符號',
|
||||
chat_sound: '播放聊天音效',
|
||||
keyboard_layout: '鍵盤配置',
|
||||
broadcast_title: '直播',
|
||||
}
|
||||
|
||||
export const connection = {
|
||||
logged_out: '您已登出。',
|
||||
reconnecting: '正在重新連線…',
|
||||
connected: '已連線',
|
||||
disconnected: '已斷線',
|
||||
kicked: '您已被移出此房間。',
|
||||
button_confirm: '確定',
|
||||
}
|
||||
|
||||
export const notifications = {
|
||||
connected: '{name} 已連線',
|
||||
disconnected: '{name} 已斷線',
|
||||
controls_taken: '{name} 接管了控制權',
|
||||
controls_taken_force: '強制接管控制權',
|
||||
controls_taken_steal: '從 {name} 奪取了控制權',
|
||||
controls_released: '{name} 釋放了控制權',
|
||||
controls_released_force: '強制釋放控制權',
|
||||
controls_released_steal: '從 {name} 強制釋放控制權',
|
||||
controls_given: '將控制權交給 {name}',
|
||||
controls_has: '{name} 擁有控制權',
|
||||
controls_has_alt: '但我已通知對方您有意接管',
|
||||
controls_requesting: '{name} 正在請求控制權',
|
||||
resolution: '將解析度改為 {width}x{height}@{rate}',
|
||||
banned: '已封鎖 {name}',
|
||||
kicked: '已踢出 {name}',
|
||||
muted: '已將 {name} 靜音',
|
||||
unmuted: '已解除 {name} 的靜音',
|
||||
}
|
||||
|
||||
export const files = {
|
||||
downloads: '下載',
|
||||
uploads: '上傳',
|
||||
upload_here: '點選或拖曳檔案至此上傳',
|
||||
}
|
@ -6,5 +6,6 @@ Vue.use(VueI18n)
|
||||
|
||||
export const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages,
|
||||
})
|
||||
|
@ -27,6 +27,10 @@ export const mutations = mutationTree(state, {
|
||||
state.side = !state.side
|
||||
set('side', state.side)
|
||||
},
|
||||
setSide(state, side: boolean) {
|
||||
state.side = side
|
||||
set('side', side)
|
||||
},
|
||||
})
|
||||
|
||||
export const actions = actionTree({ state, getters, mutations }, {})
|
||||
|
@ -6,6 +6,7 @@
|
||||
* [Reverse Proxy](/getting-started/reverse-proxy)
|
||||
* [Configuration](/getting-started/configuration)
|
||||
* [Troubleshooting](/getting-started/troubleshooting)
|
||||
* [Frequently Asked Questions](/getting-started/faq)
|
||||
* [Mobile Support](/mobile-support)
|
||||
* [Contributing](/contributing)
|
||||
* [Non Goals](/non-goals)
|
||||
|
@ -4,6 +4,21 @@
|
||||
|
||||
### New Features
|
||||
- Added nvidia support for firefox.
|
||||
- Added `?lang=<lang>` parameter to the URL, which will set the language of the interface (by @mbattista).
|
||||
- Added `?show_side=1` and `?mute_chat=1` parameter to the URL, for chat mute and show side (by @mbattista).
|
||||
- Added `NEKO_BROADCAST_AUTOSTART` to automatically start or do not start broadcasting when the room is created. By default, it is set to `true` because it was the previous behavior.
|
||||
|
||||
### Bugs
|
||||
- Fix incorrect version sorting for chromium, microsoft-edge, opera and ungoogledchromium.
|
||||
- Fix buffer overflow in Gstreamer log function [#382](https://github.com/m1k1o/neko/pull/382) (by @@tt2468).
|
||||
|
||||
### Misc
|
||||
- Added RTMP broadcast support to nvidia docker image [#274](https://github.com/m1k1o/neko/issues/274).
|
||||
- Ensured that paths are writable by neko user [#277](https://github.com/m1k1o/neko/issues/277).
|
||||
- Git commit and tag are now included in the build when creating a docker image.
|
||||
- Remove any temporary files associated with a Form after file upload, that would be otherwise never removed.
|
||||
- Add check for volume parameter in URL before setting volume (by @FapFapDragon).
|
||||
- Add glib main loop to capture manager [#383](https://github.com/m1k1o/neko/pull/383) (by @tt2468).
|
||||
|
||||
## [n.eko v2.8.0](https://github.com/m1k1o/neko/releases/tag/v2.8.0)
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
## Server build dependencies
|
||||
|
||||
If you want to compile goalng code locally, you must install additional dependencies in order for it to compile.
|
||||
If you want to compile Golang code locally, you must install additional dependencies in order for it to compile.
|
||||
|
||||
```shell
|
||||
apt-get install -y --no-install-recommends libx11-dev libxrandr-dev libxtst-dev libgstreamer1.0-dev
|
||||
|
@ -50,7 +50,7 @@ All images are also available on [GitHub Container Registry](https://github.com/
|
||||
- `ghcr.io/m1k1o/neko/xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/kde:latest`
|
||||
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently, not all images are available for ARM, because not all applications are available for ARM.
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently, not all images are available for ARM, because not all applications are available for ARM. Please note, that `m1k1o/neko:arm-*` images from dockerhub are currently not maintained and they can contain outdated software. Please use images below:
|
||||
|
||||
- `ghcr.io/m1k1o/neko/arm-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-chromium:latest`
|
||||
@ -74,8 +74,9 @@ For images with VAAPI GPU hardware acceleration using intel drivers use:
|
||||
- `ghcr.io/m1k1o/neko/intel-xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-kde:latest`
|
||||
|
||||
For images with Nvidia GPU hardware acceleration using EGL (see example below) use:
|
||||
For images with Nvidia GPU hardware acceleration using EGL (see example below) use (please note, there is a known issue with EGL and Chromium-based browsers, see [here](https://github.com/m1k1o/neko/issues/279)):
|
||||
|
||||
- `ghcr.io/m1k1o/neko/nvidia-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-chromium:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-google-chrome:latest`
|
||||
- `ghcr.io/m1k1o/neko/nvidia-microsoft-edge:latest`
|
||||
@ -86,7 +87,48 @@ GHCR images are built using GitHub actions for every tag.
|
||||
### Networking:
|
||||
- If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP.
|
||||
- If you want to use n.eko in **internal** network, set `NEKO_NAT1TO1` to your local IP address (e.g. `NEKO_NAT1TO1: 192.168.1.20`)-
|
||||
- Currently, it is not supported to supply multiple NAT addresses (see https://github.com/m1k1o/neko/issues/47).
|
||||
|
||||
Currently, it is not supported to supply multiple NAT addresses directly to neko (see https://github.com/m1k1o/neko/issues/47).
|
||||
|
||||
But it can be acheived by deploying own turn server alongside neko that is accessible from your LAN:
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
services:
|
||||
neko:
|
||||
image: "m1k1o/neko:firefox"
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "52000-52100:52000-52100/udp"
|
||||
environment:
|
||||
NEKO_SCREEN: 1920x1080@30
|
||||
NEKO_PASSWORD: neko
|
||||
NEKO_PASSWORD_ADMIN: admin
|
||||
NEKO_EPR: 52000-52100
|
||||
NEKO_ICESERVERS: '[{ "urls": [ "turn:192.168.1.60:3478" ], "username":"neko", "credential":"neko" }, { "urls": [ "stun:stun.nextcloud.com:3478" ] }]'
|
||||
coturn:
|
||||
image: 'coturn/coturn:latest'
|
||||
network_mode: "host"
|
||||
command: |
|
||||
-n
|
||||
--realm=localhost
|
||||
--fingerprint
|
||||
--listening-ip=0.0.0.0
|
||||
--external-ip=192.168.1.60
|
||||
--listening-port=3478
|
||||
--min-port=49160
|
||||
--max-port=49200
|
||||
--log-file=stdout
|
||||
--user=neko:neko
|
||||
--lt-cred-mech
|
||||
```
|
||||
|
||||
- Replace `192.168.1.60` with your LAN IP address, and allow ports `49160-49200/udp` and `3478/tcp` in your LAN.
|
||||
- Make sure you don't use `NEKO_ICELITE: true` because ICE LITE does not support TURN servers.
|
||||
|
||||
This setup adds local turn server to neko. It won't be reachable by your remote clients and your own IP won't be reachable from your lan. So it effectively just adds local candidate and allows connections from LAN.
|
||||
|
||||
### Why so many ports?
|
||||
- WebRTC needs UDP ports in order to transfer Audio/Video towards user and Mouse/Keyboard events to the server in real time.
|
||||
@ -175,7 +217,7 @@ NEKO_ICESERVERS: '[{"urls": ["turn:<MY-COTURN-SERVER>:443?transport=udp", "turn:
|
||||
|
||||
### Nvidia GPU acceleration
|
||||
|
||||
You need to have [nvidia-docker](https://github.com/NVIDIA/nvidia-docker) installed, start the container with `--gpus all` flag and use images built for nvidia (see above).
|
||||
You need to have [NVIDIA Container Toolkit](https://github.com/NVIDIA/nvidia-container-toolkit) installed, start the container with `--gpus all` flag and use images built for nvidia (see above).
|
||||
|
||||
```bash
|
||||
docker run -d --gpus all \
|
||||
@ -255,6 +297,9 @@ NEKO_BROADCAST_PIPELINE: "flvmux name=mux ! rtmpsink location={url} pulsesrc dev
|
||||
- Adding `?cast=1` will hide all control and show only video.
|
||||
- Adding `?embed=1` will hide most additional components and show only video.
|
||||
- Adding `?volume=<0-1>` will set volume to given value.
|
||||
- Adding `?lang=<language>` will set language to given value.
|
||||
- Adding `?show_side=1` will show the sidebar on startup.
|
||||
- Adding `?mute_chat=1` will mute the chat on startup.
|
||||
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
|
||||
|
||||
### Screen size
|
||||
|
@ -107,9 +107,11 @@ nat1to1: <ip>
|
||||
#### `NEKO_BROADCAST_PIPELINE`:
|
||||
- Makes it possible to create custom gstreamer pipeline used for broadcasting, strings `{url}`, `{device}` and `{display}` will be replaced.
|
||||
#### `NEKO_BROADCAST_URL`:
|
||||
- Set a default URL for broadcast streams. Setting this value will automatically enable broadcasting when n.eko starts. It can be disabled/changed later by admins in the GUI.
|
||||
- Set a default URL for broadcast streams. It can be disabled/changed later by admins in the GUI.
|
||||
- e.g. `rtmp://<your-server>:1935/ingest/<stream-key>`
|
||||
|
||||
#### `NEKO_BROADCAST_AUTOSTART`:
|
||||
- Automatically start broadcasting when neko starts and broadcast_url is set.
|
||||
- e.g. `true`
|
||||
### Server
|
||||
|
||||
#### `NEKO_BIND`:
|
||||
|
@ -72,7 +72,8 @@ services:
|
||||
version: "3.4"
|
||||
services:
|
||||
neko:
|
||||
image: "m1k1o/neko:arm-chromium"
|
||||
# see docs for more variants
|
||||
image: "ghcr.io/m1k1o/neko/arm-chromium:latest"
|
||||
restart: "unless-stopped"
|
||||
# increase on rpi's with more then 1gb ram.
|
||||
shm_size: "520mb"
|
||||
|
57
docs/getting-started/faq.md
Normal file
57
docs/getting-started/faq.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
## How to enable debug mode?
|
||||
|
||||
To see verbose information from n.eko server, you can enable debug mode using `NEKO_DEBUG`.
|
||||
|
||||
```diff
|
||||
version: "3.4"
|
||||
services:
|
||||
neko:
|
||||
image: "m1k1o/neko:firefox"
|
||||
restart: "unless-stopped"
|
||||
shm_size: "2gb"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "52000-52100:52000-52100/udp"
|
||||
environment:
|
||||
NEKO_SCREEN: 1920x1080@30
|
||||
NEKO_PASSWORD: neko
|
||||
NEKO_PASSWORD_ADMIN: admin
|
||||
NEKO_EPR: 52000-52100
|
||||
NEKO_ICELITE: 1
|
||||
+ NEKO_DEBUG: 1
|
||||
```
|
||||
|
||||
Ensure, that you have enabled debug mode in javascript console too, in order to see verbose information from client.
|
||||
|
||||
## Chinese input method is not working
|
||||
|
||||
There exists an extension for Chrome that allows you to use Chinese input method. You can install it from [here](https://chrome.google.com/webstore/detail/mclkkofklkfljcocdinagocijmpgbhab). Alternatively, you can use Google Input Tools from [here](https://www.google.com/inputtools/chrome/).
|
||||
|
||||
## Only black screen is displayed but remote cursor is moving for Chromium-based browsers (Chrome, Edge, etc.)
|
||||
|
||||
Check if you did not forget to add cap_add to your docker-compose file.
|
||||
|
||||
```yaml
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
```
|
||||
|
||||
## How can I embed the Neko desktop into web page without login prompt coming up for viewers?
|
||||
|
||||
You can use the following URL to embed the Neko desktop into a web page without login prompt coming up for viewers:
|
||||
|
||||
```
|
||||
http://<your-neko-server-ip>:8080/?usr=neko&pwd=neko
|
||||
```
|
||||
|
||||
https://stackoverflow.com/questions/15276929/how-to-make-a-video-fullscreen-when-it-is-placed-inside-an-iframe
|
||||
|
||||
Your iframe needs an attribute: `allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"` or more modern `allow="fullscreen *"`. For the second you can remove the star if your iframe has the same origin or replace it with your iframe origin.
|
||||
|
||||
## Can I use neko without docker?
|
||||
|
||||
It is strongly recommended to use Neko with Docker, as it is the easiest way to run it. But you should be able to install Neko "natively" on your host system. Neko is based on Debian and uses Xorg and Pulseaudio. You would just need to follow steps that are in Dockerfile, install all dependencies on your host system and then just run it.
|
||||
|
||||
However, it is recommend to start with existing system that has GUI with desktop manager, is based on Xorg and uses Pulseaudio (e.g. Ubuntu Desktop 22.04). For that matter you only need to install gstreamer dependencies, configure pulseaudio properly and run neko binary (you don't need to build it from scratch, you can copy it from docker image).
|
@ -4,15 +4,18 @@ Neko UI loads, but you don't see the screen, and it gives you `connection timeou
|
||||
|
||||
## Test your client
|
||||
|
||||
Some browser may block WebRTC access by default. You can check if it is enabled by going to `about:webrtc` or `chrome://webrtc-internals` in your browser.
|
||||
Some browsers may block WebRTC access by default. You can check if it is enabled by going to `about:webrtc` or `chrome://webrtc-internals` in your browser.
|
||||
|
||||
Check if your extensions are not blocking WebRTC access. For example, Privacy Badger or Private Internet Access blocks WebRTC by default.
|
||||
Check if your extensions are not blocking WebRTC access. Following extensions are known to block or does not work properly with WebRTC:
|
||||
- Privacy Badger
|
||||
- Private Internet Access
|
||||
- PIA VPN (even if disabled)
|
||||
|
||||
Test whether your client [supports](https://www.webrtc-experiment.com/DetectRTC/) and can [connect to WebRTC](https://www.webcasts.com/webrtc/).
|
||||
|
||||
## Networking
|
||||
|
||||
Most problems are networking related.
|
||||
If you are absolutely sure, that your client is working correctly, then most likely your networking is not set up correctly.
|
||||
|
||||
### Check if your ports are correctly exposed using docker
|
||||
|
||||
@ -59,6 +62,13 @@ Then try to type on one end, you should see characters on the other side.
|
||||
|
||||
If it does not work for you, then most likely your port forwarding is not working correctly. Or your ISP is blocking traffic.
|
||||
|
||||
|
||||
If you get [`Command 'nc' not found.`](https://command-not-found.com/nc) error, you can install `netcat` package using:
|
||||
|
||||
```shell
|
||||
sudo apt-get install netcat
|
||||
```
|
||||
|
||||
### Check if your external IP was determined correctly
|
||||
|
||||
One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to connect to.
|
||||
@ -67,6 +77,8 @@ One of the first logs, when the server starts, writes down your external IP that
|
||||
docker-compose logs neko | grep nat_ips
|
||||
```
|
||||
|
||||
Note: Some newer versions of docker-compose use `docker compose` instead of `docker-compose`.
|
||||
|
||||
You should see this:
|
||||
|
||||
```
|
||||
@ -94,7 +106,7 @@ services:
|
||||
+ NEKO_IPFETCH: https://ifconfig.co/ip
|
||||
```
|
||||
|
||||
Or you can specify your IP address manually using `NEKO_NAT1TO1`:
|
||||
Or you can specify your IP address manually using `NEKO_NAT1TO1`: (It's read as NAT 1 to 1, so it's capital letter 'O', not zero '0', in NAT1`TO`1)
|
||||
|
||||
```diff
|
||||
version: "3.4"
|
||||
@ -129,6 +141,7 @@ Example for pfsense with truecharts docker container:
|
||||
- Test externally to confirm it works.
|
||||
- Internally you have to access it using `<your-public-ip>:port`
|
||||
|
||||
If your router does not support NAT Loopback (NAT Hairpinning), you can use turn servers to overcome this issue. See [more details here](https://neko.m1k1o.net/#/getting-started/?id=networking) on how to setup local coturn instance.
|
||||
|
||||
### Neko works locally, but not externally
|
||||
|
||||
|
17
server/build
17
server/build
@ -1,24 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
#
|
||||
# aborting if any command returns a non-zero value
|
||||
set -e
|
||||
|
||||
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
||||
|
||||
#
|
||||
# set git build variables if git exists
|
||||
if git status > /dev/null 2>&1 && [ -z $GIT_COMMIT ] && [ -z $GIT_BRANCH ] && [ -z $GIT_DIRTY ];
|
||||
if git status > /dev/null 2>&1 && [ -z $GIT_COMMIT ] && [ -z $GIT_BRANCH ] && [ -z $GIT_TAG ];
|
||||
then
|
||||
GIT_COMMIT=`git rev-parse --short HEAD`
|
||||
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
|
||||
GIT_TAG=`git tag --points-at $GIT_COMMIT | head -n 1`
|
||||
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
|
||||
GIT_COMMIT="${GIT_DIRTY}${GIT_COMMIT}"
|
||||
fi
|
||||
|
||||
#
|
||||
# load dependencies
|
||||
go get -v -t -d .
|
||||
|
||||
#
|
||||
# build server
|
||||
go build \
|
||||
-o bin/neko \
|
||||
-ldflags "
|
||||
-s -w
|
||||
-X 'm1k1o/neko.buildDate=${BUILD_TIME}'
|
||||
-X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}'
|
||||
-X 'm1k1o/neko.gitCommit=${GIT_COMMIT}'
|
||||
-X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'
|
||||
-X 'm1k1o/neko.gitTag=${GIT_TAG}'
|
||||
" \
|
||||
cmd/neko/main.go;
|
||||
|
@ -4,7 +4,7 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/pion/ice/v2 v2.3.0
|
||||
|
@ -63,8 +63,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
|
@ -22,7 +22,7 @@ type BroacastManagerCtx struct {
|
||||
started bool
|
||||
}
|
||||
|
||||
func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string) *BroacastManagerCtx {
|
||||
func broadcastNew(pipelineFn func(url string) (string, error), url string, started bool) *BroacastManagerCtx {
|
||||
logger := log.With().
|
||||
Str("module", "capture").
|
||||
Str("submodule", "broadcast").
|
||||
@ -31,8 +31,8 @@ func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string
|
||||
return &BroacastManagerCtx{
|
||||
logger: logger,
|
||||
pipelineFn: pipelineFn,
|
||||
url: defaultUrl,
|
||||
started: defaultUrl != "",
|
||||
url: url,
|
||||
started: started && url != "",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
static void gstreamer_pipeline_log(GstPipelineCtx *ctx, char* level, const char* format, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
char buffer[100];
|
||||
vsprintf(buffer, format, argptr);
|
||||
char buffer[4096];
|
||||
vsnprintf(buffer, sizeof(buffer), format, argptr);
|
||||
va_end(argptr);
|
||||
goPipelineLog(level, buffer, ctx->pipelineId);
|
||||
}
|
||||
|
@ -31,12 +31,29 @@ var pSerial int32
|
||||
var pipelines = make(map[int]*Pipeline)
|
||||
var pipelinesLock sync.Mutex
|
||||
var registry *C.GstRegistry
|
||||
var gMainLoop *C.GMainLoop
|
||||
|
||||
func init() {
|
||||
C.gst_init(nil, nil)
|
||||
registry = C.gst_registry_get()
|
||||
}
|
||||
|
||||
func RunMainLoop() {
|
||||
if gMainLoop != nil {
|
||||
return
|
||||
}
|
||||
gMainLoop = C.g_main_loop_new(nil, C.int(0))
|
||||
C.g_main_loop_run(gMainLoop)
|
||||
}
|
||||
|
||||
func QuitMainLoop() {
|
||||
if gMainLoop == nil {
|
||||
return
|
||||
}
|
||||
C.g_main_loop_quit(gMainLoop)
|
||||
gMainLoop = nil
|
||||
}
|
||||
|
||||
func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
||||
id := atomic.AddInt32(&pSerial, 1)
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"m1k1o/neko/internal/capture/gst"
|
||||
"m1k1o/neko/internal/config"
|
||||
"m1k1o/neko/internal/types"
|
||||
)
|
||||
@ -30,7 +31,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
||||
// sinks
|
||||
broadcast: broadcastNew(func(url string) (string, error) {
|
||||
return NewBroadcastPipeline(config.AudioDevice, config.Display, config.BroadcastPipeline, url)
|
||||
}, config.BroadcastUrl),
|
||||
}, config.BroadcastUrl, config.BroadcastAutostart),
|
||||
audio: streamSinkNew(config.AudioCodec, func() (string, error) {
|
||||
return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate)
|
||||
}, "audio"),
|
||||
@ -53,6 +54,7 @@ func (manager *CaptureManagerCtx) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
go gst.RunMainLoop()
|
||||
go func() {
|
||||
for {
|
||||
before, ok := <-manager.desktop.GetScreenSizeChangeChannel()
|
||||
@ -100,6 +102,8 @@ func (manager *CaptureManagerCtx) Shutdown() error {
|
||||
manager.audio.shutdown()
|
||||
manager.video.shutdown()
|
||||
|
||||
gst.QuitMainLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,9 @@ type Capture struct {
|
||||
AudioPipeline string
|
||||
|
||||
// broadcast
|
||||
BroadcastPipeline string
|
||||
BroadcastUrl string
|
||||
BroadcastPipeline string
|
||||
BroadcastUrl string
|
||||
BroadcastAutostart bool
|
||||
}
|
||||
|
||||
func (Capture) Init(cmd *cobra.Command) error {
|
||||
@ -155,11 +156,16 @@ func (Capture) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("broadcast_url", "", "URL for broadcasting, setting this value will automatically enable broadcasting")
|
||||
cmd.PersistentFlags().String("broadcast_url", "", "a default default URL for broadcast streams, can be disabled/changed later by admins in the GUI")
|
||||
if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().Bool("broadcast_autostart", true, "automatically start broadcasting when neko starts and broadcast_url is set")
|
||||
if err := viper.BindPFlag("broadcast_autostart", cmd.PersistentFlags().Lookup("broadcast_autostart")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -247,4 +253,5 @@ func (s *Capture) Set() {
|
||||
|
||||
s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
|
||||
s.BroadcastUrl = viper.GetString("broadcast_url")
|
||||
s.BroadcastAutostart = viper.GetBool("broadcast_autostart")
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ type Server struct {
|
||||
Cert string
|
||||
Key string
|
||||
Bind string
|
||||
Proxy bool
|
||||
Static string
|
||||
PathPrefix string
|
||||
CORS []string
|
||||
@ -35,6 +36,11 @@ func (Server) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
|
||||
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("static", "./www", "path to neko client files to serve")
|
||||
if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil {
|
||||
return err
|
||||
@ -57,6 +63,7 @@ func (s *Server) Set() {
|
||||
s.Cert = viper.GetString("cert")
|
||||
s.Key = viper.GetString("key")
|
||||
s.Bind = viper.GetString("bind")
|
||||
s.Proxy = viper.GetBool("proxy")
|
||||
s.Static = viper.GetString("static")
|
||||
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))
|
||||
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
type WebSocket struct {
|
||||
Password string
|
||||
AdminPassword string
|
||||
Proxy bool
|
||||
Locks []string
|
||||
|
||||
ControlProtection bool
|
||||
@ -30,11 +29,6 @@ func (WebSocket) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
|
||||
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringSlice("locks", []string{}, "resources, that will be locked when starting (control, login)")
|
||||
if err := viper.BindPFlag("locks", cmd.PersistentFlags().Lookup("locks")); err != nil {
|
||||
return err
|
||||
@ -63,7 +57,6 @@ func (WebSocket) Init(cmd *cobra.Command) error {
|
||||
func (s *WebSocket) Set() {
|
||||
s.Password = viper.GetString("password")
|
||||
s.AdminPassword = viper.GetString("password_admin")
|
||||
s.Proxy = viper.GetBool("proxy")
|
||||
s.Locks = viper.GetStringSlice("locks")
|
||||
|
||||
s.ControlProtection = viper.GetBool("control_protection")
|
||||
|
@ -11,8 +11,8 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -35,6 +35,9 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
|
||||
|
||||
router := chi.NewRouter()
|
||||
router.Use(middleware.RequestID) // Create a request ID for each request
|
||||
if conf.Proxy {
|
||||
router.Use(middleware.RealIP)
|
||||
}
|
||||
router.Use(middleware.RequestLogger(&logformatter{logger}))
|
||||
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
||||
router.Use(middleware.Compress(5, "application/octet-stream"))
|
||||
@ -163,7 +166,13 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
err = r.ParseMultipartForm(32 << 20)
|
||||
if err != nil || r.MultipartForm == nil {
|
||||
logger.Warn().Err(err).Msg("failed to parse multipart form")
|
||||
http.Error(w, "error parsing form", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
for _, formheader := range r.MultipartForm.File["files"] {
|
||||
filePath := webSocketHandler.FileTransferPath(formheader.Filename)
|
||||
|
||||
@ -184,6 +193,11 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
|
||||
|
||||
io.Copy(f, formfile)
|
||||
}
|
||||
|
||||
err = r.MultipartForm.RemoveAll()
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to remove multipart form")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
|
@ -46,9 +46,9 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
BORADCAST_STATUS = "broadcast/status"
|
||||
BORADCAST_CREATE = "broadcast/create"
|
||||
BORADCAST_DESTROY = "broadcast/destroy"
|
||||
BROADCAST_STATUS = "broadcast/status"
|
||||
BROADCAST_CREATE = "broadcast/create"
|
||||
BROADCAST_DESTROY = "broadcast/destroy"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -2,7 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@ -31,21 +31,10 @@ func GetIP(serverUrl string) (string, error) {
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
|
||||
buf, err := ioutil.ReadAll(rsp.Body)
|
||||
buf, err := io.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(bytes.TrimSpace(buf)), nil
|
||||
}
|
||||
|
||||
func GetHttpRequestIP(r *http.Request, proxy bool) string {
|
||||
IPAddress := r.Header.Get("X-Real-Ip")
|
||||
if IPAddress == "" {
|
||||
IPAddress = r.Header.Get("X-Forwarded-For")
|
||||
}
|
||||
if IPAddress == "" || !proxy {
|
||||
IPAddress = r.RemoteAddr
|
||||
}
|
||||
return IPAddress
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ func (h *MessageHandler) adminGive(id string, session types.Session, payload *me
|
||||
ID: id,
|
||||
Target: payload.ID,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_LOCKED)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_GIVE)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -207,7 +207,7 @@ func (h *MessageHandler) adminMute(id string, session types.Session, payload *me
|
||||
Target: target.ID(),
|
||||
ID: id,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNMUTE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_MUTE)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"m1k1o/neko/internal/types/message"
|
||||
)
|
||||
|
||||
func (h *MessageHandler) boradcastCreate(session types.Session, payload *message.BroadcastCreate) error {
|
||||
func (h *MessageHandler) broadcastCreate(session types.Session, payload *message.BroadcastCreate) error {
|
||||
broadcast := h.capture.Broadcast()
|
||||
|
||||
if !session.Admin() {
|
||||
@ -44,14 +44,14 @@ func (h *MessageHandler) boradcastCreate(session types.Session, payload *message
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.boradcastStatus(nil); err != nil {
|
||||
if err := h.broadcastStatus(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
||||
func (h *MessageHandler) broadcastDestroy(session types.Session) error {
|
||||
broadcast := h.capture.Broadcast()
|
||||
|
||||
if !session.Admin() {
|
||||
@ -70,18 +70,18 @@ func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
||||
|
||||
broadcast.Stop()
|
||||
|
||||
if err := h.boradcastStatus(nil); err != nil {
|
||||
if err := h.broadcastStatus(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||
func (h *MessageHandler) broadcastStatus(session types.Session) error {
|
||||
broadcast := h.capture.Broadcast()
|
||||
|
||||
msg := message.BroadcastStatus{
|
||||
Event: event.BORADCAST_STATUS,
|
||||
Event: event.BROADCAST_STATUS,
|
||||
IsActive: broadcast.Started(),
|
||||
URL: broadcast.Url(),
|
||||
}
|
||||
@ -89,7 +89,7 @@ func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||
// if no session, broadcast change
|
||||
if session == nil {
|
||||
if err := h.sessions.AdminBroadcast(msg, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BORADCAST_STATUS)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BROADCAST_STATUS)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||
}
|
||||
|
||||
if err := session.Send(msg); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.BORADCAST_STATUS)
|
||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.BROADCAST_STATUS)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ func (h *MessageHandler) chat(id string, session types.Session, payload *message
|
||||
Content: payload.Content,
|
||||
ID: id,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_RELEASE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CHAT_MESSAGE)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -34,7 +34,7 @@ func (h *MessageHandler) chatEmote(id string, session types.Session, payload *me
|
||||
Emote: payload.Emote,
|
||||
ID: id,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_RELEASE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CHAT_EMOTE)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -115,7 +115,7 @@ func (h *MessageHandler) controlGive(id string, session types.Session, payload *
|
||||
ID: id,
|
||||
Target: payload.ID,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_LOCKED)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_GIVE)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -148,15 +148,15 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
||||
return h.screenSet(id, session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
|
||||
// Boradcast Events
|
||||
case event.BORADCAST_CREATE:
|
||||
// Broadcast Events
|
||||
case event.BROADCAST_CREATE:
|
||||
payload := &message.BroadcastCreate{}
|
||||
return errors.Wrapf(
|
||||
utils.Unmarshal(payload, raw, func() error {
|
||||
return h.boradcastCreate(session, payload)
|
||||
return h.broadcastCreate(session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
case event.BORADCAST_DESTROY:
|
||||
return errors.Wrapf(h.boradcastDestroy(session), "%s failed", header.Event)
|
||||
case event.BROADCAST_DESTROY:
|
||||
return errors.Wrapf(h.broadcastDestroy(session), "%s failed", header.Event)
|
||||
|
||||
// Admin Events
|
||||
case event.ADMIN_LOCK:
|
||||
|
@ -30,7 +30,7 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
|
||||
}
|
||||
|
||||
// send broadcast status if admin
|
||||
if err := h.boradcastStatus(session); err != nil {
|
||||
if err := h.broadcastStatus(session); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ func (h *MessageHandler) SessionConnected(id string, session types.Session) erro
|
||||
Event: event.MEMBER_CONNECTED,
|
||||
Member: session.Member(),
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_RELEASE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.MEMBER_CONNECTED)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,7 @@ func (ws *WebSocketHandler) Upgrade(w http.ResponseWriter, r *http.Request) erro
|
||||
socket := &WebSocket{
|
||||
id: id,
|
||||
ws: ws,
|
||||
address: utils.GetHttpRequestIP(r, ws.conf.Proxy),
|
||||
address: r.RemoteAddr,
|
||||
connection: connection,
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"m1k1o/neko/internal/capture"
|
||||
"m1k1o/neko/internal/config"
|
||||
@ -25,7 +26,7 @@ const Header = `&34
|
||||
/ |/ / _ \/ //_/ __ \ ) ( ')
|
||||
/ /| / __/ ,< / /_/ / ( / )
|
||||
/_/ |_/\___/_/|_|\____/ \(__)|
|
||||
&1&37 nurdism/m1k1o &33%s v%s&0
|
||||
&1&37 nurdism/m1k1o &33%s %s&0
|
||||
`
|
||||
|
||||
var (
|
||||
@ -35,13 +36,8 @@ var (
|
||||
gitCommit = "dev"
|
||||
//
|
||||
gitBranch = "dev"
|
||||
|
||||
// Major version when you make incompatible API changes,
|
||||
major = "2"
|
||||
// Minor version when you add functionality in a backwards-compatible manner, and
|
||||
minor = "8"
|
||||
// Patch version when you make backwards-compatible bug fixes.
|
||||
patch = "0"
|
||||
//
|
||||
gitTag = "dev"
|
||||
)
|
||||
|
||||
var Service *Neko
|
||||
@ -49,11 +45,9 @@ var Service *Neko
|
||||
func init() {
|
||||
Service = &Neko{
|
||||
Version: &Version{
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Patch: patch,
|
||||
GitCommit: gitCommit,
|
||||
GitBranch: gitBranch,
|
||||
GitTag: gitTag,
|
||||
BuildDate: buildDate,
|
||||
GoVersion: runtime.Version(),
|
||||
Compiler: runtime.Compiler,
|
||||
@ -69,11 +63,9 @@ func init() {
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Major string
|
||||
Minor string
|
||||
Patch string
|
||||
GitCommit string
|
||||
GitBranch string
|
||||
GitTag string
|
||||
BuildDate string
|
||||
GoVersion string
|
||||
Compiler string
|
||||
@ -81,20 +73,25 @@ type Version struct {
|
||||
}
|
||||
|
||||
func (i *Version) String() string {
|
||||
return fmt.Sprintf("%s.%s.%s %s", i.Major, i.Minor, i.Patch, i.GitCommit)
|
||||
version := i.GitTag
|
||||
if version == "" || version == "dev" {
|
||||
version = i.GitBranch
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s@%s", version, i.GitCommit)
|
||||
}
|
||||
|
||||
func (i *Version) Details() string {
|
||||
return fmt.Sprintf(
|
||||
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
|
||||
fmt.Sprintf("Version %s.%s.%s", i.Major, i.Minor, i.Patch),
|
||||
return "\n" + strings.Join([]string{
|
||||
fmt.Sprintf("Version %s", i.String()),
|
||||
fmt.Sprintf("GitCommit %s", i.GitCommit),
|
||||
fmt.Sprintf("GitBranch %s", i.GitBranch),
|
||||
fmt.Sprintf("GitTag %s", i.GitTag),
|
||||
fmt.Sprintf("BuildDate %s", i.BuildDate),
|
||||
fmt.Sprintf("GoVersion %s", i.GoVersion),
|
||||
fmt.Sprintf("Compiler %s", i.Compiler),
|
||||
fmt.Sprintf("Platform %s", i.Platform),
|
||||
)
|
||||
}, "\n") + "\n"
|
||||
}
|
||||
|
||||
type Neko struct {
|
||||
|
Loading…
x
Reference in New Issue
Block a user