mirror of
https://github.com/Cheviiot/Nivora.git
synced 2026-08-03 15:51:12 +00:00
333 lines
13 KiB
Bash
Executable File
333 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
||
set -euo pipefail
|
||
|
||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
repo_root="$(cd "${script_dir}/.." && pwd)"
|
||
cd "$repo_root"
|
||
|
||
mapfile -t all_packages < <(
|
||
for staplerfile in */Staplerfile; do
|
||
dirname "$staplerfile"
|
||
done | sort
|
||
)
|
||
|
||
packages=()
|
||
if [[ "${1:-}" == '--all' ]]; then
|
||
packages=("${all_packages[@]}")
|
||
shift
|
||
else
|
||
packages=("$@")
|
||
fi
|
||
|
||
[[ "${#packages[@]}" -gt 0 ]] || {
|
||
echo 'Использование: tools/verify_artifacts.sh {--all|package...}' >&2
|
||
exit 2
|
||
}
|
||
|
||
command -v rpm >/dev/null 2>&1 || {
|
||
echo 'Для проверки RPM требуется команда rpm' >&2
|
||
exit 2
|
||
}
|
||
command -v stplr-spec >/dev/null 2>&1 || {
|
||
echo 'Для проверки метаданных требуется stplr-spec' >&2
|
||
exit 2
|
||
}
|
||
|
||
contains_path() {
|
||
local pattern="$1"
|
||
local path
|
||
for path in "${payload[@]}"; do
|
||
# shellcheck disable=SC2053
|
||
[[ "$path" == $pattern ]] && return 0
|
||
done
|
||
return 1
|
||
}
|
||
|
||
for package in "${packages[@]}"; do
|
||
[[ -f "${package}/Staplerfile" ]] || {
|
||
echo "Неизвестный пакет: ${package}" >&2
|
||
exit 2
|
||
}
|
||
|
||
mapfile -t artifacts < <(find "$package" -maxdepth 1 -type f -name '*.rpm' -print)
|
||
[[ "${#artifacts[@]}" -eq 1 ]] || {
|
||
echo "${package}: ожидался один RPM, найдено ${#artifacts[@]}" >&2
|
||
exit 1
|
||
}
|
||
|
||
artifact="${artifacts[0]}"
|
||
package_name="$(stplr-spec get-field --path "${package}/Staplerfile" name)"
|
||
rpm_name="$(rpm -qp --queryformat '%{NAME}' "$artifact")"
|
||
[[ "$rpm_name" == "${package_name}+stplr-"* ]] || {
|
||
echo "${package}: неверное имя RPM: ${rpm_name}" >&2
|
||
exit 1
|
||
}
|
||
|
||
mapfile -t payload < <(rpm -qlp "$artifact")
|
||
contains_path "/usr/share/licenses/${package_name}/*" || {
|
||
echo "${package}: отсутствует лицензия в собственном namespace" >&2
|
||
exit 1
|
||
}
|
||
contains_path '/usr/share/licenses/LICENSE' && {
|
||
echo "${package}: обнаружен общий конфликтный путь лицензии" >&2
|
||
exit 1
|
||
}
|
||
|
||
recipe="$(<"${package}/Staplerfile")"
|
||
if [[ "$recipe" == *'files-find-binary'* ]]; then
|
||
contains_path '/usr/bin/*' || contains_path '/usr/sbin/*' || {
|
||
echo "${package}: files-find-binary не добавил исполняемые файлы" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
if [[ "$recipe" == *'files-find-desktop'* ]]; then
|
||
contains_path '/usr/share/applications/*.desktop' || {
|
||
echo "${package}: files-find-desktop не добавил desktop-файл" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
if [[ "$recipe" == *'files-find-systemd'* ]]; then
|
||
contains_path '/usr/lib/systemd/system/*' || {
|
||
echo "${package}: files-find-systemd не добавил unit-файл" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
if [[ "$package" == 'claude' ]]; then
|
||
for forbidden_path in \
|
||
/usr/bin/claude-alt \
|
||
/usr/lib/claude-alt/claude-alt-bin \
|
||
/usr/share/applications/com.anthropic.ClaudeAlt.desktop; do
|
||
contains_path "$forbidden_path" && {
|
||
echo "${package}: обнаружен компонент отдельного пакета ClaudeAlt: ${forbidden_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
fi
|
||
|
||
if [[ "$package" == 'claude-alt' ]]; then
|
||
for required_path in \
|
||
/usr/bin/claude-alt \
|
||
/usr/lib/claude-alt/claude-alt-bin \
|
||
/usr/lib/claude-alt/resources/app.asar \
|
||
/usr/share/applications/com.anthropic.ClaudeAlt.desktop \
|
||
/usr/share/icons/hicolor/512x512/apps/claude-alt.png; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует компонент ClaudeAlt: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
fi
|
||
|
||
if [[ "$package" == 'balena-etcher' ]]; then
|
||
for required_path in \
|
||
/usr/bin/balena-etcher \
|
||
/usr/lib/balena-etcher/balena-etcher \
|
||
/usr/lib/balena-etcher/balenaEtcher \
|
||
/usr/lib/balena-etcher/resources/etcher-util \
|
||
/usr/share/applications/balena-etcher.desktop \
|
||
/usr/share/pixmaps/balena-etcher.png; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует компонент balenaEtcher: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
|
||
balena_command_target="$(
|
||
rpm -qp --dump "$artifact" |
|
||
awk '$1 == "/usr/bin/balena-etcher" {print $11}'
|
||
)"
|
||
balena_alias_target="$(
|
||
rpm -qp --dump "$artifact" |
|
||
awk '$1 == "/usr/lib/balena-etcher/balenaEtcher" {print $11}'
|
||
)"
|
||
[[ "$balena_command_target" == '../lib/balena-etcher/balena-etcher' ]] || {
|
||
echo "${package}: неверная ссылка /usr/bin/balena-etcher" >&2
|
||
exit 1
|
||
}
|
||
[[ "$balena_alias_target" == 'balena-etcher' ]] || {
|
||
echo "${package}: обнаружена битая upstream-ссылка balenaEtcher" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
if [[ "$package" == 'codex' ]]; then
|
||
for required_path in \
|
||
/usr/bin/codex-app \
|
||
/usr/bin/codex-computer-use-linux \
|
||
/opt/codex-app/resources/app.asar \
|
||
/opt/codex-app/resources/codex.asar \
|
||
/opt/codex-app/resources/plugins/openai-bundled/.agents/plugins/marketplace.json \
|
||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/.codex-plugin/plugin.json \
|
||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/.mcp.json \
|
||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/assets/app-icon.png \
|
||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/bin/codex-computer-use-linux \
|
||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/bin/codex-computer-use-cosmic \
|
||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/bin/computer-use-linux-cosmic \
|
||
/usr/share/applications/codex-app.desktop \
|
||
/usr/share/icons/hicolor/512x512/apps/codex-app.png; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует upstream-компонент Codex: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
|
||
codex_command_target="$(
|
||
rpm -qp --dump "$artifact" |
|
||
awk '$1 == "/usr/bin/codex-app" {print $11}'
|
||
)"
|
||
[[ "$codex_command_target" == '/opt/codex-app/codex-app' ]] || {
|
||
echo "${package}: команда Codex запускается не напрямую: ${codex_command_target}" >&2
|
||
exit 1
|
||
}
|
||
codex_computer_use_target="$(
|
||
rpm -qp --dump "$artifact" |
|
||
awk '$1 == "/usr/bin/codex-computer-use-linux" {print $11}'
|
||
)"
|
||
[[ "$codex_computer_use_target" == '/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/bin/codex-computer-use-linux' ]] || {
|
||
echo "${package}: неверная ссылка Computer Use: ${codex_computer_use_target}" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
if [[ "$package" == 'github-desktop' ]]; then
|
||
for required_path in \
|
||
/usr/bin/github-desktop \
|
||
/opt/github-desktop/desktop \
|
||
/opt/github-desktop/resources/app \
|
||
/usr/share/applications/github-desktop.desktop \
|
||
/usr/share/icons/hicolor/scalable/apps/github-desktop.svg; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует компонент GitHub Desktop: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
|
||
github_desktop_target="$(
|
||
rpm -qp --dump "$artifact" |
|
||
awk '$1 == "/usr/bin/github-desktop" {print $11}'
|
||
)"
|
||
[[ "$github_desktop_target" == '/opt/github-desktop/desktop' ]] || {
|
||
echo "${package}: команда GitHub Desktop запускается не напрямую" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
if [[ "$package" == 'opencode' ]]; then
|
||
for required_path in \
|
||
/usr/bin/opencode-desktop \
|
||
/opt/OpenCode/ai.opencode.desktop \
|
||
/opt/OpenCode/resources/app.asar \
|
||
/usr/share/applications/opencode-desktop.desktop \
|
||
/usr/share/icons/hicolor/128x128/apps/ai.opencode.desktop.png; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует upstream-компонент OpenCode: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
|
||
opencode_command_target="$(
|
||
rpm -qp --dump "$artifact" |
|
||
awk '$1 == "/usr/bin/opencode-desktop" {print $11}'
|
||
)"
|
||
[[ "$opencode_command_target" == '/opt/OpenCode/ai.opencode.desktop' ]] || {
|
||
echo "${package}: команда OpenCode запускается не напрямую: ${opencode_command_target}" >&2
|
||
exit 1
|
||
}
|
||
contains_path '/usr/lib/opencode-desktop/*' && {
|
||
echo "${package}: обнаружен удалённый wrapper OpenCode" >&2
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
if [[ "$package" == 'ventoy' ]]; then
|
||
for required_path in \
|
||
/usr/bin/ventoy \
|
||
'/opt/ventoy/VentoyGUI.*' \
|
||
/opt/ventoy/boot/boot.img \
|
||
/opt/ventoy/tool/VentoyWorker.sh \
|
||
/opt/ventoy/ventoy/ventoy.disk.img.xz \
|
||
/usr/share/applications/ventoy.desktop \
|
||
/usr/share/icons/hicolor/128x128/apps/ventoy.png; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует компонент Ventoy: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
fi
|
||
|
||
if [[ "$package" == 'yandex-browser-stable' ]]; then
|
||
for command in cpio rpm2cpio; do
|
||
command -v "$command" >/dev/null 2>&1 || {
|
||
echo "${package}: для проверки desktop-файлов требуется ${command}" >&2
|
||
exit 2
|
||
}
|
||
done
|
||
for required_path in \
|
||
/usr/bin/yandex-browser \
|
||
/usr/bin/yandex-browser-stable \
|
||
/opt/yandex/browser/yandex-browser \
|
||
/opt/yandex/browser/yandex_browser \
|
||
/opt/yandex/browser/yandex_browser-sandbox \
|
||
/usr/share/appdata/yandex-browser.appdata.xml \
|
||
/usr/share/applications/ru.yandex.desktop.browser.desktop \
|
||
/usr/share/applications/yandex-browser.desktop \
|
||
/usr/share/icons/hicolor/256x256/apps/yandex-browser.png \
|
||
/usr/share/mime/packages/yandex-browser-yprotect.xml; do
|
||
contains_path "$required_path" || {
|
||
echo "${package}: отсутствует компонент Яндекс Браузера: ${required_path}" >&2
|
||
exit 1
|
||
}
|
||
done
|
||
contains_path '/etc/cron.daily/yandex-browser' && {
|
||
echo "${package}: обнаружена upstream cron-задача обновления" >&2
|
||
exit 1
|
||
}
|
||
contains_path '/etc/xdg/autostart/yandex-browser_user_setup.desktop' && {
|
||
echo "${package}: обнаружен нежелательный upstream autostart" >&2
|
||
exit 1
|
||
}
|
||
|
||
compatibility_desktop="$(
|
||
set +o pipefail
|
||
rpm2cpio "$artifact" |
|
||
cpio -i --quiet --to-stdout \
|
||
/usr/share/applications/yandex-browser.desktop
|
||
)"
|
||
canonical_desktop="$(
|
||
set +o pipefail
|
||
rpm2cpio "$artifact" |
|
||
cpio -i --quiet --to-stdout \
|
||
/usr/share/applications/ru.yandex.desktop.browser.desktop
|
||
)"
|
||
desktop_entry_hidden() {
|
||
awk '
|
||
$0 == "[Desktop Entry]" { in_entry = 1; next }
|
||
/^\[/ { in_entry = 0 }
|
||
in_entry && $0 == "NoDisplay=true" { found = 1 }
|
||
END { exit !found }
|
||
'
|
||
}
|
||
desktop_entry_hidden <<<"$compatibility_desktop" || {
|
||
echo "${package}: совместимый desktop-id виден в меню приложений" >&2
|
||
exit 1
|
||
}
|
||
if desktop_entry_hidden <<<"$canonical_desktop"; then
|
||
echo "${package}: canonical desktop-id ошибочно скрыт" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
while read -r path _ _ _ mode _; do
|
||
[[ "$path" == /usr/bin/* || "$path" == /usr/sbin/* ]] || continue
|
||
[[ "$mode" == 0100755 || "$mode" == 0120000 ]] || {
|
||
echo "${package}: неверные права ${mode} у ${path}" >&2
|
||
exit 1
|
||
}
|
||
done < <(rpm -qp --dump "$artifact")
|
||
|
||
echo "OK: ${package} ($(basename "$artifact"), ${#payload[@]} путей)"
|
||
done
|
||
|
||
echo "OK: payload проверен для ${#packages[@]} пакетов"
|