Files
Nivora/tools/verify_artifacts.sh

333 lines
13 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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[@]} пакетов"