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:
Александр
2026-07-25 00:49:32 +10:00
commit f405ae2bcf
168 changed files with 8483 additions and 0 deletions
+6
View File
@@ -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")"
+13
View File
@@ -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"
+21
View File
@@ -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
+236
View File
@@ -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"
}
+17
View File
@@ -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

+35
View File
@@ -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
+23
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
include = "../stapler-repo.toml"