mirror of
https://github.com/Cheviiot/Nivora.git
synced 2026-08-03 15:51:12 +00:00
feat: create autonomous Nivora package repository
Maintain a validated cross-distribution package catalog. Automate upstream updates, isolated builds, diagnostics, and direct publication. Build the official GitHub Desktop sources for Linux with working OAuth.
This commit is contained in:
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
package_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
repo_root="$(cd "${package_dir}/.." && pwd)"
|
||||
exec "${repo_root}/tools/package_updates.sh" check "$(basename "$package_dir")"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
new_version="${1:?new version is required}"
|
||||
package_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
staplerfile="${package_dir}/Staplerfile"
|
||||
debian_version="${new_version/-beta./.beta.}"
|
||||
|
||||
stplr-spec set-field --path "$staplerfile" version "$new_version"
|
||||
stplr-spec set-field --path "$staplerfile" release 1
|
||||
stplr-spec set-field --path "$staplerfile" debian_version "$debian_version"
|
||||
stplr-spec update-checksums --path "$staplerfile"
|
||||
@@ -0,0 +1,21 @@
|
||||
Сведения о лицензии пакета AniDesk
|
||||
|
||||
Лицензия: GPL-2.0-only.
|
||||
Исходный проект: https://github.com/theDesConnet/AniDesk
|
||||
Официальная страница релизов: https://github.com/theDesConnet/AniDesk/releases
|
||||
Сайт проекта: https://anidesk.ds1nc.ru
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный Linux deb-артефакт AniDesk из релиза theDesConnet/AniDesk.
|
||||
- AniDesk является неофициальным desktop-клиентом Anixart; сервис Anixart, учетные записи, API и сетевые функции не входят в пакет и регулируются правилами соответствующих сервисов.
|
||||
- Встроенный видеоплеер использует внешние источники и сетевые запросы приложения. Доступность отдельных источников зависит от upstream-приложения и внешних сервисов.
|
||||
- Anime4K-апскейл использует WebGPU/Vulkan. Для него нужны рабочие драйверы GPU, Vulkan loader и Mesa/проприетарный Vulkan-драйвер; без них базовый запуск приложения возможен, но ускорение видео может быть недоступно.
|
||||
- Команда `anidesk` запускает upstream-приложение без отключения GPU. Если из-за драйвера Vulkan/GLX приложение падает на старте, можно временно запустить `ANIDESK_DISABLE_GPU=1 anidesk`; этот режим отключает GPU-ускорение Chromium и не предназначен для Anime4K/WebGPU.
|
||||
- Upstream deb поставляет иконку `anidesk.png` как ICO-контейнер с неверным расширением. Пакет Nivora заменяет ее на PNG-экспорт того же изображения, чтобы desktop-меню корректно показывали иконку.
|
||||
- Upstream-приложение создает API-клиент только при старте renderer-страницы. Пакет Nivora добавляет runtime-патч: после смены endpoint API в настройках renderer автоматически перезагружается, чтобы контент сразу грузился с выбранного адреса.
|
||||
- Discord Rich Presence работает только при установленном и запущенном Discord-клиенте с доступным локальным IPC.
|
||||
- Пользовательские настройки, авторизация, кэш, история и загруженные данные создаются приложением в домашнем каталоге пользователя после запуска и не поставляются внутри пакета.
|
||||
- В `/usr/share/licenses/anidesk` дополнительно устанавливаются bundled-лицензии Electron/Chromium, которые поставляет upstream-артефакт.
|
||||
|
||||
Полный официальный текст GPL-2.0:
|
||||
https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
|
||||
@@ -0,0 +1,236 @@
|
||||
name='anidesk'
|
||||
version='0.0.1-beta.7'
|
||||
debian_version='0.0.1.beta.7'
|
||||
release=5
|
||||
summary='Unofficial Anixart desktop client'
|
||||
summary_ru='Неофициальный desktop-клиент Anixart'
|
||||
group='Video/Players'
|
||||
desc='AniDesk is an unofficial Electron desktop client for Anixart with account support, a built-in video player and Anime4K WebGPU upscaling.'
|
||||
desc_ru='AniDesk — неофициальный Electron desktop-клиент Anixart с поддержкой аккаунта, встроенным видеоплеером и Anime4K-апскейлом через WebGPU.'
|
||||
homepage='https://github.com/theDesConnet/AniDesk'
|
||||
maintainer='DesConnet, hack1exe <https://github.com/theDesConnet/AniDesk>'
|
||||
|
||||
architectures=('amd64')
|
||||
|
||||
license=('GPL-2.0-only')
|
||||
|
||||
provides=()
|
||||
replaces=('anidesk')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('libgtk-3-0' 'libnotify4' 'libnss3' 'xdg-utils' 'libatspi2.0-0' 'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libasound2' 'libvulkan1' 'mesa-vulkan-drivers')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('gtk3' 'libnotify' 'nss' 'xdg-utils' 'at-spi2-core' 'libdrm' 'mesa-libgbm' 'libxcb' 'alsa-lib' 'vulkan-loader' 'mesa-vulkan-drivers')
|
||||
deps_arch=('gtk3' 'libnotify' 'nss' 'xdg-utils' 'at-spi2-core' 'libdrm' 'mesa' 'libxcb' 'alsa-lib' 'vulkan-icd-loader' 'vulkan-radeon')
|
||||
deps_opensuse=('gtk3' 'libnotify4' 'mozilla-nss' 'xdg-utils' 'at-spi2-core' 'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libasound2' 'libvulkan1' 'Mesa-vulkan-device-select')
|
||||
deps_altlinux=('libgtk+3' 'libnotify' 'libnss' 'xdg-utils' 'libat-spi2-core' 'libdrm' 'libgbm' 'libxcb' 'libalsa' 'libvulkan1' 'mesa-dri-drivers')
|
||||
|
||||
build_deps=('binutils')
|
||||
build_deps_debian=('binutils' 'python3')
|
||||
build_deps_ubuntu=("${build_deps_debian[@]}")
|
||||
build_deps_fedora=('binutils' 'python3')
|
||||
build_deps_arch=('binutils' 'python')
|
||||
build_deps_opensuse=('binutils' 'python3')
|
||||
build_deps_altlinux=('binutils' 'python3')
|
||||
|
||||
opt_deps=()
|
||||
opt_deps_debian=('gnome-keyring' 'discord')
|
||||
opt_deps_ubuntu=("${opt_deps_debian[@]}")
|
||||
opt_deps_fedora=('gnome-keyring' 'discord')
|
||||
opt_deps_arch=('gnome-keyring' 'discord')
|
||||
opt_deps_opensuse=('gnome-keyring' 'discord')
|
||||
opt_deps_altlinux=('gnome-keyring')
|
||||
|
||||
sources=(
|
||||
"https://github.com/theDesConnet/AniDesk/releases/download/v${version}/anidesk_${debian_version}_amd64.deb?~archive=false&~name=anidesk.deb"
|
||||
"local:///anidesk.png"
|
||||
"local:///anidesk-wrapper"
|
||||
"local:///LICENSE"
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:70fc1207824757f7ddab63f9e210f31bb907ea43dee3d2827fe79c3ef00252b6'
|
||||
'sha256:6d7791424bd7db712b121f7e66206289b1d31eb2a8ea998d9aeced2c7075dfe8'
|
||||
'sha256:f58b9f720bd63ff49053f949dd3f0a738afc2aa2cee57fedbd2b73440c123721'
|
||||
'sha256:892e6b268f44a850a102cc6367fec45853fe3556840d44a6106f08487553e6c4'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/extracted"
|
||||
ar x anidesk.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/extracted"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/lib"
|
||||
cp -a "${srcdir}/extracted/usr/lib/anidesk" "${pkgdir}/usr/lib/"
|
||||
chmod 0755 "${pkgdir}/usr/lib/anidesk/chrome-sandbox"
|
||||
chmod 755 "${pkgdir}/usr/lib/anidesk/anidesk"
|
||||
|
||||
python3 - "${pkgdir}/usr/lib/anidesk/resources/app.asar" <<'PY'
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
asar_path = Path(sys.argv[1])
|
||||
data = asar_path.read_bytes()
|
||||
_, _, _, header_size = struct.unpack("<IIII", data[:16])
|
||||
header = json.loads(data[16 : 16 + header_size])
|
||||
base_offset = 16 + ((header_size + 3) & ~3)
|
||||
|
||||
|
||||
def iter_files(node, prefix=""):
|
||||
for name, entry in node.get("files", {}).items():
|
||||
path_name = f"{prefix}{name}"
|
||||
if "files" in entry:
|
||||
yield from iter_files(entry, f"{path_name}/")
|
||||
else:
|
||||
yield path_name, entry
|
||||
|
||||
|
||||
entries = []
|
||||
for path_name, entry in iter_files(header):
|
||||
if "offset" not in entry or "size" not in entry:
|
||||
continue
|
||||
offset = int(entry["offset"])
|
||||
size = int(entry["size"])
|
||||
content = data[base_offset + offset : base_offset + offset + size]
|
||||
entries.append((path_name, content))
|
||||
|
||||
patched = {}
|
||||
bundle_patch_applied = False
|
||||
source_patch_applied = False
|
||||
|
||||
for path_name, content in entries:
|
||||
if path_name == "public/build/bundle.js":
|
||||
text = content.decode()
|
||||
old = (
|
||||
'onChangeCallback:(e,t)=>{x.set(t),set(o,!0),'
|
||||
'baseSettings.restartRequired=!0}'
|
||||
)
|
||||
new = (
|
||||
'onChangeCallback:(e,t)=>{get(n)!==t&&(x.set(t),set(o,!0),'
|
||||
'baseSettings.restartRequired=!0,queueMicrotask(()=>location.reload()))}'
|
||||
)
|
||||
if old not in text:
|
||||
raise SystemExit("AniDesk ASAR patch failed: endpoint dropdown marker not found")
|
||||
patched[path_name] = text.replace(old, new, 1).encode()
|
||||
bundle_patch_applied = True
|
||||
elif path_name == "src/app/settings-pages/SettingsApp.svelte":
|
||||
text = content.decode()
|
||||
old = """ onChangeCallback={(e, v) => {
|
||||
endpointUrlRaw.set(v);
|
||||
restartRequired = true;
|
||||
baseSettings.restartRequired = true;
|
||||
}}
|
||||
"""
|
||||
new = """ onChangeCallback={(e, v) => {
|
||||
if (endpointUrl === v) {
|
||||
return;
|
||||
}
|
||||
endpointUrlRaw.set(v);
|
||||
restartRequired = true;
|
||||
baseSettings.restartRequired = true;
|
||||
queueMicrotask(() => window.location.reload());
|
||||
}}
|
||||
"""
|
||||
if old not in text:
|
||||
raise SystemExit("AniDesk ASAR patch failed: endpoint source marker not found")
|
||||
patched[path_name] = text.replace(old, new, 1).encode()
|
||||
source_patch_applied = True
|
||||
|
||||
if not bundle_patch_applied:
|
||||
raise SystemExit("AniDesk ASAR patch failed: runtime bundle was not patched")
|
||||
if not source_patch_applied:
|
||||
raise SystemExit("AniDesk ASAR patch failed: source view was not patched")
|
||||
|
||||
|
||||
def rebuild(node, prefix="", offset_state=None, payload=None):
|
||||
if offset_state is None:
|
||||
offset_state = {"offset": 0}
|
||||
if payload is None:
|
||||
payload = []
|
||||
new_node = copy.deepcopy(node)
|
||||
for name, entry in list(new_node.get("files", {}).items()):
|
||||
path_name = f"{prefix}{name}"
|
||||
if "files" in entry:
|
||||
new_node["files"][name] = rebuild(
|
||||
entry, f"{path_name}/", offset_state, payload
|
||||
)
|
||||
continue
|
||||
content = None
|
||||
for candidate_path, candidate_content in entries:
|
||||
if candidate_path == path_name:
|
||||
content = patched.get(path_name, candidate_content)
|
||||
break
|
||||
if content is None:
|
||||
continue
|
||||
entry["size"] = len(content)
|
||||
entry["offset"] = str(offset_state["offset"])
|
||||
digest = hashlib.sha256(content).hexdigest()
|
||||
entry["integrity"] = {
|
||||
"algorithm": "SHA256",
|
||||
"hash": digest,
|
||||
"blockSize": 4194304,
|
||||
"blocks": [digest],
|
||||
}
|
||||
offset_state["offset"] += len(content)
|
||||
payload.append(content)
|
||||
if prefix == "":
|
||||
return new_node, payload
|
||||
return new_node
|
||||
|
||||
|
||||
new_header, payload = rebuild(header)
|
||||
header_json = json.dumps(new_header, separators=(",", ":")).encode()
|
||||
header_padded_size = (len(header_json) + 3) & ~3
|
||||
asar_path.write_bytes(
|
||||
struct.pack("<IIII", 4, header_padded_size + 8, header_padded_size + 4, len(header_json))
|
||||
+ header_json
|
||||
+ (b"\0" * (header_padded_size - len(header_json)))
|
||||
+ b"".join(payload)
|
||||
)
|
||||
PY
|
||||
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
install -Dm755 anidesk-wrapper "${pkgdir}/usr/bin/anidesk"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/share"
|
||||
cp -a "${srcdir}/extracted/usr/share/applications" "${pkgdir}/usr/share/"
|
||||
cp -a "${srcdir}/extracted/usr/share/doc" "${pkgdir}/usr/share/"
|
||||
cp -a "${srcdir}/extracted/usr/share/pixmaps" "${pkgdir}/usr/share/"
|
||||
install -Dm644 anidesk.png "${pkgdir}/usr/share/pixmaps/anidesk.png"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/share/icons/hicolor/256x256/apps"
|
||||
install -Dm644 anidesk.png "${pkgdir}/usr/share/icons/hicolor/256x256/apps/anidesk.png"
|
||||
|
||||
install-license LICENSE "anidesk/LICENSE.nivora"
|
||||
install-license "${srcdir}/extracted/usr/lib/anidesk/LICENSES.chromium.html" \
|
||||
"anidesk/LICENSES.chromium.html"
|
||||
install-license "${srcdir}/extracted/usr/share/doc/anidesk/copyright" \
|
||||
"anidesk/LICENSE.electron"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/lib/anidesk/**/*" \
|
||||
"/usr/share/doc/anidesk/*" \
|
||||
"/usr/share/icons/hicolor/256x256/apps/anidesk.png" \
|
||||
"/usr/share/pixmaps/anidesk.png"
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
binary="${ANIDESK_BINARY:-/usr/lib/anidesk/anidesk}"
|
||||
flags=()
|
||||
|
||||
case "${ANIDESK_DISABLE_GPU:-0}" in
|
||||
1 | true | yes | on)
|
||||
flags+=(
|
||||
'--disable-gpu'
|
||||
'--disable-gpu-compositing'
|
||||
'--disable-software-rasterizer'
|
||||
)
|
||||
;;
|
||||
esac
|
||||
|
||||
exec "${binary}" "${flags[@]}" "$@"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
sandbox=/usr/lib/anidesk/chrome-sandbox
|
||||
if [[ ! -f "$sandbox" ]]; then
|
||||
echo "Ошибка: не найден Electron sandbox: ${sandbox}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -L /proc/self/ns/user ]] && command -v unshare >/dev/null 2>&1 \
|
||||
&& unshare --user true 2>/dev/null; then
|
||||
chmod 0755 "$sandbox"
|
||||
else
|
||||
chmod 4755 "$sandbox"
|
||||
fi
|
||||
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
optional_refresh "кэш иконок" gtk-update-icon-cache -f -q /usr/share/icons/hicolor
|
||||
fi
|
||||
if command -v kbuildsycoca6 >/dev/null 2>&1; then
|
||||
optional_refresh "кэш KDE" kbuildsycoca6 --noincremental
|
||||
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
|
||||
optional_refresh "кэш KDE" kbuildsycoca5 --noincremental
|
||||
fi
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
optional_refresh "кэш иконок" gtk-update-icon-cache -f -q /usr/share/icons/hicolor
|
||||
fi
|
||||
if command -v kbuildsycoca6 >/dev/null 2>&1; then
|
||||
optional_refresh "кэш KDE" kbuildsycoca6 --noincremental
|
||||
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
|
||||
optional_refresh "кэш KDE" kbuildsycoca5 --noincremental
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
include = "../stapler-repo.toml"
|
||||
Reference in New Issue
Block a user