From 73b5e87e95c7f137567229386565d78805e0495f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= <153805936+Cheviiot@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:02:25 +1000 Subject: [PATCH] feat(max): add MAX messenger package, sandboxed with Firejail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MAX is the Russian state-affiliated messenger; the official Qt6 client is repacked from download.max.ru with no independent security track record, so it never runs unsandboxed. /usr/bin/max is a wrapper (not a symlink) that always launches through `firejail --profile=max`; the .desktop Exec goes through the same wrapper so there's no launch path that bypasses it. The profile hides the rest of the home directory behind private ~/.config, ~/.cache and ~/.local/share/max subdirectories, drops capabilities, enables seccomp, blocks system D-Bus, and blacklists the standalone crashpad_handler crash-report uploader. Full elimination of first-party telemetry isn't claimed or possible from the sandbox layer alone — documented in LICENSE. Also wires up repo-wide bookkeeping now that all three package changes (telegram-desktop -> telegram rename, vk-messenger, max) are in: README package count/catalog rows, docs/maintenance.md naming table and directory count, and tools/package_updates.sh + tools/validate_repo.py package lists. --- README.md | 6 ++- docs/maintenance.md | 4 +- max/.stapler/update-check | 6 +++ max/LICENSE | 33 ++++++++++++ max/Staplerfile | 105 ++++++++++++++++++++++++++++++++++++++ max/max.desktop | 16 ++++++ max/max.profile | 53 +++++++++++++++++++ max/postinstall.sh | 29 +++++++++++ max/postremove.sh | 25 +++++++++ max/stapler-repo.toml | 1 + tools/package_updates.sh | 53 ++++++++++++++++++- tools/validate_repo.py | 4 +- 12 files changed, 329 insertions(+), 6 deletions(-) create mode 100755 max/.stapler/update-check create mode 100644 max/LICENSE create mode 100644 max/Staplerfile create mode 100644 max/max.desktop create mode 100644 max/max.profile create mode 100755 max/postinstall.sh create mode 100755 max/postremove.sh create mode 100644 max/stapler-repo.toml diff --git a/README.md b/README.md index 58deba3..29fd63e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Nivora предлагает открытые рецепты упаковки д официальный репозиторий Stapler и не официальные пакеты upstream-проектов. -**22 пакета** · **6 категорий** · `amd64`, `arm64` и `all` +**24 пакета** · **6 категорий** · `amd64`, `arm64` и `all` [![Проверка Nivora](https://github.com/Cheviiot/Nivora/actions/workflows/quality.yml/badge.svg)](https://github.com/Cheviiot/Nivora/actions/workflows/quality.yml) @@ -47,9 +47,11 @@ stplr info nivora/codex |:--|:--:|:--:|:--| | [Clash Verge Rev](https://github.com/clash-verge-rev/clash-verge-rev) | `2.5.2` | `amd64`, `arm64` | `stplr install nivora/clash-verge-rev` | | [Happ](https://happ.su/) | `3.3.6` | `amd64`, `arm64` | `stplr install nivora/happ` | +| [MAX](https://max.ru/) (в песочнице Firejail) | `26.24.0~75261` | `amd64` | `stplr install nivora/max` | | [NetBird](https://netbird.io/) | `0.75.0` | `amd64`, `arm64` | `stplr install nivora/netbird` | | [Tailscale](https://tailscale.com/) | `1.98.9` | `amd64`, `arm64` | `stplr install nivora/tailscale` | -| [Telegram Desktop](https://desktop.telegram.org/) | `7.0.5` | `amd64` | `stplr install nivora/telegram-desktop` | +| [Telegram](https://desktop.telegram.org/) | `7.0.5` | `amd64` | `stplr install nivora/telegram` | +| [VK Messenger](https://vk.me/app) | `6.0.57` | `amd64` | `stplr install nivora/vk-messenger` | | [Яндекс Браузер](https://browser.yandex.ru/) | `26.4.1.1110` | `amd64` | `stplr install nivora/yandex-browser-stable` | ### Удалённый доступ diff --git a/docs/maintenance.md b/docs/maintenance.md index 6eef9dd..1b6d682 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -2,7 +2,7 @@ ## Инварианты -- В репозитории ровно 20 каталогов с `Staplerfile`. +- В репозитории ровно 24 каталога с `Staplerfile`. - Каталог совпадает с `name` и командой в README. - Upstream-версия не меняется из-за патча рецепта; для этого повышается `release`. - Desktop-id, AppStream component-id, units и пути данных не меняются без отдельной миграции. @@ -20,7 +20,9 @@ Stapler сам добавляет текущее `name` в generated `Provides` | `claude` | Upstream DEB: `Package: claude-desktop`; desktop-id `com.anthropic.Claude` сохранён; старое имя пакета `claude-desktop` заменяется | | `codex` | Отображаемое имя `Codex`; команда и desktop-id `codex-app` сохранены | | `github-desktop` | Официальный upstream `desktop/desktop`; Linux-сборка без стороннего форка | +| `max` | Upstream package name `max`; версия хранится как `VERSION~BUILD` (совпадает с номером сборки в имени deb-файла, как у ALT eepm) для воспроизводимости; пакет всегда запускает бинарник через Firejail (`/etc/firejail/max.profile`), `/usr/bin/max` — не симлинк, а обёртка с `exec firejail` | | `nivora-cli` | Многоязычная оболочка Nivora для Stapler | +| `telegram` | Upstream-тарбол не даёт своего package ID; desktop-id `org.telegram.desktop` сохранён; каталог и package ID переименованы с `telegram-desktop`. В отличие от `claude`/`claude-desktop`, `replaces` не включает старое имя (это разрешено только для `claude` в `validate_repo.py`) — у кого установлен `telegram-desktop`, нужно вручную `stplr install telegram` и `stplr remove telegram-desktop` | ## Локальные проверки diff --git a/max/.stapler/update-check b/max/.stapler/update-check new file mode 100755 index 0000000..efb050a --- /dev/null +++ b/max/.stapler/update-check @@ -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")" diff --git a/max/LICENSE b/max/LICENSE new file mode 100644 index 0000000..044e869 --- /dev/null +++ b/max/LICENSE @@ -0,0 +1,33 @@ +Сведения о лицензии пакета max + +Лицензия: Custom (проприетарная). +Исходный продукт: https://max.ru/ +Официальный deb-репозиторий: https://download.max.ru/linux/deb/ + +Нюансы пакета: +- Пакет перепаковывает официальный Linux deb-артефакт MAX Messenger Client + из deb-репозитория max.ru; отдельного публичного репозитория исходников + или GitHub-релиза у продукта нет. +- Приложение собрано на Qt6 и распространяется полностью самодостаточным + бандлом (собственные библиотеки Qt в `lib64/`). +- Пакет запускает приложение только через Firejail-песочницу (профиль + `/etc/firejail/max.profile`): домашний каталог пользователя виден + приложению только через отдельные `~/.config/max`, `~/.cache/max`, + `~/.local/share/max`; capabilities сброшены, seccomp включён, + системная D-Bus недоступна. Отдельный процесс `crashpad_handler` + (сбор и отправка крэш-дампов) заблокирован явным `blacklist`, чтобы + ограничить исходящую телеметрию, не относящуюся к функциям обмена + сообщениями. +- Полная гарантия отсутствия любой сетевой телеметрии невозможна: + трафик самого мессенджера к серверам max.ru неотличим от служебного + на уровне песочницы. Firejail ограничивает доступ к системе и файлам, + а не содержимое протокола приложения. +- Для полноценной работы требуется учетная запись, привязанная к + российскому номеру телефона. +- Использование мессенджера, учетной записи и сетевых функций + регулируется условиями MAX. +- Пакет помечен как несвободный через `nonfree=1`. + +Официальные условия: +https://legal.max.ru/ps +https://legal.max.ru/pp diff --git a/max/Staplerfile b/max/Staplerfile new file mode 100644 index 0000000..1c52763 --- /dev/null +++ b/max/Staplerfile @@ -0,0 +1,105 @@ +name='max' +version='26.24.0~75261' +release=1 +summary='MAX messenger client, sandboxed with Firejail' +summary_ru='Клиент мессенджера MAX в песочнице Firejail' +group='Networking/Instant messaging' +desc='MAX is the Russian state-affiliated instant messaging client. This package always launches it inside a Firejail sandbox that hides the rest of the home directory and blocks the standalone crash-report uploader, since the app has no independent security track record and no reason to see the whole system.' +desc_ru='MAX — российский государственно-аффилированный клиент мгновенных сообщений. Пакет всегда запускает его в песочнице Firejail, которая скрывает остальной домашний каталог и блокирует отдельный процесс отправки крэш-дампов — у приложения нет независимой истории аудита безопасности и нет причин видеть всю систему.' +homepage='https://max.ru/' +maintainer='max.ru ' + +architectures=('amd64') + +license=('Custom') +nonfree=1 +nonfree_msg='MAX является проприетарным приложением. Использование мессенджера, учетной записи и сетевых функций регулируется условиями MAX.' +nonfree_url='https://legal.max.ru/ps' + +provides=() +replaces=('max') +conflicts=() + +auto_reqprov_method="dirty" +auto_req=0 +auto_prov=0 + +deps=('firejail') +deps_debian=('firejail' 'libxcb-xinerama0' 'libxcb-composite0' 'libxcb-ewmh2' 'libva-x11-2' 'libva-drm2' 'libvdpau1' 'libnotify4' 'libxcb-dri2-0' 'libopengl0' 'libxcb-cursor0' 'libxkbcommon-x11-0' 'libxcb-icccm4' 'libxcb-keysyms1' 'libxss1' 'libglib2.0-0' 'gsettings-desktop-schemas' 'ca-certificates') +deps_ubuntu=("${deps_debian[@]}") +deps_fedora=('firejail' 'libxcb' 'libva' 'libvdpau' 'libnotify' 'libglvnd-opengl' 'xcb-util-cursor' 'libxkbcommon-x11' 'xcb-util-wm' 'xcb-util-keysyms' 'libXScrnSaver' 'glib2' 'gsettings-desktop-schemas' 'ca-certificates') +deps_arch=('firejail' 'libxcb' 'libva' 'libvdpau' 'libnotify' 'libglvnd' 'xcb-util-cursor' 'libxkbcommon-x11' 'xcb-util-wm' 'xcb-util-keysyms' 'libxss' 'glib2' 'gsettings-desktop-schemas' 'ca-certificates') +deps_opensuse=('firejail' 'libxcb1' 'libva2' 'libvdpau1' 'libnotify4' 'libxcb-cursor0' 'libxkbcommon-x11-0' 'xcb-util-wm0' 'xcb-util-keysyms0' 'libXss1' 'libglib-2_0-0' 'gsettings-desktop-schemas' 'ca-certificates') +deps_altlinux=('firejail' 'libxcb' 'libxcbutil-cursor' 'libxcbutil-icccm' 'libxcbutil-keysyms' 'libXScrnSaver' 'libva' 'libvdpau' 'libnotify' 'libOpenGL' 'libxkbcommon-x11' 'glib2' 'gsettings-desktop-schemas' 'ca-certificates') + +opt_deps=() +opt_deps_debian=('desktop-file-utils' 'hicolor-icon-theme' 'pipewire') +opt_deps_ubuntu=("${opt_deps_debian[@]}") +opt_deps_fedora=('desktop-file-utils' 'hicolor-icon-theme' 'pipewire') +opt_deps_arch=('desktop-file-utils' 'hicolor-icon-theme' 'pipewire') +opt_deps_opensuse=('desktop-file-utils' 'hicolor-icon-theme' 'pipewire') +opt_deps_altlinux=('desktop-file-utils' 'icon-theme-hicolor' 'pipewire') + +build_deps=('binutils') + +sources=( + "https://download.max.ru/linux/deb/pool/main/m/max/MAX-26.24.0.75261.deb?~archive=false&~name=max.deb" + 'local:///max.desktop' + 'local:///max.profile' + 'local:///LICENSE' +) + +checksums=( + 'sha256:7151aa2559e5fe22dd4dd6cf037b5d5cd38d302f84e49d4c4be94e1c4fe1f7d5' + 'sha256:80ec5c0f4777ab00c433c25ffa70ef23d1752785e6584288d189518b8b43d613' + 'sha256:cf8c986f7f1a9841be33b43cfd8e34e6c75dac6ffea07aad07869177fc7768c7' + 'sha256:ddfcad273084c8b5690f6276fb29d8990ff4a7114db8a89b3f46255f8675e6b6' +) + +scripts=( + ['postinstall']='postinstall.sh' + ['postremove']='postremove.sh' +) + +package() { + cd "${srcdir}" + + mkdir -p "${srcdir}/extracted" + ar x max.deb + tar -xf data.tar.* -C "${srcdir}/extracted" + + install -dm755 "${pkgdir}/usr/share" + cp -a "${srcdir}/extracted/usr/share/max" "${pkgdir}/usr/share/max" + install -Dm644 "${srcdir}/extracted/usr/share/pixmaps/max.png" \ + "${pkgdir}/usr/share/pixmaps/max.png" + + for icon in "${srcdir}"/extracted/usr/share/icons/hicolor/*/apps/max.png; do + size="$(basename "$(dirname "$(dirname "$icon")")")" + install -Dm644 "$icon" \ + "${pkgdir}/usr/share/icons/hicolor/${size}/apps/max.png" + done + + install -Dm644 max.profile "${pkgdir}/etc/firejail/max.profile" + + # The desktop file and every other launch path go through this wrapper — + # MAX never runs unsandboxed regardless of how it's started. + install -Dm755 /dev/stdin "${pkgdir}/usr/bin/max" <<'EOF' +#!/bin/sh +exec firejail --profile=max -- /usr/share/max/bin/max "$@" +EOF + + install -Dm644 max.desktop "${pkgdir}/usr/share/applications/max.desktop" + + install-license "${srcdir}/LICENSE" "max/LICENSE.nivora" +} + +files() { + files-find-binary + files-find-desktop + files-find-license + files-find \ + "/etc/firejail/max.profile" \ + "/usr/share/max/**/*" \ + "/usr/share/pixmaps/max.png" \ + "/usr/share/icons/hicolor/*/apps/max.png" +} diff --git a/max/max.desktop b/max/max.desktop new file mode 100644 index 0000000..65daacf --- /dev/null +++ b/max/max.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Type=Application +Terminal=false +Name=MAX +Exec=/usr/bin/max %U +Icon=max +MimeType=x-scheme-handler/max; +Comment=MAX Messenger Client Application +Comment[ru]=Клиентское приложение мессенджера MAX +StartupWMClass=max +Categories=Chat;Network;InstantMessaging;Qt; +Keywords=max;chat;im;messaging;messenger;sms; +DBusActivatable=false +SingleMainWindow=true +X-GNOME-UsesNotifications=true +X-GNOME-SingleWindow=true diff --git a/max/max.profile b/max/max.profile new file mode 100644 index 0000000..b280355 --- /dev/null +++ b/max/max.profile @@ -0,0 +1,53 @@ +# Firejail profile for MAX Messenger (Nivora package) +# MAX is a proprietary Russian messenger; this profile keeps it away from +# the rest of the user's files and system, and blocks its standalone +# crash-report uploader. + +include max.local +include globals.local + +noblacklist ${HOME}/.config/max +noblacklist ${HOME}/.cache/max +noblacklist ${HOME}/.local/share/max + +include disable-common.inc +include disable-devel.inc +include disable-exec.inc +include disable-interpreters.inc +include disable-programs.inc +include landlock-common.inc + +mkdir ${HOME}/.config/max +mkdir ${HOME}/.cache/max +mkdir ${HOME}/.local/share/max +whitelist ${HOME}/.config/max +whitelist ${HOME}/.cache/max +whitelist ${HOME}/.local/share/max +include whitelist-common.inc +include whitelist-run-common.inc +include whitelist-usr-share-common.inc +include whitelist-var-common.inc + +caps.drop all +netfilter +nodvd +nogroups +nonewprivs +noroot +notv +nou2f +protocol unix,inet,inet6 +seccomp +restrict-namespaces + +disable-mnt +private-dev +private-tmp + +# Chromium/Qt-style standalone crash uploader: no messaging function, only +# phones home with crash dumps. Block it outright rather than trust a +# network-level filter. +blacklist /usr/share/max/bin/crashpad_handler +blacklist /usr/share/max/bin/max-service/bin/crashpad_handler + +dbus-system none diff --git a/max/postinstall.sh b/max/postinstall.sh new file mode 100755 index 0000000..a3422b2 --- /dev/null +++ b/max/postinstall.sh @@ -0,0 +1,29 @@ +#!/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 + +if ! command -v firejail >/dev/null 2>&1; then + echo "Предупреждение: firejail не найден в PATH — MAX запускается только через firejail, пакет firejail должен быть установлен." >&2 +fi diff --git a/max/postremove.sh b/max/postremove.sh new file mode 100755 index 0000000..8d4bf97 --- /dev/null +++ b/max/postremove.sh @@ -0,0 +1,25 @@ +#!/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 diff --git a/max/stapler-repo.toml b/max/stapler-repo.toml new file mode 100644 index 0000000..9c222ce --- /dev/null +++ b/max/stapler-repo.toml @@ -0,0 +1 @@ +include = "../stapler-repo.toml" diff --git a/tools/package_updates.sh b/tools/package_updates.sh index ebcd8ea..5bd505e 100755 --- a/tools/package_updates.sh +++ b/tools/package_updates.sh @@ -13,15 +13,17 @@ readonly -a PACKAGES=( fisher github-desktop happ + max netbird nivora-cli opencode parsec pineconemc tailscale - telegram-desktop + telegram ventoy vintner + vk-messenger vual yandex-browser-stable ) @@ -214,6 +216,51 @@ latest_yandex_browser() { printf '%s\n' "$version" } +latest_max() { + local filename ver build + filename="$( + curl --retry 3 --retry-delay 2 --retry-all-errors \ + --connect-timeout 30 --max-time 120 -fsSL \ + 'https://download.max.ru/linux/deb/dists/stable/main/binary-amd64/Packages.gz' | + gzip -dc | + awk ' + /^Package: max$/ { selected = 1; next } + /^Package: / { selected = 0 } + selected && /^Filename: / { print $2 } + ' | + sort -V | + tail -1 + )" + [[ -n "$filename" ]] || die 'cannot determine latest MAX version' + filename="$(basename "$filename")" + filename="${filename#MAX-}" + filename="${filename%.deb}" + ver="${filename%.*}" + build="${filename##*.}" + [[ -n "$ver" && -n "$build" ]] || die 'cannot parse latest MAX filename' + printf '%s~%s\n' "$ver" "$build" +} + +latest_vk_messenger() { + local temp_dir version + temp_dir="$(mktemp -d)" + curl --retry 3 --retry-delay 2 --retry-all-errors \ + --connect-timeout 30 --max-time 300 -fsSL \ + -o "${temp_dir}/vk-messenger.deb" \ + 'https://upload.object2.vk-apps.com/vk-me-desktop-dev-5837a06d-5f28-484a-ac22-045903cb1b1a/latest/vk-messenger.deb' + ( + cd "$temp_dir" + ar x vk-messenger.deb + tar -xOf control.tar.* ./control + ) >"${temp_dir}/control" + version="$(awk '$1 == "Version:" {print $2; exit}' "${temp_dir}/control")" + version="${version%-*}" + [[ -n "$version" ]] || die 'cannot determine latest VK Messenger version' + printf '%s\n' "$version" + find "$temp_dir" -mindepth 1 -delete + rmdir "$temp_dir" +} + latest_version() { case "$1" in adwyra) github_latest_release Cheviiot/Adwyra ;; @@ -228,15 +275,17 @@ latest_version() { github_latest_release desktop/desktop | sed 's/^release-//' ;; happ) github_latest_release Happ-proxy/happ-desktop ;; + max) latest_max ;; netbird) github_latest_release netbirdio/netbird ;; nivora-cli) current_version nivora-cli ;; opencode) github_latest_release anomalyco/opencode ;; parsec) latest_parsec ;; pineconemc) github_latest_release ElyPrismLauncher/Launcher ;; tailscale) latest_tailscale ;; - telegram-desktop) github_latest_release telegramdesktop/tdesktop ;; + telegram) github_latest_release telegramdesktop/tdesktop ;; ventoy) github_latest_release ventoy/Ventoy ;; vintner) github_latest_release Cheviiot/vintner ;; + vk-messenger) latest_vk_messenger ;; vual) github_latest_release Cheviiot/Vual ;; yandex-browser-stable) latest_yandex_browser ;; *) die "unknown package: $1" ;; diff --git a/tools/validate_repo.py b/tools/validate_repo.py index 34b778f..5b0a29e 100755 --- a/tools/validate_repo.py +++ b/tools/validate_repo.py @@ -23,15 +23,17 @@ EXPECTED_PACKAGES = ( "fisher", "github-desktop", "happ", + "max", "netbird", "nivora-cli", "opencode", "parsec", "pineconemc", "tailscale", - "telegram-desktop", + "telegram", "ventoy", "vintner", + "vk-messenger", "vual", "yandex-browser-stable", )