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.
@@ -0,0 +1,5 @@
|
||||
* text=auto eol=lf
|
||||
*.png binary
|
||||
*.svg text eol=lf
|
||||
*.sh text eol=lf
|
||||
Staplerfile text eol=lf
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Подготовить stplr-spec
|
||||
description: Устанавливает закреплённые Go, системные инструменты и stplr-spec
|
||||
|
||||
inputs:
|
||||
system-packages:
|
||||
description: Дополнительные пакеты Ubuntu через пробел
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Установить Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5
|
||||
with:
|
||||
go-version: 1.26.5
|
||||
cache: false
|
||||
|
||||
- name: Установить системные инструменты
|
||||
if: ${{ inputs.system-packages != '' }}
|
||||
shell: bash
|
||||
env:
|
||||
SYSTEM_PACKAGES: ${{ inputs.system-packages }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
read -r -a packages <<<"$SYSTEM_PACKAGES"
|
||||
sudo apt-get install -y "${packages[@]}"
|
||||
|
||||
- name: Собрать закреплённый stplr-spec
|
||||
shell: bash
|
||||
env:
|
||||
STPLR_UTILS_COMMIT: c6ddbb5e4e5637d97bb7b2587729178d715c6c52
|
||||
run: |
|
||||
git clone --filter=blob:none \
|
||||
https://altlinux.space/stapler/stplr-utils.git \
|
||||
"$RUNNER_TEMP/stplr-utils"
|
||||
git -C "$RUNNER_TEMP/stplr-utils" checkout "$STPLR_UTILS_COMMIT"
|
||||
(
|
||||
cd "$RUNNER_TEMP/stplr-utils"
|
||||
GOBIN="$RUNNER_TEMP/bin" go install ./cmd/stplr-spec
|
||||
)
|
||||
echo "$RUNNER_TEMP/bin" >>"$GITHUB_PATH"
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
@@ -0,0 +1,201 @@
|
||||
name: CI · Автономное обновление пакетов
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 03:00 Asia/Vladivostok (UTC+10)
|
||||
- cron: "0 17 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: nivora-autonomous-updates
|
||||
cancel-in-progress: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
detect:
|
||||
name: Найти обновления
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
packages: ${{ steps.updates.outputs.packages }}
|
||||
has_updates: ${{ steps.updates.outputs.has_updates }}
|
||||
github_desktop_update: ${{ steps.updates.outputs.github_desktop_update }}
|
||||
github_desktop_version: ${{ steps.updates.outputs.github_desktop_version }}
|
||||
steps:
|
||||
- name: Получить репозиторий
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Подготовить инструменты
|
||||
uses: ./.github/actions/setup-stplr-spec
|
||||
with:
|
||||
system-packages: jq
|
||||
|
||||
- name: Сформировать план обновления
|
||||
id: updates
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
packages_output="$(tools/package_updates.sh outdated)"
|
||||
mapfile -t packages <<<"$packages_output"
|
||||
if [[ -z "$packages_output" ]]; then
|
||||
packages=()
|
||||
fi
|
||||
if [[ "${#packages[@]}" -eq 0 ]]; then
|
||||
{
|
||||
echo 'packages=[]'
|
||||
echo 'has_updates=false'
|
||||
echo 'github_desktop_update=false'
|
||||
echo 'github_desktop_version='
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
packages_json="$(
|
||||
printf '%s\n' "${packages[@]}" |
|
||||
jq -Rsc 'split("\n") | map(select(length > 0))'
|
||||
)"
|
||||
github_desktop_update=false
|
||||
github_desktop_version=''
|
||||
for package in "${packages[@]}"; do
|
||||
if [[ "$package" == github-desktop ]]; then
|
||||
github_desktop_update=true
|
||||
read -r _ github_desktop_version < <(
|
||||
tools/package_updates.sh check github-desktop
|
||||
)
|
||||
fi
|
||||
done
|
||||
{
|
||||
echo "packages=${packages_json}"
|
||||
echo 'has_updates=true'
|
||||
echo "github_desktop_update=${github_desktop_update}"
|
||||
echo "github_desktop_version=${github_desktop_version}"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
|
||||
github-desktop:
|
||||
name: Подготовить GitHub Desktop
|
||||
needs: detect
|
||||
if: ${{ needs.detect.outputs.github_desktop_update == 'true' }}
|
||||
uses: ./.github/workflows/github-desktop-linux.yml
|
||||
with:
|
||||
version: ${{ needs.detect.outputs.github_desktop_version }}
|
||||
publish: true
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
update:
|
||||
name: Изолированно обновить и опубликовать
|
||||
needs:
|
||||
- detect
|
||||
- github-desktop
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
needs.detect.result == 'success' &&
|
||||
needs.detect.outputs.has_updates == 'true'
|
||||
}}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: Получить репозиторий
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Подготовить инструменты
|
||||
uses: ./.github/actions/setup-stplr-spec
|
||||
with:
|
||||
system-packages: jq rpm shellcheck
|
||||
|
||||
- name: Изолированно обновить и проверить каждый пакет
|
||||
id: isolated
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
PACKAGES_JSON: ${{ needs.detect.outputs.packages }}
|
||||
AUTONOMOUS_UPDATE_RESULTS_DIR: ${{ runner.temp }}/update-results
|
||||
run: |
|
||||
mapfile -t packages < <(jq -r '.[]' <<<"$PACKAGES_JSON")
|
||||
tools/autonomous_package_updates.sh "${packages[@]}"
|
||||
|
||||
successful_json="$(
|
||||
jq -Rsc 'split("\n") | map(select(length > 0))' \
|
||||
<"$AUTONOMOUS_UPDATE_RESULTS_DIR/successful-packages"
|
||||
)"
|
||||
failed_json="$(
|
||||
cut -f1 "$AUTONOMOUS_UPDATE_RESULTS_DIR/failed-packages" |
|
||||
jq -Rsc 'split("\n") | map(select(length > 0))'
|
||||
)"
|
||||
echo "successful=${successful_json}" >>"$GITHUB_OUTPUT"
|
||||
echo "failed=${failed_json}" >>"$GITHUB_OUTPUT"
|
||||
|
||||
- name: Сохранить журналы и diff каждого пакета
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: autonomous-update-diagnostics-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/update-results
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
- name: Применить только успешные обновления
|
||||
env:
|
||||
AUTONOMOUS_UPDATE_RESULTS_DIR: ${{ runner.temp }}/update-results
|
||||
run: |
|
||||
while IFS= read -r package; do
|
||||
[[ -n "$package" ]] || continue
|
||||
git apply \
|
||||
"$AUTONOMOUS_UPDATE_RESULTS_DIR/${package}/update.patch"
|
||||
done <"$AUTONOMOUS_UPDATE_RESULTS_DIR/successful-packages"
|
||||
tools/sync_readme_versions.py
|
||||
tools/run_checks.sh
|
||||
|
||||
- name: Отправить обновления прямо в main
|
||||
id: publish
|
||||
env:
|
||||
SUCCESSFUL_PACKAGES: ${{ steps.isolated.outputs.successful }}
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo 'Изменений после обновления нет'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
git add README.md ./*/Staplerfile
|
||||
packages="$(jq -r 'join(", ")' <<<"$SUCCESSFUL_PACKAGES")"
|
||||
git commit -m "chore: autonomously update ${packages}"
|
||||
git pull --rebase origin main
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Синхронизировать постоянные отчёты о сбоях
|
||||
if: ${{ always() && steps.isolated.outcome == 'success' }}
|
||||
env:
|
||||
AUTONOMOUS_UPDATE_RESULTS_DIR: ${{ runner.temp }}/update-results
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: tools/report_update_failures.sh
|
||||
|
||||
- name: Сообщить о несовместимых пакетах
|
||||
if: ${{ always() && steps.isolated.outputs.failed != '[]' }}
|
||||
env:
|
||||
FAILED_PACKAGES: ${{ steps.isolated.outputs.failed }}
|
||||
AUTONOMOUS_UPDATE_RESULTS_DIR: ${{ runner.temp }}/update-results
|
||||
run: |
|
||||
{
|
||||
echo '## Требуется ручная диагностика'
|
||||
echo
|
||||
echo 'Обновление остальных пакетов продолжено и опубликовано.'
|
||||
echo 'Для несовместимых пакетов сохранён artifact с логами и diff:'
|
||||
echo
|
||||
while IFS=$'\t' read -r package phase; do
|
||||
printf -- '- **%s**: сбой на фазе **%s**\n' "$package" "$phase"
|
||||
done <"$AUTONOMOUS_UPDATE_RESULTS_DIR/failed-packages"
|
||||
} >>"$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "::warning::Требуется ручная диагностика: $(jq -r 'join(\", \")' <<<"$FAILED_PACKAGES")"
|
||||
@@ -0,0 +1,47 @@
|
||||
name: CI · Ручная сборка пакетов
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
packages:
|
||||
description: "all или package ID через пробел"
|
||||
required: true
|
||||
default: all
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: nivora-clean-build
|
||||
cancel-in-progress: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Изолированная сборка
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 300
|
||||
steps:
|
||||
- name: Получить репозиторий
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Подготовить инструменты
|
||||
uses: ./.github/actions/setup-stplr-spec
|
||||
|
||||
- name: Собрать пакеты
|
||||
env:
|
||||
REQUESTED_PACKAGES: ${{ inputs.packages }}
|
||||
run: |
|
||||
if [[ "$REQUESTED_PACKAGES" == all ]]; then
|
||||
tools/clean_build.sh --all
|
||||
tools/verify_artifacts.sh --all
|
||||
NIVORA_DEB_BUILD_MODE=host tools/test_package_lifecycle.sh
|
||||
else
|
||||
read -r -a packages <<<"$REQUESTED_PACKAGES"
|
||||
tools/clean_build.sh "${packages[@]}"
|
||||
tools/verify_artifacts.sh "${packages[@]}"
|
||||
fi
|
||||
@@ -0,0 +1,166 @@
|
||||
name: CI · Сборка GitHub Desktop для Linux
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: "Версия desktop/desktop без префикса release-"
|
||||
required: true
|
||||
type: string
|
||||
publish:
|
||||
description: "Опубликовать tar.gz в GitHub Release Nivora"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Версия desktop/desktop без префикса release-"
|
||||
required: true
|
||||
default: "3.6.3"
|
||||
type: string
|
||||
publish:
|
||||
description: "Опубликовать tar.gz в GitHub Release Nivora"
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: nivora-github-desktop-linux-${{ inputs.version || '3.6.3' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Linux ${{ matrix.arch }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
arch: x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Установить Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: "24.15.0"
|
||||
|
||||
- name: Установить системные зависимости
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libsecret-1-dev pkg-config python3
|
||||
|
||||
- name: Получить официальные исходники GitHub Desktop
|
||||
env:
|
||||
VERSION: ${{ inputs.version || '3.6.3' }}
|
||||
run: |
|
||||
git clone \
|
||||
--branch "release-${VERSION}" \
|
||||
--depth 1 \
|
||||
--recurse-submodules \
|
||||
--shallow-submodules \
|
||||
https://github.com/desktop/desktop.git \
|
||||
github-desktop
|
||||
git -C github-desktop remote set-url origin \
|
||||
https://github.com/desktop/desktop.git
|
||||
|
||||
- name: Добавить поддержку OAuth callback в Linux
|
||||
working-directory: github-desktop
|
||||
run: |
|
||||
main_process='app/src/main-process/main.ts'
|
||||
perl -0pi -e \
|
||||
"s/if \\(__DEV_SECRETS__\\) \\{\\n possibleProtocols\\.add\\('x-github-desktop-dev-auth'\\)\\n\\} else \\{\\n possibleProtocols\\.add\\('x-github-desktop-auth'\\)\\n\\}/possibleProtocols.add('x-github-desktop-auth')\\nif (__DEV_SECRETS__) {\\n possibleProtocols.add('x-github-desktop-dev-auth')\\n}/" \
|
||||
"$main_process"
|
||||
perl -0pi -e \
|
||||
"s/if \\(__WIN32__ && args\\['protocol-launcher'\\] === true\\)/if ((__WIN32__ || __LINUX__) \\&\\& args['protocol-launcher'] === true)/" \
|
||||
"$main_process"
|
||||
grep -Fq "possibleProtocols.add('x-github-desktop-auth')" \
|
||||
"$main_process"
|
||||
grep -Fq "(__WIN32__ || __LINUX__)" "$main_process"
|
||||
|
||||
- name: Установить зависимости из upstream lockfile
|
||||
working-directory: github-desktop
|
||||
run: |
|
||||
node vendor/yarn-1.21.1.js install --frozen-lockfile --non-interactive
|
||||
|
||||
- name: Собрать официальный Electron-клиент
|
||||
working-directory: github-desktop
|
||||
env:
|
||||
NODE_ENV: production
|
||||
RELEASE_CHANNEL: production
|
||||
run: node vendor/yarn-1.21.1.js build:prod
|
||||
|
||||
- name: Упаковать Linux-сборку
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
VERSION: ${{ inputs.version || '3.6.3' }}
|
||||
run: |
|
||||
dist="github-desktop/dist/desktop-linux-${ARCH}"
|
||||
test -x "${dist}/desktop"
|
||||
grep -aFq 'x-github-desktop-auth' \
|
||||
"${dist}/resources/app/main.js"
|
||||
mkdir -p package/github-desktop
|
||||
cp -a "${dist}/." package/github-desktop/
|
||||
printf '%s\n' "$(git -C github-desktop rev-parse HEAD)" \
|
||||
>package/github-desktop/UPSTREAM_COMMIT
|
||||
tar \
|
||||
--sort=name \
|
||||
--mtime='UTC 2020-01-01' \
|
||||
--owner=0 \
|
||||
--group=0 \
|
||||
--numeric-owner \
|
||||
-C package \
|
||||
-cf "github-desktop-${VERSION}-linux-${ARCH}.tar" \
|
||||
github-desktop
|
||||
gzip -n -9 "github-desktop-${VERSION}-linux-${ARCH}.tar"
|
||||
|
||||
- name: Сохранить артефакт
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: github-desktop-${{ inputs.version || '3.6.3' }}-linux-${{ matrix.arch }}
|
||||
path: github-desktop-${{ inputs.version || '3.6.3' }}-linux-${{ matrix.arch }}.tar.gz
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
publish:
|
||||
name: Опубликовать Linux-артефакты
|
||||
if: ${{ inputs.publish }}
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Скачать обе архитектуры
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: github-desktop-${{ inputs.version || '3.6.3' }}-linux-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Создать контрольные суммы
|
||||
run: sha256sum github-desktop-*.tar.gz >SHA256SUMS
|
||||
|
||||
- name: Создать или обновить GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
VERSION: ${{ inputs.version || '3.6.3' }}
|
||||
run: |
|
||||
tag="github-desktop-${VERSION}-linux"
|
||||
if ! gh release view "$tag" >/dev/null 2>&1; then
|
||||
gh release create "$tag" \
|
||||
--title "GitHub Desktop ${VERSION} for Linux" \
|
||||
--notes "Linux artifacts built by Nivora CI from the official desktop/desktop release-${VERSION} source and its pinned submodules."
|
||||
fi
|
||||
gh release upload "$tag" \
|
||||
github-desktop-*.tar.gz \
|
||||
SHA256SUMS \
|
||||
--clobber
|
||||
@@ -0,0 +1,49 @@
|
||||
name: CI · Проверка репозитория
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: nivora-quality-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
name: Статика, метаданные и тесты
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- name: Получить репозиторий
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Подготовить инструменты
|
||||
uses: ./.github/actions/setup-stplr-spec
|
||||
with:
|
||||
system-packages: jq rpm shellcheck
|
||||
|
||||
- name: Выполнить проверки
|
||||
run: tools/run_checks.sh
|
||||
|
||||
- name: Сверить upstream-версии
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set +e
|
||||
tools/package_updates.sh check-all
|
||||
status=$?
|
||||
set -e
|
||||
if [[ "$status" -eq 10 ]]; then
|
||||
echo "::warning::Для одного или нескольких пакетов доступны обновления"
|
||||
elif [[ "$status" -ne 0 ]]; then
|
||||
exit "$status"
|
||||
fi
|
||||
@@ -0,0 +1,11 @@
|
||||
*.deb
|
||||
*.rpm
|
||||
*.pkg.tar.*
|
||||
*.apk
|
||||
.stplr-spec-bc-export/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
.idea/
|
||||
.vscode/
|
||||
*~
|
||||
@@ -0,0 +1,92 @@
|
||||
# История изменений
|
||||
|
||||
В файле фиксируются значимые изменения Nivora.
|
||||
|
||||
## 2026-07-24 — GitHub Desktop
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен GitHub Desktop 3.6.3 для `amd64` и `arm64`, собираемый напрямую из
|
||||
официального `desktop/desktop` и официальных submodule-репозиториев GitHub.
|
||||
- Сторонний Linux-форк `shiftkey/desktop` и готовые артефакты других авторов не
|
||||
используются.
|
||||
- Тяжёлая сборка вынесена в отдельный GitHub Actions workflow для нативных
|
||||
`x64`/`arm64` runner’ов; результаты и SHA-256 публикуются в релизе Nivora.
|
||||
|
||||
## 2026-07-24 — очистка переходной инфраструктуры
|
||||
|
||||
### Изменено
|
||||
|
||||
- Удалены одноразовый мигратор прежнего каталога, переходные package ID,
|
||||
совместимые команды и относящиеся к ним документация и тесты.
|
||||
- Транзакционная проверка теперь тестирует обновление внутри Nivora без фиктивных
|
||||
пакетов стороннего каталога.
|
||||
- Codex обновлён до `26.721.31836`; новый upstream-сбросил `release` в `1`.
|
||||
- У Яндекс Браузера canonical desktop-id совпадает с идентификатором окна и XDG
|
||||
Portal, а прежний ID скрыт и сохранён только для совместимости: GNOME больше
|
||||
не разделяет launcher и запущенное окно на два приложения.
|
||||
|
||||
## 2026-07-24 — Яндекс Браузер
|
||||
|
||||
### Добавлено
|
||||
|
||||
- В каталог добавлен Яндекс Браузер 26.4.1.1110 из официального stable-репозитория
|
||||
для `amd64`.
|
||||
- Сохранены штатные desktop-id, XDG Portal-интеграция, MIME-типы и иконки.
|
||||
- Обновления браузера переданы Stapler: upstream cron-задача и автозапуск
|
||||
системной настройки не устанавливаются.
|
||||
|
||||
## 2026-07-24 — Ventoy
|
||||
|
||||
### Добавлено
|
||||
|
||||
- В каталог добавлен Ventoy 1.1.16 с официальным нативным Linux GUI.
|
||||
- Пакет поддерживает `amd64` и `arm64`, отображается в меню приложений как
|
||||
**Ventoy** и запрашивает права на работу с накопителями через `pkexec`.
|
||||
|
||||
### Обновлено
|
||||
|
||||
- Clash Verge Rev обновлён до 2.5.2, Claude — до 1.24012.0,
|
||||
Codex — до 26.721.30844, Happ — до 3.3.6, NetBird — до 0.75.0,
|
||||
OpenCode — до 1.18.4.
|
||||
- Tray-иконки Happ переработаны на основе оригинального разорванного H-знака:
|
||||
графитовая версия для отключённого состояния и лаймовые акценты для соединения.
|
||||
- Codex и OpenCode возвращены к чистым upstream-бинарникам: удалены runtime/ASAR-патчи,
|
||||
wrapper OpenCode, лимиты памяти и GPU-fallback; сохранены только системная упаковка,
|
||||
отображаемые названия и иконки.
|
||||
- Для Codex восстановлен предусмотренный Linux Computer Use: bundled plugin и
|
||||
backend `computer-use-linux` 0.4.1 собираются из закреплённых upstream-исходников,
|
||||
а Linux-патчи взяты из `ilysenko/codex-desktop-linux` без обхода серверного
|
||||
feature gate.
|
||||
|
||||
## 2026-07-19 — ClaudeAlt и balenaEtcher
|
||||
|
||||
### Добавлено
|
||||
|
||||
- В каталог добавлен balenaEtcher 2.1.6 для безопасной записи образов на
|
||||
SD-карты и USB-накопители (`amd64`).
|
||||
|
||||
### Изменено
|
||||
|
||||
- Второй профиль Claude заменён самостоятельным приложением ClaudeAlt с отдельными
|
||||
executable/resources tree, Electron `productName`, Wayland `app_id`, X11 `WM_CLASS`,
|
||||
desktop-файлом, каталогом данных и Cowork VM socket.
|
||||
- Старые команды второго аккаунта сохранены как совместимые алиасы ClaudeAlt.
|
||||
- Существующий каталог `Claude-Account-2` автоматически используется после обновления,
|
||||
чтобы сохранить авторизацию второго аккаунта.
|
||||
- ClaudeAlt и Nivora получили новые PNG-иконки с настоящей
|
||||
прозрачностью вне скруглённых границ.
|
||||
- Иконка ClaudeAlt подключена к desktop-файлу и Electron `BrowserWindow`.
|
||||
- Штатный tray-глиф Claude окрашен в оранжевый для основного приложения и в
|
||||
бирюзовый для ClaudeAlt; большая app-иконка больше не используется в лотке.
|
||||
- Размер и визуальные отступы иконки ClaudeAlt нормализованы под
|
||||
upstream-иконку Claude.
|
||||
|
||||
## 2026-07-17 — первый релиз
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Независимый Stapler-каталог из 15 пакетов.
|
||||
- Новые документация, логотип, валидатор, тесты и GitHub Actions.
|
||||
- Изолированная сборка всех пакетов и проверка фактического содержимого RPM.
|
||||
- Транзакционные тесты установки, обновления и удаления DEB/RPM для критичных пакетов.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Участие в Nivora
|
||||
|
||||
Проект принимает исправления рецептов, автоматизации и документации.
|
||||
|
||||
## Перед изменением
|
||||
|
||||
1. Проверьте официальное название, package ID, desktop-id и AppStream component-id.
|
||||
2. Найдите официальный HTTPS-источник для каждой архитектуры.
|
||||
3. Уточните лицензию и возможность переупаковки.
|
||||
4. Сверьте зависимости на поддерживаемых дистрибутивах.
|
||||
|
||||
## Требования к пакету
|
||||
|
||||
- Имя каталога точно совпадает с `name`.
|
||||
- Версия совпадает с upstream; изменение рецепта повышает `release`.
|
||||
- Скачиваемые sources привязаны к версии и защищены SHA-256.
|
||||
- `local:///`-файлы присутствуют в каталоге, а скрипты имеют executable bit.
|
||||
- `provides`, `replaces` и `conflicts` описывают package names, а не имена бинарников или desktop-id.
|
||||
- Lifecycle-скрипты идемпотентны и не удаляют пользовательские данные.
|
||||
- `files()` описывает весь payload и не захватывает чужие пути.
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
tools/run_checks.sh
|
||||
stplr-spec verify-checksums --path package/Staplerfile
|
||||
tools/clean_build.sh package
|
||||
```
|
||||
|
||||
Для изменения package ID нужны отдельные DEB/RPM-тесты замены и проверка
|
||||
сохранности данных.
|
||||
|
||||
## Pull request
|
||||
|
||||
Один pull request должен решать одну задачу. В описании укажите:
|
||||
|
||||
- что изменилось;
|
||||
- ссылку на upstream;
|
||||
- выполненные проверки и сборки;
|
||||
- оставшиеся ограничения.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Cheviiot
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,124 @@
|
||||
<p align="center">
|
||||
<img src=".github/assets/nivora.png" width="96" height="96" alt="Nivora">
|
||||
</p>
|
||||
|
||||
# Nivora
|
||||
|
||||
**Независимый каталог Linux-приложений для Stapler.**
|
||||
|
||||
Nivora предлагает открытые рецепты упаковки десктопных приложений и
|
||||
инструментов, которых может не быть в системном репозитории. Это не
|
||||
официальный репозиторий Stapler и не официальные пакеты upstream-проектов.
|
||||
|
||||
<!-- package-count -->
|
||||
**19 пакетов** · **6 категорий** · `amd64`, `arm64` и `all`
|
||||
|
||||
[](https://github.com/Cheviiot/Nivora/actions/workflows/quality.yml)
|
||||
|
||||
## Подключение
|
||||
|
||||
Требуется [Stapler](https://stplr.dev/docs/intro/) `v0.1.1` или новее.
|
||||
|
||||
```bash
|
||||
sudo stplr repo add nivora https://github.com/Cheviiot/Nivora.git
|
||||
sudo stplr refresh
|
||||
```
|
||||
|
||||
Установка пакета:
|
||||
|
||||
```bash
|
||||
sudo stplr install nivora/codex
|
||||
```
|
||||
|
||||
Проверить описание до установки:
|
||||
|
||||
```bash
|
||||
stplr info nivora/codex
|
||||
```
|
||||
|
||||
## Каталог
|
||||
|
||||
Название ведёт на источник приложения. `all` означает, что сам пакет не
|
||||
содержит архитектурно-зависимых бинарников.
|
||||
|
||||
### Интернет, сеть и VPN
|
||||
|
||||
| Приложение | Версия | Архитектуры | Установка |
|
||||
|:--|:--:|:--:|:--|
|
||||
| [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` |
|
||||
| [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` |
|
||||
| [Яндекс Браузер](https://browser.yandex.ru/) | `26.4.1.1110` | `amd64` | `stplr install nivora/yandex-browser-stable` |
|
||||
|
||||
### Удалённый доступ
|
||||
|
||||
| Приложение | Версия | Архитектуры | Установка |
|
||||
|:--|:--:|:--:|:--|
|
||||
| [Parsec](https://parsec.app/downloads) | `150-104a` | `amd64` | `stplr install nivora/parsec` |
|
||||
|
||||
### AI и разработка
|
||||
|
||||
| Приложение | Версия | Архитектуры | Установка |
|
||||
|:--|:--:|:--:|:--|
|
||||
| [Chatbox](https://chatboxai.app/ru) | `1.20.3` | `amd64`, `arm64` | `stplr install nivora/chatbox` |
|
||||
| [Claude](https://code.claude.com/docs/en/desktop-quickstart) | `1.24012.0` | `amd64`, `arm64` | `stplr install nivora/claude-desktop` |
|
||||
| [Codex](docs/packages/codex.md) | `26.721.31836` | `amd64` | `stplr install nivora/codex` |
|
||||
| [GitHub Desktop](docs/packages/github-desktop.md) | `3.6.3` | `amd64`, `arm64` | `stplr install nivora/github-desktop` |
|
||||
| [OpenCode](https://opencode.ai/) | `1.18.4` | `amd64`, `arm64` | `stplr install nivora/opencode` |
|
||||
|
||||
### Рабочий стол
|
||||
|
||||
| Приложение | Версия | Архитектуры | Установка |
|
||||
|:--|:--:|:--:|:--|
|
||||
| [Adwyra](https://github.com/Cheviiot/Adwyra) | `0.6.1` | `all` | `stplr install nivora/adwyra` |
|
||||
| [AniDesk](https://github.com/theDesConnet/AniDesk) | `0.0.1-beta.7` | `amd64` | `stplr install nivora/anidesk` |
|
||||
|
||||
### Игры
|
||||
|
||||
| Приложение | Версия | Архитектуры | Установка |
|
||||
|:--|:--:|:--:|:--|
|
||||
| [PineconeMC](https://pineconemc.com/) | `11.0.3` | `amd64`, `arm64` | `stplr install nivora/pineconemc` |
|
||||
| [Vual](https://github.com/Cheviiot/Vual) | `0.3.1` | `all` | `stplr install nivora/vual` |
|
||||
|
||||
### Системные инструменты
|
||||
|
||||
| Приложение | Версия | Архитектуры | Установка |
|
||||
|:--|:--:|:--:|:--|
|
||||
| [balenaEtcher](https://etcher.balena.io/) | `2.1.6` | `amd64` | `stplr install nivora/balena-etcher` |
|
||||
| [Fisher](https://github.com/jorgebucaran/fisher) | `4.4.8` | `all` | `stplr install nivora/fisher` |
|
||||
| [Nivora Stapler Helper](docs/packages/nivora-stplr.md) | `0.3.0` | `all` | `stplr install nivora/nivora-stplr` |
|
||||
| [Ventoy](docs/packages/ventoy.md) | `1.1.17` | `amd64`, `arm64` | `stplr install nivora/ventoy` |
|
||||
|
||||
## Обновление
|
||||
|
||||
```bash
|
||||
sudo stplr refresh
|
||||
sudo stplr upgrade
|
||||
```
|
||||
|
||||
Рецепты сохраняют пути пользовательских конфигураций. Обычное обновление и
|
||||
удаление пакета не должно сбрасывать настройки или выполнять logout.
|
||||
|
||||
## Безопасность и доверие
|
||||
|
||||
- Каждый `Staplerfile` доступен для проверки.
|
||||
- Файлы загружаются из указанных upstream-источников.
|
||||
- SHA-256 проверяет целостность загрузки, но не делает upstream автоматически безопасным.
|
||||
- Условия проприетарных приложений определяются их разработчиками.
|
||||
- Наличие CI не обещает абсолютную безопасность или совместимость с любой системой.
|
||||
|
||||
Подробнее: [модель доверия](docs/security-model.md) и [политика безопасности](SECURITY.md).
|
||||
|
||||
## Для сопровождающих
|
||||
|
||||
```bash
|
||||
tools/run_checks.sh
|
||||
tools/package_updates.sh check-all
|
||||
tools/clean_build.sh --all
|
||||
tools/verify_artifacts.sh --all
|
||||
tools/test_package_lifecycle.sh
|
||||
```
|
||||
|
||||
Правила изменений описаны в [CONTRIBUTING.md](CONTRIBUTING.md), а порядок сопровождения — в
|
||||
[docs/maintenance.md](docs/maintenance.md).
|
||||
@@ -0,0 +1,27 @@
|
||||
# Политика безопасности
|
||||
|
||||
## Что относится к Nivora
|
||||
|
||||
Сообщайте о проблемах в:
|
||||
|
||||
- `Staplerfile`, wrappers и lifecycle-скриптах;
|
||||
- неверных source URL или checksum;
|
||||
- опасном поведении при установке, обновлении или удалении;
|
||||
- потере или неожиданной передаче пользовательских данных из-за логики пакета;
|
||||
- секретах, случайно попавших в репозиторий.
|
||||
|
||||
Уязвимости самого приложения следует передавать upstream-разработчику.
|
||||
|
||||
## Как сообщить
|
||||
|
||||
Для обычной ошибки создайте [issue](https://github.com/Cheviiot/Nivora/issues) и укажите:
|
||||
|
||||
- пакет и архитектуру;
|
||||
- дистрибутив и версию Stapler;
|
||||
- точную команду и полный текст ошибки;
|
||||
- ожидаемое и фактическое поведение.
|
||||
|
||||
Не публикуйте пароли, API-ключи, setup keys, cookies и диагностические архивы без
|
||||
предварительной проверки.
|
||||
|
||||
Подробная модель доверия: [docs/security-model.md](docs/security-model.md).
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,13 @@
|
||||
Сведения о лицензии пакета Adwyra
|
||||
|
||||
Лицензия: GPL-3.0-or-later.
|
||||
Исходный проект: https://github.com/Cheviiot/Adwyra
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет устанавливает исходный Python/GTK-проект Adwyra в системную файловую структуру.
|
||||
- Код приложения распространяется на условиях GNU GPL версии 3 или любой более поздней версии.
|
||||
- При распространении измененных версий нужно сохранять условия GPL и предоставлять соответствующий исходный код.
|
||||
- Пакет не добавляет закрытых бинарных компонентов.
|
||||
|
||||
Полный официальный текст GPL-3.0:
|
||||
https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -0,0 +1,123 @@
|
||||
name='adwyra'
|
||||
version='0.6.1'
|
||||
release=3
|
||||
summary='Minimal GNOME application launcher'
|
||||
summary_ru='Минималистичный лаунчер приложений для GNOME'
|
||||
desc='Adwyra is a compact GTK 4/libadwaita application launcher for GNOME with an application grid, search, folders, pinned apps, drag-and-drop ordering, themes, hotkeys and plugin support.'
|
||||
desc_ru='Adwyra — компактный лаунчер приложений для GNOME на GTK 4/libadwaita с сеткой приложений, поиском, папками, закреплением, drag-and-drop сортировкой, темами, горячими клавишами и плагинами.'
|
||||
group='Graphical desktop/GNOME'
|
||||
homepage='https://github.com/Cheviiot/Adwyra'
|
||||
maintainer='Cheviiot <https://github.com/Cheviiot>'
|
||||
|
||||
architectures=('all')
|
||||
|
||||
license=('GPL-3.0-or-later')
|
||||
|
||||
provides=()
|
||||
replaces=('adwyra')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
# Зависимости для запуска
|
||||
deps=(
|
||||
'python3'
|
||||
'gtk4'
|
||||
'libadwaita'
|
||||
)
|
||||
|
||||
# Скрипты установки/удаления
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
deps_debian=(
|
||||
'python3'
|
||||
'python3-gi'
|
||||
'gir1.2-gtk-4.0'
|
||||
'gir1.2-adw-1'
|
||||
)
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=(
|
||||
'python3'
|
||||
'python3-gobject'
|
||||
'gtk4'
|
||||
'libadwaita'
|
||||
)
|
||||
deps_arch=(
|
||||
'python'
|
||||
'python-gobject'
|
||||
'gtk4'
|
||||
'libadwaita'
|
||||
)
|
||||
deps_altlinux=(
|
||||
'python3'
|
||||
'python3-module-pygobject3'
|
||||
'libgtk4'
|
||||
'libadwaita'
|
||||
)
|
||||
deps_opensuse=(
|
||||
'python3'
|
||||
'python3-gobject'
|
||||
'gtk4'
|
||||
'libadwaita'
|
||||
)
|
||||
|
||||
# Источники - используем архив для предотвращения проблем с кэшем
|
||||
sources=(
|
||||
"https://github.com/Cheviiot/adwyra/archive/refs/tags/v${version}.tar.gz"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
checksums=(
|
||||
'4acb67460a6d302d5c60c369fe68e616baff2aeafb760c421cb65a54dc8df39a'
|
||||
'4acd6128d4fbff11fd374e862520cf93aacad91650de1fe16fc4e424bd1ddc14'
|
||||
)
|
||||
|
||||
# AppStream
|
||||
appstream_app_id='com.github.adwyra'
|
||||
|
||||
package() {
|
||||
# GitHub создаёт архив с именем репозитория (Adwyra)
|
||||
src="${srcdir}/Adwyra-${version}"
|
||||
|
||||
# Бинарный файл
|
||||
install -Dm755 "${src}/adwyra" "${pkgdir}/usr/bin/adwyra"
|
||||
|
||||
# Python модуль
|
||||
install -dm755 "${pkgdir}/usr/lib/adwyra"
|
||||
cp -r "${src}/src/adwyra" "${pkgdir}/usr/lib/adwyra/"
|
||||
find "${pkgdir}/usr/lib/adwyra" -depth -path '*/__pycache__*' -delete
|
||||
|
||||
# Обновляем путь в бинарнике
|
||||
sed -i 's|os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")|'\''/usr/lib/adwyra'\''|' \
|
||||
"${pkgdir}/usr/bin/adwyra"
|
||||
|
||||
# Desktop файл
|
||||
install-desktop "${src}/data/com.github.adwyra.desktop"
|
||||
|
||||
# Иконки всех размеров
|
||||
for size in 16 24 32 48 64 128 256 512; do
|
||||
install -Dm644 "${src}/data/icons/hicolor/${size}x${size}/apps/com.github.adwyra.png" \
|
||||
"${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/com.github.adwyra.png"
|
||||
done
|
||||
|
||||
# AppStream metainfo
|
||||
install -Dm644 "${src}/data/com.github.adwyra.metainfo.xml" \
|
||||
"${pkgdir}/usr/share/metainfo/com.github.adwyra.metainfo.xml"
|
||||
|
||||
# Лицензия
|
||||
install-license "${src}/LICENSE" "adwyra/LICENSE.upstream"
|
||||
install-license "${srcdir}/LICENSE" "adwyra/LICENSE.nivora"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/lib/adwyra/**/*" \
|
||||
"/usr/share/icons/hicolor/*/apps/com.github.adwyra.png" \
|
||||
"/usr/share/metainfo/com.github.adwyra.metainfo.xml"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
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
|
||||
|
||||
# Обновляем desktop базу данных
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
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
|
||||
|
||||
# Обновляем desktop базу данных
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
[inherit]
|
||||
@@ -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")"
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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[@]}" "$@"
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -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
|
||||
@@ -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"
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,144 @@
|
||||
name='balena-etcher'
|
||||
version='2.1.6'
|
||||
release=1
|
||||
summary='Safe SD card and USB drive image flasher'
|
||||
summary_ru='Безопасная запись образов на SD-карты и USB-накопители'
|
||||
group='System/Configuration/Hardware'
|
||||
desc='balenaEtcher is a graphical utility for writing operating-system images to SD cards and USB drives with validation after flashing.'
|
||||
desc_ru='balenaEtcher — графическая утилита для записи образов операционных систем на SD-карты и USB-накопители с проверкой данных после прошивки.'
|
||||
homepage='https://etcher.balena.io/'
|
||||
maintainer='Balena Ltd. <hello@balena.io>'
|
||||
|
||||
architectures=('amd64')
|
||||
|
||||
license=('Apache-2.0')
|
||||
|
||||
provides=()
|
||||
replaces=('balena-etcher')
|
||||
conflicts=()
|
||||
|
||||
# Electron и нативный helper поставляются в официальном DEB.
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=(
|
||||
'libgtk-3-0' 'libnotify4' 'libnss3' 'libxtst6' 'xdg-utils'
|
||||
'libatspi2.0-0' 'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libasound2'
|
||||
'libatk1.0-0' 'libc6' 'libcairo2' 'libcups2' 'libdbus-1-3' 'libexpat1'
|
||||
'libfontconfig1' 'libfreetype6' 'libgcc1' 'libgdk-pixbuf2.0-0 | libgdk-pixbuf-2.0-0'
|
||||
'libglib2.0-0' 'liblzma5' 'libnspr4' 'libpango1.0-0 | libpango-1.0-0'
|
||||
'libstdc++6' 'libx11-6' 'libxcomposite1' 'libxcursor1' 'libxdamage1'
|
||||
'libxext6' 'libxfixes3' 'libxi6' 'libxrandr2' 'libxrender1' 'libxss1'
|
||||
'kde-cli-tools | kde-runtime | trash-cli | libglib2.0-bin | gvfs-bin'
|
||||
'polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1'
|
||||
)
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=(
|
||||
'gtk3' 'libnotify' 'nss' 'libXtst' 'xdg-utils' 'at-spi2-core'
|
||||
'libdrm' 'mesa-libgbm' 'libxcb' 'alsa-lib' 'atk' 'glibc' 'cairo'
|
||||
'cups-libs' 'dbus-libs' 'expat' 'fontconfig' 'freetype' 'libgcc'
|
||||
'gdk-pixbuf2' 'glib2' 'xz-libs' 'nspr' 'pango' 'libstdc++' 'libX11'
|
||||
'libXcomposite' 'libXcursor' 'libXdamage' 'libXext' 'libXfixes' 'libXi'
|
||||
'libXrandr' 'libXrender' 'libXScrnSaver' 'polkit'
|
||||
)
|
||||
deps_arch=(
|
||||
'gtk3' 'libnotify' 'nss' 'libxtst' 'xdg-utils' 'at-spi2-core'
|
||||
'libdrm' 'mesa' 'libxcb' 'alsa-lib' 'atk' 'glibc' 'cairo' 'cups'
|
||||
'dbus' 'expat' 'fontconfig' 'freetype2' 'gcc-libs' 'gdk-pixbuf2'
|
||||
'glib2' 'xz' 'nspr' 'pango' 'libx11' 'libxcomposite' 'libxcursor'
|
||||
'libxdamage' 'libxext' 'libxfixes' 'libxi' 'libxrandr' 'libxrender'
|
||||
'libxss' 'polkit'
|
||||
)
|
||||
deps_opensuse=(
|
||||
'gtk3' 'libnotify4' 'mozilla-nss' 'libXtst6' 'xdg-utils' 'at-spi2-core'
|
||||
'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libasound2' 'libatk-1_0-0'
|
||||
'glibc' 'libcairo2' 'libcups2' 'libdbus-1-3' 'libexpat1'
|
||||
'fontconfig' 'libfreetype6' 'libgcc_s1' 'gdk-pixbuf-loader-rsvg'
|
||||
'glib2' 'liblzma5' 'mozilla-nspr' 'libpango-1_0-0' 'libstdc++6'
|
||||
'libX11-6' 'libXcomposite1' 'libXcursor1' 'libXdamage1' 'libXext6'
|
||||
'libXfixes3' 'libXi6' 'libXrandr2' 'libXrender1' 'libXss1' 'polkit'
|
||||
)
|
||||
deps_altlinux=(
|
||||
'libgtk+3' 'libnotify' 'libnss' 'libXtst' 'xdg-utils' 'libat-spi2-core'
|
||||
'libdrm' 'libgbm' 'libxcb' 'libalsa' 'libatk' 'glibc' 'libcairo'
|
||||
'libcups' 'libdbus' 'libexpat' 'fontconfig' 'libfreetype' 'libgcc1'
|
||||
'libgdk-pixbuf' 'glib2' 'liblzma' 'libnspr' 'libpango' 'libstdc++6'
|
||||
'libX11' 'libXcomposite' 'libXcursor' 'libXdamage' 'libXext' 'libXfixes'
|
||||
'libXi' 'libXrandr' 'libXrender' 'libXScrnSaver' 'polkit'
|
||||
)
|
||||
|
||||
opt_deps=()
|
||||
opt_deps_debian=()
|
||||
opt_deps_ubuntu=("${opt_deps_debian[@]}")
|
||||
opt_deps_fedora=('polkit-gnome | polkit-kde' 'gvfs')
|
||||
opt_deps_arch=('polkit-gnome | polkit-kde-agent' 'gvfs')
|
||||
opt_deps_opensuse=('polkit-gnome | polkit-kde-agent-6' 'glib2-tools')
|
||||
opt_deps_altlinux=(
|
||||
'polkit-gnome | polkit-kde-agent | lxqt-policykit | xfce-polkit'
|
||||
'gvfs'
|
||||
)
|
||||
|
||||
build_deps=('binutils')
|
||||
|
||||
sources=(
|
||||
"https://github.com/balena-io/etcher/releases/download/v${version}/balena-etcher_${version}_amd64.deb?~archive=false&~name=balena-etcher.deb"
|
||||
"https://raw.githubusercontent.com/balena-io/etcher/v${version}/LICENSE?~archive=false&~name=LICENSE"
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:2bdebb46c9f750a9abf11c188ff69a405b4a4fed114333d634c3b3fe59a64057'
|
||||
'sha256:39445b459f86621683f9731fb6a7d070819dc379840e8ef52f62bb1c68942291'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/extracted"
|
||||
ar x balena-etcher.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/extracted"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/lib"
|
||||
cp -a "${srcdir}/extracted/usr/lib/balena-etcher" "${pkgdir}/usr/lib/"
|
||||
chmod 0755 "${pkgdir}/usr/lib/balena-etcher/chrome-sandbox"
|
||||
chmod 0755 \
|
||||
"${pkgdir}/usr/lib/balena-etcher/balena-etcher" \
|
||||
"${pkgdir}/usr/lib/balena-etcher/resources/etcher-util"
|
||||
|
||||
# Upstream DEB содержит balenaEtcher-ссылку на каталог GitHub Actions.
|
||||
# Сохраняем совместимое имя, но направляем его на локальный executable.
|
||||
ln -sfn balena-etcher "${pkgdir}/usr/lib/balena-etcher/balenaEtcher"
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
ln -sfn ../lib/balena-etcher/balena-etcher \
|
||||
"${pkgdir}/usr/bin/balena-etcher"
|
||||
|
||||
install -Dm644 \
|
||||
"${srcdir}/extracted/usr/share/applications/balena-etcher.desktop" \
|
||||
"${pkgdir}/usr/share/applications/balena-etcher.desktop"
|
||||
install -Dm644 \
|
||||
"${srcdir}/extracted/usr/share/pixmaps/balena-etcher.png" \
|
||||
"${pkgdir}/usr/share/pixmaps/balena-etcher.png"
|
||||
|
||||
install-license LICENSE "balena-etcher/LICENSE"
|
||||
install-license \
|
||||
"${srcdir}/extracted/usr/share/doc/balena-etcher/copyright" \
|
||||
"balena-etcher/copyright"
|
||||
install-license \
|
||||
"${srcdir}/extracted/usr/lib/balena-etcher/LICENSES.chromium.html" \
|
||||
"balena-etcher/LICENSES.chromium.html"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/lib/balena-etcher/**/*" \
|
||||
"/usr/share/pixmaps/balena-etcher.png"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
sandbox=/usr/lib/balena-etcher/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 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,20 @@
|
||||
#!/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 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"
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,20 @@
|
||||
Сведения о лицензии пакета Chatbox
|
||||
|
||||
Лицензия: GPL-3.0-only.
|
||||
Исходный проект: https://github.com/chatboxai/chatbox
|
||||
Официальный сайт: https://chatboxai.app/ru
|
||||
Официальная страница загрузки: https://chatboxai.app/ru
|
||||
Условия использования сервиса: https://chatboxai.app/ru/terms
|
||||
Политика конфиденциальности: https://chatboxai.app/ru/privacy
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный Linux deb-артефакт Chatbox Desktop из релиза `chatboxai/chatbox`.
|
||||
- Chatbox Community Edition распространяется как открытый клиент под GPL-3.0; bundled-зависимости Electron/Chromium устанавливаются вместе с upstream-артефактом.
|
||||
- Последние релизы Chatbox могут выходить без Linux-артефактов. Пакет Nivora использует последний upstream-релиз, где реально опубликованы Linux deb-файлы для amd64 и arm64.
|
||||
- Chatbox может работать с Chatbox AI Service, OpenAI, Claude, Gemini, Ollama и другими внешними провайдерами. Учетные записи, API-ключи, подписки, сетевые функции и модели регулируются условиями соответствующих сервисов.
|
||||
- История чатов, настройки, кэш, авторизация и локальные базы данных создаются приложением в домашнем каталоге пользователя после запуска и не поставляются внутри пакета.
|
||||
- Пакет добавляет команды `chatbox`, `chatbox-ai` и `chatbox-desktop` для совместимого запуска приложения.
|
||||
- В `/usr/share/licenses/chatbox` дополнительно устанавливаются bundled-лицензии Electron/Chromium, если они присутствуют в upstream-артефакте.
|
||||
|
||||
Полный официальный текст GPL-3.0:
|
||||
https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -0,0 +1,115 @@
|
||||
name='chatbox'
|
||||
version='1.20.3'
|
||||
release=3
|
||||
summary='Desktop AI client for multiple LLM providers'
|
||||
summary_ru='Десктопный AI-клиент для разных LLM-провайдеров'
|
||||
group='Development/Tools'
|
||||
desc='Chatbox is a desktop AI client for Chatbox AI, OpenAI, Claude, Gemini, Ollama and other LLM providers with local chat history and file support.'
|
||||
desc_ru='Chatbox — десктопный AI-клиент для Chatbox AI, OpenAI, Claude, Gemini, Ollama и других LLM-провайдеров с локальной историей чатов и поддержкой файлов.'
|
||||
homepage='https://chatboxai.app/ru'
|
||||
maintainer='Chatbox <https://github.com/chatboxai/chatbox>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('GPL-3.0-only')
|
||||
|
||||
provides=()
|
||||
replaces=('chatbox')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('libgtk-3-0' 'libnotify4' 'libnss3' 'libxss1' 'libxtst6' 'xdg-utils' 'libatspi2.0-0' 'libuuid1' 'libsecret-1-0' 'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libasound2')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('gtk3' 'libnotify' 'nss' 'libXScrnSaver' 'libXtst' 'xdg-utils' 'at-spi2-core' 'libuuid' 'libsecret' 'libdrm' 'mesa-libgbm' 'libxcb' 'alsa-lib')
|
||||
deps_arch=('gtk3' 'libnotify' 'nss' 'libxss' 'libxtst' 'xdg-utils' 'at-spi2-core' 'libutil-linux' 'libsecret' 'libdrm' 'mesa' 'libxcb' 'alsa-lib')
|
||||
deps_opensuse=('gtk3' 'libnotify4' 'mozilla-nss' 'libXScrnSaver1' 'libXtst6' 'xdg-utils' 'at-spi2-core' 'libuuid1' 'libsecret-1-0' 'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libasound2')
|
||||
deps_altlinux=('libgtk+3' 'libnotify' 'libnss' 'libXScrnSaver' 'libXtst' 'xdg-utils' 'libat-spi2-core' 'libuuid' 'libsecret' 'libdrm' 'libgbm' 'libxcb' 'libalsa')
|
||||
|
||||
opt_deps=()
|
||||
opt_deps_debian=('libappindicator3-1' 'gnome-keyring')
|
||||
opt_deps_ubuntu=("${opt_deps_debian[@]}")
|
||||
opt_deps_fedora=('libappindicator-gtk3' 'gnome-keyring')
|
||||
opt_deps_arch=('libappindicator-gtk3' 'gnome-keyring')
|
||||
opt_deps_opensuse=('libappindicator3-1' 'gnome-keyring')
|
||||
opt_deps_altlinux=('libappindicator-gtk3' 'gnome-keyring')
|
||||
|
||||
build_deps=('binutils')
|
||||
|
||||
sources=(
|
||||
"https://github.com/chatboxai/chatbox/releases/download/v${version}/Chatbox-${version}-amd64.deb?~archive=false&~name=chatbox.deb"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://github.com/chatboxai/chatbox/releases/download/v${version}/Chatbox-${version}-arm64.deb?~archive=false&~name=chatbox.deb"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:7a5b86200c180142ee7d4ed5d56edaabba3a3b37b3b8d8c4eed32678d327e141'
|
||||
'sha256:9408c95380fe46177c49bb9ba845dc0d706e88cdd2d637b5d2b9168730713acd'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'sha256:909c8ccbb5965a63967af2428210456eb2c9fb7baf88a1a6971e3b07ead81d60'
|
||||
'sha256:9408c95380fe46177c49bb9ba845dc0d706e88cdd2d637b5d2b9168730713acd'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/extracted"
|
||||
ar x chatbox.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/extracted"
|
||||
|
||||
install -dm755 "${pkgdir}/opt"
|
||||
cp -a "${srcdir}/extracted/opt/Chatbox" "${pkgdir}/opt/"
|
||||
chmod 0755 "${pkgdir}/opt/Chatbox/chrome-sandbox"
|
||||
chmod 755 "${pkgdir}/opt/Chatbox/xyz.chatboxapp.app"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/share"
|
||||
cp -a "${srcdir}/extracted/usr/share/applications" "${pkgdir}/usr/share/"
|
||||
cp -a "${srcdir}/extracted/usr/share/icons" "${pkgdir}/usr/share/"
|
||||
|
||||
if [[ -d "${srcdir}/extracted/usr/share/doc" ]]; then
|
||||
cp -a "${srcdir}/extracted/usr/share/doc" "${pkgdir}/usr/share/"
|
||||
fi
|
||||
|
||||
sed -i 's/^Categories=.*/Categories=Development;/' \
|
||||
"${pkgdir}/usr/share/applications/xyz.chatboxapp.app.desktop"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
ln -sfn /opt/Chatbox/xyz.chatboxapp.app "${pkgdir}/usr/bin/chatbox"
|
||||
ln -sfn /opt/Chatbox/xyz.chatboxapp.app "${pkgdir}/usr/bin/chatbox-ai"
|
||||
ln -sfn /opt/Chatbox/xyz.chatboxapp.app "${pkgdir}/usr/bin/chatbox-desktop"
|
||||
|
||||
install-license LICENSE "chatbox/LICENSE.nivora"
|
||||
|
||||
if [[ -f "${srcdir}/extracted/opt/Chatbox/LICENSE.electron.txt" ]]; then
|
||||
install-license "${srcdir}/extracted/opt/Chatbox/LICENSE.electron.txt" \
|
||||
"chatbox/LICENSE.electron.txt"
|
||||
fi
|
||||
if [[ -f "${srcdir}/extracted/opt/Chatbox/LICENSES.chromium.html" ]]; then
|
||||
install-license "${srcdir}/extracted/opt/Chatbox/LICENSES.chromium.html" \
|
||||
"chatbox/LICENSES.chromium.html"
|
||||
fi
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find \
|
||||
"/opt/Chatbox/**/*" \
|
||||
"/usr/share/doc/xyz.chatboxapp.app/*" \
|
||||
"/usr/share/icons/hicolor/*/apps/xyz.chatboxapp.app.png"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
sandbox=/opt/Chatbox/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
|
||||
@@ -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"
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,14 @@
|
||||
Сведения о лицензии пакета Clash Verge Rev
|
||||
|
||||
Лицензия: GPL-3.0-only.
|
||||
Исходный проект: https://github.com/clash-verge-rev/clash-verge-rev
|
||||
Официальный сайт: https://www.clashverge.dev
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный deb-артефакт Clash Verge Rev для Linux.
|
||||
- Приложение распространяется на условиях GNU GPL версии 3.
|
||||
- GUI-клиент работает с профилями Mihomo/Clash; сами профили, подписки и внешние серверы в пакет не входят.
|
||||
- При дальнейшем распространении измененных сборок нужно соблюдать требования GPL-3.0-only.
|
||||
|
||||
Полный официальный текст GPL-3.0:
|
||||
https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -0,0 +1,91 @@
|
||||
name='clash-verge-rev'
|
||||
version=2.5.2
|
||||
release=2
|
||||
summary='Tauri GUI client for Mihomo/Clash proxy profiles'
|
||||
summary_ru='GUI-клиент на Tauri для профилей Mihomo/Clash'
|
||||
group='Networking/Other'
|
||||
desc='Clash Verge Rev is a modern cross-platform GUI client based on Tauri for a tailored proxy experience with Mihomo/Clash profiles.'
|
||||
desc_ru='Clash Verge Rev — современный кроссплатформенный GUI-клиент на Tauri для удобной работы с профилями прокси Mihomo/Clash.'
|
||||
homepage='https://www.clashverge.dev'
|
||||
maintainer='clash-verge-rev contributors <https://github.com/clash-verge-rev/clash-verge-rev>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('GPL-3.0-only')
|
||||
|
||||
provides=()
|
||||
replaces=('clash-verge-rev')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('libc6' 'libgtk-3-0' 'libwebkit2gtk-4.1-0' 'libayatana-appindicator3-1' 'libssl3 | libssl3t64' 'libcap2-bin')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('glibc' 'gtk3' 'webkit2gtk4.1' 'libayatana-appindicator' 'openssl-libs' 'libcap')
|
||||
deps_arch=('glibc' 'gtk3' 'webkit2gtk-4.1' 'libayatana-appindicator' 'openssl' 'libcap')
|
||||
deps_opensuse=('glibc' 'gtk3' 'libwebkit2gtk-4_1-0' 'libayatana-appindicator3-1' 'libopenssl3' 'libcap-progs')
|
||||
deps_altlinux=('glibc' 'libgtk+3' 'libwebkit2gtk4.1' 'libayatana-appindicator3-1' 'libssl3' 'libcap-utils')
|
||||
|
||||
build_deps=('binutils')
|
||||
|
||||
sources=(
|
||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v${version}/Clash.Verge_${version}_amd64.deb?~archive=false&~name=clash-verge.deb"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v${version}/Clash.Verge_${version}_arm64.deb?~archive=false&~name=clash-verge.deb"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:035c83ed14b16df1dd397e5d710b34bedd5d27beb6678549ceaeeadf9bc167ed'
|
||||
'sha256:5b886bd36073c4604b97926ad81ad26ea64b9172f47f014c127b6a05f80e37a9'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'sha256:598a5a852d7bf9dc40a976780ef2afc9a4e5bfe7b99533e5f956f9e2f9def72f'
|
||||
'sha256:5b886bd36073c4604b97926ad81ad26ea64b9172f47f014c127b6a05f80e37a9'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/extracted"
|
||||
ar x clash-verge.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/extracted"
|
||||
|
||||
for path in usr opt etc; do
|
||||
if [[ -d "${srcdir}/extracted/${path}" ]]; then
|
||||
mkdir -p "${pkgdir}/${path}"
|
||||
cp -a "${srcdir}/extracted/${path}/." "${pkgdir}/${path}/"
|
||||
fi
|
||||
done
|
||||
|
||||
chmod 755 \
|
||||
"${pkgdir}/usr/bin/clash-verge" \
|
||||
"${pkgdir}/usr/bin/clash-verge-service" \
|
||||
"${pkgdir}/usr/bin/clash-verge-service-install" \
|
||||
"${pkgdir}/usr/bin/clash-verge-service-uninstall" \
|
||||
"${pkgdir}/usr/bin/verge-mihomo" \
|
||||
"${pkgdir}/usr/bin/verge-mihomo-alpha"
|
||||
|
||||
install-license "${srcdir}/LICENSE" "clash-verge-rev/LICENSE.nivora"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/lib/Clash Verge/**/*" \
|
||||
"/usr/share/icons/**/*"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/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
|
||||
@@ -0,0 +1 @@
|
||||
[inherit]
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,26 @@
|
||||
Сведения о лицензии пакета Claude
|
||||
|
||||
Лицензия: Proprietary.
|
||||
Исходный продукт: https://claude.ai/download
|
||||
Документация desktop-приложения Claude: https://code.claude.com/docs/en/desktop-quickstart
|
||||
Официальный Linux-репозиторий: https://downloads.claude.ai/claude-desktop/apt/stable
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный Linux deb-артефакт `claude-desktop` из APT-репозитория Anthropic.
|
||||
- В пакет входит десктопное Electron-приложение Claude с поддержкой сценариев Claude Code.
|
||||
- Команда `claude-desktop` запускает основное desktop-приложение.
|
||||
- Для одновременной работы с двумя аккаунтами пакет добавляет самостоятельное приложение `ClaudeAlt` с командами `claude-alt` / `claude-code-alt`, отдельной app-иконкой, executable/resources tree и Electron-идентичностью `com.anthropic.ClaudeAlt`.
|
||||
- Tray-иконки основного Claude и ClaudeAlt являются перекрашенными вариантами штатного глифа Anthropic и регулируются теми же проприетарными условиями, что и исходный продукт.
|
||||
- ClaudeAlt использует отдельный каталог web-данных `${XDG_CONFIG_HOME:-~/.config}/ClaudeAlt`, чтобы не смешивать cookies, авторизацию, OAuth-кэш, IndexedDB, Local Storage и Chromium lock-файлы. При обновлении существующий `${XDG_CONFIG_HOME:-~/.config}/Claude-Account-2` продолжает использоваться для сохранения авторизации.
|
||||
- Путь профиля можно переопределить через `CLAUDE_ALT_DATA_DIR`; прежняя переменная `CLAUDE_DESKTOP_ACCOUNT2_DIR` поддерживается для совместимости.
|
||||
- Глобальная память и конфигурация Claude Code в `~/.claude` могут использоваться обоими приложениями, поскольку этот путь принадлежит Claude Code, а не Electron-профилю.
|
||||
- Облачные чаты и память Claude не объединяются пакетом: эти данные принадлежат конкретной учетной записи и хранятся на стороне Anthropic.
|
||||
- Команда `claude` намеренно не добавляется, чтобы не конфликтовать с отдельным Claude Code CLI.
|
||||
- Для полноценной работы требуется учетная запись Anthropic и подходящая подписка или доступ к сервису Claude.
|
||||
- Настройки, авторизация, история, рабочие каталоги и данные проектов создаются приложением после запуска и не поставляются внутри пакета.
|
||||
- Функции, зависящие от виртуализации или интеграции окружения, могут требовать дополнительные системные компоненты: QEMU, virtiofsd, portal backend, keyring/kwallet и аудиоподсистему.
|
||||
- Исходный продукт является проприетарным. Использование Claude, Claude Code, сетевых функций, моделей и учетной записи регулируется юридическими условиями Anthropic.
|
||||
- Внутри upstream-пакета также поставляются компоненты Electron/Chromium и virtiofsd со своими лицензиями; их тексты устанавливаются в `/usr/share/licenses/claude-desktop`.
|
||||
|
||||
Официальные условия:
|
||||
https://www.anthropic.com/legal/consumer-terms
|
||||
@@ -0,0 +1,388 @@
|
||||
name='claude-desktop'
|
||||
version=1.24012.0
|
||||
release=4
|
||||
summary='Claude desktop application'
|
||||
summary_ru='Десктопное приложение Claude'
|
||||
group='Development/Tools'
|
||||
desc='Claude is the Anthropic desktop application for Claude and Claude Code workflows with chat, project context, integrated terminal, editor and visual diff review.'
|
||||
desc_ru='Claude — десктопное приложение Anthropic для Claude и сценариев Claude Code: чат, контекст проекта, встроенный терминал, редактор и визуальный просмотр diff.'
|
||||
homepage='https://code.claude.com/docs/en/desktop-quickstart'
|
||||
maintainer='Anthropic PBC <support@anthropic.com>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('Custom')
|
||||
nonfree=1
|
||||
nonfree_msg='Claude Desktop является проприетарным приложением Anthropic. Использование Claude, Claude Code, учетной записи, подписки, сетевых функций и моделей регулируется условиями Anthropic.'
|
||||
nonfree_url='https://www.anthropic.com/legal/consumer-terms'
|
||||
|
||||
provides=()
|
||||
replaces=('claude-desktop')
|
||||
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' 'libsecret-1-0' 'libc6' 'libxtst6' 'libuuid1' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk | xdg-desktop-portal-gnome | xdg-desktop-portal-kde')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('gtk3' 'libnotify' 'nss' 'xdg-utils' 'at-spi2-core' 'libdrm' 'mesa-libgbm' 'libxcb' 'libsecret' 'glibc' 'libXtst' 'libuuid' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||
deps_arch=('gtk3' 'libnotify' 'nss' 'xdg-utils' 'at-spi2-core' 'libdrm' 'libgbm' 'libxcb' 'libsecret' 'glibc' 'libxtst' 'libutil-linux' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||
deps_opensuse=('gtk3' 'libnotify4' 'mozilla-nss' 'xdg-utils' 'at-spi2-core' 'libdrm2' 'libgbm1' 'libxcb-dri3-0' 'libsecret-1-0' 'glibc' 'libXtst6' 'libuuid1' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||
deps_altlinux=('libgtk+3' 'libnotify' 'libnss' 'xdg-utils' 'libat-spi2-core' 'libdrm' 'libgbm' 'libxcb' 'libsecret' 'glibc' 'libXtst' 'libuuid' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||
|
||||
opt_deps=()
|
||||
opt_deps_debian=('libasound2t64 | libasound2 | pulseaudio' 'libayatana-appindicator3-1 | libappindicator3-1' 'ca-certificates' 'gnome-keyring | kwalletd6 | kwalletd5' 'qemu-system-x86' 'ovmf' 'virtiofsd')
|
||||
opt_deps_ubuntu=("${opt_deps_debian[@]}")
|
||||
opt_deps_fedora=('alsa-lib' 'pulseaudio-libs' 'libappindicator-gtk3' 'ca-certificates' 'gnome-keyring' 'kwallet' 'qemu-system-x86' 'edk2-ovmf' 'virtiofsd')
|
||||
opt_deps_arch=('alsa-lib' 'pulseaudio' 'libappindicator-gtk3' 'ca-certificates' 'gnome-keyring' 'kwallet' 'qemu-system-x86' 'edk2-ovmf' 'virtiofsd')
|
||||
opt_deps_opensuse=('libasound2' 'libpulse0' 'libappindicator3-1' 'ca-certificates' 'gnome-keyring' 'kwallet6' 'qemu-x86' 'ovmf' 'virtiofsd')
|
||||
opt_deps_altlinux=('libalsa' 'libappindicator-gtk3' 'ca-certificates' 'gnome-keyring' 'qemu-system-x86' 'virtiofsd')
|
||||
|
||||
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')
|
||||
|
||||
sources=(
|
||||
"https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop/claude-desktop_${version}_amd64.deb?~archive=false&~name=claude-desktop.deb"
|
||||
"local:///claude-alt"
|
||||
"local:///claude-alt.png"
|
||||
"local:///claude-tray-orange.png"
|
||||
"local:///claude-alt-tray-turquoise.png"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop/claude-desktop_${version}_arm64.deb?~archive=false&~name=claude-desktop.deb"
|
||||
"local:///claude-alt"
|
||||
"local:///claude-alt.png"
|
||||
"local:///claude-tray-orange.png"
|
||||
"local:///claude-alt-tray-turquoise.png"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:1096a8063956f8430fdbe504bb1c5e02985f31d0f45d082e4966e5d30ea7e3ac'
|
||||
'sha256:00d9fe6549d82f564a99c6e8ec54d1380fdf8f0a20c99bb9b3cda1f689adf57d'
|
||||
'sha256:f23de36a74c3f6ddae92511fc0dc659816853bf1d28520a6e0232fe2606fd0b4'
|
||||
'sha256:be51081c0e0b2eec3489c25da38194dccf68fe7445928b32c30e1b8051eb557b'
|
||||
'sha256:21d1db80c30921346252ed405fa36cb5c48c287afdd8ebe474073389fd88e85e'
|
||||
'sha256:40efe7a2755d02c9fe13f98cae18705e2d31e5bbb24816ebbd4951c5e3a56b3f'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'sha256:37dd5dc1b0bfcb3192561acbd33cb59037806fe9b95221a3ddf38244203196f1'
|
||||
'sha256:00d9fe6549d82f564a99c6e8ec54d1380fdf8f0a20c99bb9b3cda1f689adf57d'
|
||||
'sha256:f23de36a74c3f6ddae92511fc0dc659816853bf1d28520a6e0232fe2606fd0b4'
|
||||
'sha256:be51081c0e0b2eec3489c25da38194dccf68fe7445928b32c30e1b8051eb557b'
|
||||
'sha256:21d1db80c30921346252ed405fa36cb5c48c287afdd8ebe474073389fd88e85e'
|
||||
'sha256:40efe7a2755d02c9fe13f98cae18705e2d31e5bbb24816ebbd4951c5e3a56b3f'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/extracted"
|
||||
ar x claude-desktop.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/extracted"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/lib"
|
||||
cp -a "${srcdir}/extracted/usr/lib/claude-desktop" "${pkgdir}/usr/lib/"
|
||||
chmod 0755 "${pkgdir}/usr/lib/claude-desktop/chrome-sandbox"
|
||||
chmod 755 \
|
||||
"${pkgdir}/usr/lib/claude-desktop/claude-desktop" \
|
||||
"${pkgdir}/usr/lib/claude-desktop/resources/chrome-native-host" \
|
||||
"${pkgdir}/usr/lib/claude-desktop/resources/cowork-linux-helper" \
|
||||
"${pkgdir}/usr/lib/claude-desktop/resources/virtiofsd"
|
||||
|
||||
# Сохраняем штатный tray-глиф Claude, меняя только его цвет.
|
||||
for icon_name in TrayIconLinux.png TrayIconLinux-Dark.png; do
|
||||
install -Dm644 "${srcdir}/claude-tray-orange.png" \
|
||||
"${pkgdir}/usr/lib/claude-desktop/resources/${icon_name}"
|
||||
done
|
||||
|
||||
# ClaudeAlt получает отдельные executable и app.asar. Остальные неизменяемые
|
||||
# Electron-файлы подключаются к основной копии относительными symlink-ами.
|
||||
# Это сохраняет отдельный resourcesPath, но не удваивает пакет целиком.
|
||||
install -dm755 \
|
||||
"${pkgdir}/usr/lib/claude-alt" \
|
||||
"${pkgdir}/usr/lib/claude-alt/resources"
|
||||
cp -p "${pkgdir}/usr/lib/claude-desktop/claude-desktop" \
|
||||
"${pkgdir}/usr/lib/claude-alt/claude-alt-bin"
|
||||
|
||||
for entry in "${pkgdir}/usr/lib/claude-desktop"/*; do
|
||||
entry_name="${entry##*/}"
|
||||
case "${entry_name}" in
|
||||
claude-desktop | resources) continue ;;
|
||||
esac
|
||||
ln -s "../claude-desktop/${entry_name}" \
|
||||
"${pkgdir}/usr/lib/claude-alt/${entry_name}"
|
||||
done
|
||||
|
||||
for entry in "${pkgdir}/usr/lib/claude-desktop/resources"/*; do
|
||||
entry_name="${entry##*/}"
|
||||
case "${entry_name}" in
|
||||
app.asar)
|
||||
cp -p "${entry}" "${pkgdir}/usr/lib/claude-alt/resources/app.asar"
|
||||
;;
|
||||
icon.png | TrayIconLinux.png | TrayIconLinux-Dark.png)
|
||||
# Эти ресурсы ниже заменяются иконкой ClaudeAlt.
|
||||
;;
|
||||
*)
|
||||
ln -s "../../claude-desktop/resources/${entry_name}" \
|
||||
"${pkgdir}/usr/lib/claude-alt/resources/${entry_name}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Большая иконка ClaudeAlt используется окном, а в tray устанавливается
|
||||
# отдельный бирюзовый вариант штатного глифа Claude.
|
||||
install -Dm644 "${srcdir}/claude-alt.png" \
|
||||
"${pkgdir}/usr/lib/claude-alt/resources/icon.png"
|
||||
for icon_name in TrayIconLinux.png TrayIconLinux-Dark.png; do
|
||||
install -Dm644 "${srcdir}/claude-alt-tray-turquoise.png" \
|
||||
"${pkgdir}/usr/lib/claude-alt/resources/${icon_name}"
|
||||
done
|
||||
|
||||
alt_asar="${pkgdir}/usr/lib/claude-alt/resources/app.asar"
|
||||
|
||||
python3 - "${alt_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()
|
||||
if len(data) < 16:
|
||||
raise SystemExit("ClaudeAlt app.asar is too small")
|
||||
|
||||
_, _, _, header_size = struct.unpack("<IIII", data[:16])
|
||||
header = json.loads(data[16 : 16 + header_size])
|
||||
base_offset = 16 + ((header_size + 3) & ~3)
|
||||
output = bytearray()
|
||||
patched_package_json = 0
|
||||
patched_cowork_socket = 0
|
||||
patched_user_agent = 0
|
||||
|
||||
|
||||
def integrity_for(content: bytes, block_size: int = 4194304) -> dict:
|
||||
blocks = [
|
||||
hashlib.sha256(content[index : index + block_size]).hexdigest()
|
||||
for index in range(0, len(content), block_size)
|
||||
]
|
||||
if not blocks:
|
||||
blocks = [hashlib.sha256(b"").hexdigest()]
|
||||
return {
|
||||
"algorithm": "SHA256",
|
||||
"hash": hashlib.sha256(content).hexdigest(),
|
||||
"blockSize": block_size,
|
||||
"blocks": blocks,
|
||||
}
|
||||
|
||||
|
||||
def rebuild_node(node: dict, prefix: str = "") -> dict:
|
||||
global patched_cowork_socket, patched_package_json, patched_user_agent
|
||||
|
||||
rebuilt = {}
|
||||
for name, child in node.get("files", {}).items():
|
||||
path = f"{prefix}/{name}" if prefix else name
|
||||
if "files" in child:
|
||||
rebuilt[name] = {
|
||||
**{key: value for key, value in child.items() if key != "files"},
|
||||
"files": rebuild_node(child, path),
|
||||
}
|
||||
continue
|
||||
|
||||
entry = copy.deepcopy(child)
|
||||
if entry.get("unpacked"):
|
||||
rebuilt[name] = entry
|
||||
continue
|
||||
|
||||
offset = int(entry["offset"])
|
||||
size = int(entry["size"])
|
||||
content = data[base_offset + offset : base_offset + offset + size]
|
||||
content_changed = False
|
||||
|
||||
if path == "package.json":
|
||||
package_json = json.loads(content)
|
||||
expected = {
|
||||
"productName": "Claude",
|
||||
"desktopName": "com.anthropic.Claude.desktop",
|
||||
}
|
||||
for field, expected_value in expected.items():
|
||||
if package_json.get(field) != expected_value:
|
||||
raise SystemExit(
|
||||
f"unexpected Claude package.json {field}: "
|
||||
f"{package_json.get(field)!r} != {expected_value!r}"
|
||||
)
|
||||
# Отдельный productName нужен Electron для независимого instance
|
||||
# lock. Официальный User-Agent восстанавливается ниже отдельно.
|
||||
package_json["productName"] = "ClaudeAlt"
|
||||
package_json["desktopName"] = "com.anthropic.ClaudeAlt.desktop"
|
||||
content = (json.dumps(package_json, indent=2) + "\n").encode("utf-8")
|
||||
patched_package_json += 1
|
||||
content_changed = True
|
||||
|
||||
socket_name = b"claude-cowork-vm.sock"
|
||||
socket_matches = content.count(socket_name)
|
||||
if socket_matches:
|
||||
content = content.replace(socket_name, b"claude-alt-cowork-vm.sock")
|
||||
patched_cowork_socket += socket_matches
|
||||
content_changed = True
|
||||
|
||||
user_agent_marker = (
|
||||
b'P.app.isPackaged||P.app.setName("Claude");RRn();'
|
||||
)
|
||||
user_agent_matches = content.count(user_agent_marker)
|
||||
if user_agent_matches:
|
||||
content = content.replace(
|
||||
user_agent_marker,
|
||||
b'P.app.isPackaged||P.app.setName("Claude");'
|
||||
b'P.app.userAgentFallback=P.app.userAgentFallback'
|
||||
b'.replaceAll("ClaudeAlt","Claude");RRn();',
|
||||
)
|
||||
patched_user_agent += user_agent_matches
|
||||
content_changed = True
|
||||
|
||||
entry["offset"] = str(len(output))
|
||||
if content_changed:
|
||||
entry["size"] = len(content)
|
||||
entry["integrity"] = integrity_for(
|
||||
content,
|
||||
entry.get("integrity", {}).get("blockSize", 4194304),
|
||||
)
|
||||
output.extend(content)
|
||||
rebuilt[name] = entry
|
||||
|
||||
return rebuilt
|
||||
|
||||
|
||||
rebuilt_header = {"files": rebuild_node(header)}
|
||||
if patched_package_json != 1:
|
||||
raise SystemExit(
|
||||
f"expected one ClaudeAlt package.json, got {patched_package_json}"
|
||||
)
|
||||
if patched_cowork_socket != 1:
|
||||
raise SystemExit(
|
||||
f"expected one ClaudeAlt Cowork socket, got {patched_cowork_socket}"
|
||||
)
|
||||
if patched_user_agent != 1:
|
||||
raise SystemExit(
|
||||
f"expected one ClaudeAlt User-Agent patch, got {patched_user_agent}"
|
||||
)
|
||||
|
||||
header_bytes = json.dumps(rebuilt_header, separators=(",", ":")).encode("utf-8")
|
||||
header_padded_size = (len(header_bytes) + 3) & ~3
|
||||
patched_data = (
|
||||
struct.pack(
|
||||
"<IIII",
|
||||
4,
|
||||
header_padded_size + 8,
|
||||
header_padded_size + 4,
|
||||
len(header_bytes),
|
||||
)
|
||||
+ header_bytes
|
||||
+ (b"\0" * (header_padded_size - len(header_bytes)))
|
||||
+ bytes(output)
|
||||
)
|
||||
if patched_data.count(b"com.anthropic.ClaudeAlt.desktop") != 1:
|
||||
raise SystemExit("ClaudeAlt desktopName was not written exactly once")
|
||||
if patched_data.count(b'"productName": "ClaudeAlt"') != 1:
|
||||
raise SystemExit("ClaudeAlt productName was not written exactly once")
|
||||
if patched_data.count(b'.replaceAll("ClaudeAlt","Claude")') != 1:
|
||||
raise SystemExit("ClaudeAlt official User-Agent patch is missing")
|
||||
if patched_data.count(b"claude-alt-cowork-vm.sock") != 1:
|
||||
raise SystemExit("ClaudeAlt Cowork socket was not written exactly once")
|
||||
asar_path.write_bytes(patched_data)
|
||||
PY
|
||||
|
||||
chmod 755 "${pkgdir}/usr/lib/claude-alt/claude-alt-bin"
|
||||
install -Dm755 "${srcdir}/claude-alt" \
|
||||
"${pkgdir}/usr/lib/claude-alt/claude-alt"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/share"
|
||||
cp -a "${srcdir}/extracted/usr/share/applications" "${pkgdir}/usr/share/"
|
||||
cp -a "${srcdir}/extracted/usr/share/icons" "${pkgdir}/usr/share/"
|
||||
|
||||
desktop_file="$(find "${pkgdir}/usr/share/applications" -maxdepth 1 -type f -name "*.desktop" | head -1)"
|
||||
[[ -n "${desktop_file}" ]] || {
|
||||
echo "Claude desktop file not found" >&2
|
||||
return 1
|
||||
}
|
||||
sed -i \
|
||||
-e '0,/^Name=/{s/^Name=.*/Name=Claude\nName[ru]=Claude/}' \
|
||||
-e 's/^Categories=.*/Categories=Development;/' \
|
||||
"${desktop_file}"
|
||||
|
||||
install -Dm644 "${srcdir}/claude-alt.png" \
|
||||
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/claude-alt.png"
|
||||
|
||||
install -Dm644 /dev/stdin "${pkgdir}/usr/share/applications/com.anthropic.ClaudeAlt.desktop" <<'EOF'
|
||||
[Desktop Entry]
|
||||
Name=ClaudeAlt
|
||||
Name[ru]=ClaudeAlt
|
||||
Comment=Independent secondary Claude desktop application
|
||||
Comment[ru]=Независимое второе приложение Claude
|
||||
GenericName=AI Assistant
|
||||
Keywords=AI;Chat;Assistant;Claude;Code;LLM;
|
||||
Exec=claude-alt %U
|
||||
Icon=claude-alt
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
StartupWMClass=com.anthropic.ClaudeAlt
|
||||
SingleMainWindow=true
|
||||
Categories=Development;
|
||||
Actions=NewChat;NewCode;
|
||||
|
||||
[Desktop Action NewChat]
|
||||
Name=New chat
|
||||
Name[ru]=Новый чат
|
||||
Exec=claude-alt claude://claude.ai/new
|
||||
|
||||
[Desktop Action NewCode]
|
||||
Name=New Claude Code session
|
||||
Name[ru]=Новая сессия Claude Code
|
||||
Exec=claude-alt claude://code/new
|
||||
EOF
|
||||
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
ln -sfn /usr/lib/claude-desktop/claude-desktop "${pkgdir}/usr/bin/claude-desktop"
|
||||
ln -sfn /usr/lib/claude-alt/claude-alt "${pkgdir}/usr/bin/claude-alt"
|
||||
ln -sfn /usr/lib/claude-alt/claude-alt "${pkgdir}/usr/bin/claude-code-alt"
|
||||
ln -sfn /usr/lib/claude-alt/claude-alt "${pkgdir}/usr/bin/claude-desktop-account2"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/share/licenses/claude-desktop"
|
||||
install-license "${srcdir}/LICENSE" "claude-desktop/LICENSE.nivora"
|
||||
install -Dm644 "${srcdir}/extracted/usr/share/doc/claude-desktop/copyright" \
|
||||
"${pkgdir}/usr/share/licenses/claude-desktop/copyright"
|
||||
install -Dm644 "${srcdir}/extracted/usr/lib/claude-desktop/LICENSES.chromium.html" \
|
||||
"${pkgdir}/usr/share/licenses/claude-desktop/LICENSES.chromium.html"
|
||||
install -Dm644 "${srcdir}/extracted/usr/lib/claude-desktop/resources/virtiofsd.LICENSE-APACHE" \
|
||||
"${pkgdir}/usr/share/licenses/claude-desktop/virtiofsd.LICENSE-APACHE"
|
||||
install -Dm644 "${srcdir}/extracted/usr/lib/claude-desktop/resources/virtiofsd.LICENSE-BSD-3-Clause" \
|
||||
"${pkgdir}/usr/share/licenses/claude-desktop/virtiofsd.LICENSE-BSD-3-Clause"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/lib/claude-desktop/**/*" \
|
||||
"/usr/lib/claude-alt/**/*" \
|
||||
"/usr/share/icons/hicolor/*/apps/claude-desktop.png" \
|
||||
"/usr/share/icons/hicolor/*/apps/claude-alt.png"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ -z "${HOME:-}" ] && [ -z "${XDG_CONFIG_HOME:-}" ]; then
|
||||
echo "claude-alt: HOME or XDG_CONFIG_HOME is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
config_home="${XDG_CONFIG_HOME:-${HOME}/.config}"
|
||||
default_data_dir="${config_home}/ClaudeAlt"
|
||||
legacy_data_dir="${CLAUDE_DESKTOP_ACCOUNT2_DIR:-${config_home}/Claude-Account-2}"
|
||||
claude_alt_executable="${CLAUDE_ALT_EXECUTABLE:-/usr/lib/claude-alt/claude-alt-bin}"
|
||||
|
||||
if [ -n "${CLAUDE_ALT_DATA_DIR:-}" ]; then
|
||||
data_dir="${CLAUDE_ALT_DATA_DIR}"
|
||||
elif [ -d "${default_data_dir}" ]; then
|
||||
data_dir="${default_data_dir}"
|
||||
elif [ -d "${legacy_data_dir}" ]; then
|
||||
# Сохраняем авторизацию прежнего второго профиля после обновления пакета.
|
||||
data_dir="${legacy_data_dir}"
|
||||
else
|
||||
data_dir="${default_data_dir}"
|
||||
fi
|
||||
|
||||
if [ ! -x "${claude_alt_executable}" ]; then
|
||||
echo "claude-alt: executable not found: ${claude_alt_executable}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${data_dir}"
|
||||
|
||||
# Electron использует это имя для portal-интеграций. Основной Wayland app_id
|
||||
# задаётся отдельным desktopName внутри ClaudeAlt app.asar.
|
||||
CHROME_DESKTOP='com.anthropic.ClaudeAlt.desktop'
|
||||
export CHROME_DESKTOP
|
||||
|
||||
exec "${claude_alt_executable}" \
|
||||
--user-data-dir="${data_dir}" \
|
||||
--class=com.anthropic.ClaudeAlt \
|
||||
"$@"
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
sandbox=/usr/lib/claude-desktop/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
|
||||
@@ -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"
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,21 @@
|
||||
Сведения о лицензии пакета codex-app
|
||||
|
||||
Лицензия: Custom.
|
||||
Источник перепаковки: https://github.com/Boria138/codex-app-linux
|
||||
Linux-адаптация: https://github.com/ilysenko/codex-desktop-linux
|
||||
Backend Computer Use: https://github.com/agent-sh/computer-use-linux
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет устанавливает неофициальную Linux-перепаковку проприетарного настольного приложения.
|
||||
- Исходный репозиторий перепаковки не публикует единую корневую лицензию для всего устанавливаемого артефакта.
|
||||
- Внутри архива исходного проекта присутствуют отдельные лицензии Electron, Chromium и других компонентов; при сборке они устанавливаются в каталог лицензий пакета.
|
||||
- Linux-патчи Computer Use из codex-desktop-linux и backend computer-use-linux
|
||||
распространяются по лицензии MIT; их исходные файлы LICENSE устанавливаются
|
||||
рядом с остальными лицензиями пакета.
|
||||
- Использование приложения и сетевых функций регулируется условиями исходного сервиса.
|
||||
- Пакет помечен как несвободный через `nonfree=1`.
|
||||
|
||||
Справочные ссылки:
|
||||
- https://github.com/Boria138/codex-app-linux
|
||||
- https://github.com/ilysenko/codex-desktop-linux
|
||||
- https://github.com/agent-sh/computer-use-linux
|
||||
@@ -0,0 +1,176 @@
|
||||
name='codex'
|
||||
version=26.721.31836
|
||||
release=1
|
||||
summary='Unofficial Linux repack of the Codex desktop app'
|
||||
summary_ru='Неофициальная Linux-перепаковка десктопного приложения Codex'
|
||||
group='Development/Tools'
|
||||
desc='codex-app-linux repacks the Codex desktop application into Linux Electron artifacts and bundles a Linux codex CLI binary for local workflows.'
|
||||
desc_ru='codex-app-linux перепаковывает десктопное приложение Codex в Linux-артефакты Electron и включает Linux-бинарник codex CLI для локальных сценариев.'
|
||||
homepage='https://github.com/Boria138/codex-app-linux'
|
||||
maintainer='Boria138 <https://github.com/Boria138>'
|
||||
|
||||
architectures=('amd64')
|
||||
|
||||
license=('Custom')
|
||||
nonfree=1
|
||||
nonfree_msg='codex-app устанавливает неофициальную Linux-перепаковку проприетарного настольного приложения. Использование приложения и сетевых функций регулируется условиями исходного сервиса.'
|
||||
nonfree_url='https://github.com/Boria138/codex-app-linux'
|
||||
|
||||
provides=()
|
||||
replaces=('codex')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('ca-certificates' 'libc6' 'libasound2' 'libatk-bridge2.0-0' 'libatk1.0-0' 'libatspi2.0-0' 'libcairo2' 'libcups2' 'libcurl3-gnutls | libcurl4' 'libdbus-1-3' 'libexpat1' 'libgbm1' 'libglib2.0-0' 'libgtk-3-0' 'libnspr4' 'libnss3' 'libpango-1.0-0' 'libsecret-1-0' 'libudev1' 'libx11-6' 'libxcb1' 'libxcomposite1' 'libxdamage1' 'libxext6' 'libxfixes3' 'libxkbcommon0' 'libxkbfile1' 'libxrandr2' 'libxss1' 'libxtst6' 'xdg-desktop-portal' 'xdg-utils')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('ca-certificates' 'glibc' 'alsa-lib' 'atk' 'at-spi2-atk' 'at-spi2-core' 'cairo' 'cups-libs' 'libcurl' 'dbus-libs' 'expat' 'mesa-libgbm' 'glib2' 'gtk3' 'nspr' 'nss' 'pango' 'libsecret' 'systemd-libs' 'libX11' 'libxcb' 'libXcomposite' 'libXdamage' 'libXext' 'libXfixes' 'libxkbcommon' 'libxkbfile' 'libXrandr' 'libXScrnSaver' 'libXtst' 'xdg-desktop-portal' 'xdg-utils')
|
||||
deps_arch=('ca-certificates' 'glibc' 'alsa-lib' 'atk' 'at-spi2-atk' 'at-spi2-core' 'cairo' 'cups' 'curl' 'dbus' 'expat' 'libgbm' 'glib2' 'gtk3' 'nspr' 'nss' 'pango' 'libsecret' 'systemd-libs' 'libx11' 'libxcb' 'libxcomposite' 'libxdamage' 'libxext' 'libxfixes' 'libxkbcommon' 'libxkbfile' 'libxrandr' 'libxss' 'libxtst' 'xdg-desktop-portal' 'xdg-utils')
|
||||
deps_opensuse=('ca-certificates' 'glibc' 'alsa' 'libatk-1_0-0' 'libatk-bridge-2_0-0' 'libatspi0' 'libcairo2' 'cups-libs' 'libcurl4' 'libdbus-1-3' 'libexpat1' 'libgbm1' 'glib2' 'gtk3' 'mozilla-nspr' 'mozilla-nss' 'pango' 'libsecret-1-0' 'libudev1' 'libX11-6' 'libxcb1' 'libXcomposite1' 'libXdamage1' 'libXext6' 'libXfixes3' 'libxkbcommon0' 'libxkbfile1' 'libXrandr2' 'libXss1' 'libXtst6' 'xdg-desktop-portal' 'xdg-utils')
|
||||
deps_altlinux=('ca-certificates' 'glibc' 'libalsa' 'at-spi2-atk' 'libat-spi2-core' 'libatk' 'libcairo' 'libcups' 'libcurl' 'libdbus' 'libexpat' 'libgbm' 'glib2' 'libgtk+3' 'libnspr' 'libnss' 'libpango' 'libsecret' 'libudev0' 'libX11' 'libxcb' 'libXcomposite' 'libXdamage' 'libXext' 'libXfixes' 'libxkbcommon' 'libxkbfile' 'libXrandr' 'libXScrnSaver' 'libXtst' 'xdg-desktop-portal' 'xdg-utils')
|
||||
|
||||
build_deps=('binutils')
|
||||
build_deps_debian=('binutils' 'build-essential' 'cargo' 'libwayland-dev' 'nodejs' 'pkg-config' 'python3' 'rustc')
|
||||
build_deps_ubuntu=("${build_deps_debian[@]}")
|
||||
build_deps_fedora=('binutils' 'cargo' 'gcc' 'gcc-c++' 'make' 'nodejs' 'pkgconf-pkg-config' 'python3' 'rust' 'wayland-devel')
|
||||
build_deps_arch=('base-devel' 'binutils' 'cargo' 'nodejs' 'pkgconf' 'python' 'rust' 'wayland')
|
||||
build_deps_opensuse=('binutils' 'cargo' 'gcc' 'gcc-c++' 'make' 'nodejs' 'pkg-config' 'python3' 'rust' 'wayland-devel')
|
||||
build_deps_altlinux=('binutils' 'gcc-c++' 'libwayland-client-devel' 'make' 'node' 'pkg-config' 'python3' 'rust' 'rust-cargo')
|
||||
|
||||
sources=(
|
||||
"https://github.com/Boria138/codex-app-linux/releases/download/${version}/codex-app-${version}-x86_64.tar.gz?~archive=false&~name=codex-app.tar.gz"
|
||||
'https://github.com/ilysenko/codex-desktop-linux/archive/15f3bd4fabc44bc0afba85b16d0121f0b4abd052.tar.gz?~archive=false&~name=codex-linux-port.tar.gz'
|
||||
'https://github.com/agent-sh/computer-use-linux/archive/refs/tags/v0.4.1.tar.gz?~archive=false&~name=computer-use-linux.tar.gz'
|
||||
'local:///asar_tree.py'
|
||||
'local:///patch-computer-use.js'
|
||||
"local:///codex-app.png"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:f48c0ae3fca556d66525b863dd009a0bd0702115517f83c39f8a44cf4d128eb3'
|
||||
'sha256:8cde2dac642947cdfab23d302056ef81b574cc7f5684f25f7116f80411ec551c'
|
||||
'sha256:fab36489be4f1e92591d2ebcd28a96a95f788b5d1ba87824985623ab7df5e3f4'
|
||||
'sha256:9259dc4b9ae217855d55414b2266db08d9b40c5b0cdbca20a183234ff55ba006'
|
||||
'sha256:91b3cc2a6b660e3962f24976a29a261dae9a38f879758e6e6463e1295477e172'
|
||||
'sha256:5c4fe03d5777a9684e37ec9a2331302abf525f53db83762e6adaafd2fc5eb510'
|
||||
'sha256:755f407295f07facd26c968b73a204db5a6f25deac890d230062b09c33eb395e'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
tar -xzf codex-app.tar.gz
|
||||
tar -xzf codex-linux-port.tar.gz
|
||||
tar -xzf computer-use-linux.tar.gz
|
||||
src="$(find "${srcdir}" -maxdepth 1 -type d -name "codex-app-${version}-x64" | head -1)"
|
||||
linux_port_src="$(find "${srcdir}" -maxdepth 1 -type d -name 'codex-desktop-linux-*' | head -1)"
|
||||
computer_use_src="$(find "${srcdir}" -maxdepth 1 -type d -name 'computer-use-linux-*' | head -1)"
|
||||
|
||||
cargo build \
|
||||
--manifest-path "${computer_use_src}/Cargo.toml" \
|
||||
--target-dir "${srcdir}/computer-use-target" \
|
||||
--release \
|
||||
--locked
|
||||
|
||||
install -dm755 "${pkgdir}/opt/codex-app"
|
||||
cp -a "${src}/." "${pkgdir}/opt/codex-app/"
|
||||
|
||||
codex_asar="${pkgdir}/opt/codex-app/resources/codex.asar"
|
||||
extracted_asar="${srcdir}/codex-asar"
|
||||
python3 "${srcdir}/asar_tree.py" extract "${codex_asar}" "${extracted_asar}"
|
||||
node "${srcdir}/patch-computer-use.js" "${linux_port_src}" "${extracted_asar}"
|
||||
python3 "${srcdir}/asar_tree.py" rebuild "${codex_asar}" "${extracted_asar}"
|
||||
|
||||
bundled_root="${pkgdir}/opt/codex-app/resources/plugins/openai-bundled"
|
||||
plugin_dir="${bundled_root}/plugins/computer-use"
|
||||
install -dm755 "${plugin_dir}"
|
||||
cp -a \
|
||||
"${linux_port_src}/plugins/openai-bundled/plugins/computer-use/." \
|
||||
"${plugin_dir}/"
|
||||
find "${plugin_dir}" -name '.gitkeep' -delete
|
||||
install -Dm755 \
|
||||
"${srcdir}/computer-use-target/release/computer-use-linux" \
|
||||
"${plugin_dir}/bin/codex-computer-use-linux"
|
||||
install -Dm755 \
|
||||
"${srcdir}/computer-use-target/release/computer-use-linux-cosmic" \
|
||||
"${plugin_dir}/bin/codex-computer-use-cosmic"
|
||||
install -Dm755 \
|
||||
"${srcdir}/computer-use-target/release/computer-use-linux-cosmic" \
|
||||
"${plugin_dir}/bin/computer-use-linux-cosmic"
|
||||
install -Dm644 codex-app.png "${plugin_dir}/assets/app-icon.png"
|
||||
|
||||
install -Dm644 /dev/stdin \
|
||||
"${bundled_root}/.agents/plugins/marketplace.json" <<'EOF'
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"name": "computer-use",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/computer-use"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
chmod 755 "${pkgdir}/opt/codex-app/codex-app"
|
||||
chmod 755 "${pkgdir}/opt/codex-app/resources/codex"
|
||||
if [[ -f "${pkgdir}/opt/codex-app/chrome-sandbox" ]]; then
|
||||
chmod 0755 "${pkgdir}/opt/codex-app/chrome-sandbox"
|
||||
fi
|
||||
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
ln -sfn /opt/codex-app/codex-app "${pkgdir}/usr/bin/codex-app"
|
||||
ln -sfn \
|
||||
/opt/codex-app/resources/plugins/openai-bundled/plugins/computer-use/bin/codex-computer-use-linux \
|
||||
"${pkgdir}/usr/bin/codex-computer-use-linux"
|
||||
|
||||
install -Dm644 codex-app.png \
|
||||
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/codex-app.png"
|
||||
|
||||
install -Dm644 "${src}/LICENSE.electron.txt" \
|
||||
"${pkgdir}/usr/share/licenses/codex/LICENSE.electron.txt"
|
||||
install -Dm644 "${src}/LICENSES.chromium.html" \
|
||||
"${pkgdir}/usr/share/licenses/codex/LICENSES.chromium.html"
|
||||
install -Dm644 "${linux_port_src}/LICENSE" \
|
||||
"${pkgdir}/usr/share/licenses/codex/LICENSE.codex-desktop-linux"
|
||||
install -Dm644 "${computer_use_src}/LICENSE" \
|
||||
"${pkgdir}/usr/share/licenses/codex/LICENSE.computer-use-linux"
|
||||
install-license "${srcdir}/LICENSE" "codex/LICENSE.nivora"
|
||||
|
||||
install -Dm644 /dev/stdin "${pkgdir}/usr/share/applications/codex-app.desktop" <<'EOF'
|
||||
[Desktop Entry]
|
||||
Name=Codex
|
||||
Comment=Desktop coding agent client
|
||||
Comment[ru]=Десктопный клиент coding agent
|
||||
Exec=/usr/bin/codex-app %U
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=codex-app
|
||||
Categories=Development;IDE;
|
||||
StartupWMClass=codex-app
|
||||
EOF
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find "/opt/codex-app/**/*"
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find "/usr/share/icons/hicolor/*/apps/codex-app.png"
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract and rebuild an Electron ASAR while preserving its metadata tree."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterator
|
||||
|
||||
|
||||
class AsarError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def align4(value: int) -> int:
|
||||
return (value + 3) & ~3
|
||||
|
||||
|
||||
def read_archive(path: Path) -> tuple[dict[str, Any], bytes, int]:
|
||||
payload = path.read_bytes()
|
||||
if len(payload) < 16:
|
||||
raise AsarError(f"{path}: truncated ASAR header")
|
||||
|
||||
size_pickle_payload, header_pickle_size, header_payload_size, json_size = (
|
||||
struct.unpack_from("<IIII", payload)
|
||||
)
|
||||
expected_header_payload_size = 4 + align4(json_size)
|
||||
if (
|
||||
size_pickle_payload != 4
|
||||
or header_pickle_size != 4 + header_payload_size
|
||||
or header_payload_size != expected_header_payload_size
|
||||
):
|
||||
raise AsarError(f"{path}: unsupported ASAR header layout")
|
||||
|
||||
data_offset = 8 + header_pickle_size
|
||||
if data_offset > len(payload) or 16 + json_size > data_offset:
|
||||
raise AsarError(f"{path}: invalid ASAR header size")
|
||||
|
||||
try:
|
||||
header = json.loads(payload[16 : 16 + json_size].decode("utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||
raise AsarError(f"{path}: invalid ASAR JSON header") from error
|
||||
if not isinstance(header, dict) or not isinstance(header.get("files"), dict):
|
||||
raise AsarError(f"{path}: ASAR header has no file tree")
|
||||
return header, payload, data_offset
|
||||
|
||||
|
||||
def safe_component(name: str) -> None:
|
||||
if (
|
||||
not name
|
||||
or name in {".", ".."}
|
||||
or "/" in name
|
||||
or "\\" in name
|
||||
or "\0" in name
|
||||
):
|
||||
raise AsarError(f"unsafe ASAR entry name: {name!r}")
|
||||
|
||||
|
||||
def entries(
|
||||
files: dict[str, Any], prefix: tuple[str, ...] = ()
|
||||
) -> Iterator[tuple[tuple[str, ...], dict[str, Any]]]:
|
||||
for name, metadata in files.items():
|
||||
safe_component(name)
|
||||
if not isinstance(metadata, dict):
|
||||
raise AsarError(f"invalid metadata for {'/'.join((*prefix, name))}")
|
||||
relative = (*prefix, name)
|
||||
children = metadata.get("files")
|
||||
if children is not None:
|
||||
if not isinstance(children, dict):
|
||||
raise AsarError(f"invalid directory entry: {'/'.join(relative)}")
|
||||
yield from entries(children, relative)
|
||||
else:
|
||||
yield relative, metadata
|
||||
|
||||
|
||||
def output_path(root: Path, relative: tuple[str, ...]) -> Path:
|
||||
candidate = root.joinpath(*relative)
|
||||
try:
|
||||
candidate.resolve().relative_to(root.resolve())
|
||||
except ValueError as error:
|
||||
raise AsarError(f"entry escapes extraction root: {'/'.join(relative)}") from error
|
||||
return candidate
|
||||
|
||||
|
||||
def extract(archive: Path, destination: Path) -> None:
|
||||
header, payload, data_offset = read_archive(archive)
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for relative, metadata in entries(header["files"]):
|
||||
if metadata.get("unpacked") is True or "link" in metadata:
|
||||
continue
|
||||
try:
|
||||
size = int(metadata["size"])
|
||||
offset = int(metadata["offset"])
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
raise AsarError(f"invalid file entry: {'/'.join(relative)}") from error
|
||||
start = data_offset + offset
|
||||
end = start + size
|
||||
if offset < 0 or size < 0 or end > len(payload):
|
||||
raise AsarError(f"file lies outside archive: {'/'.join(relative)}")
|
||||
|
||||
target = output_path(destination, relative)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(payload[start:end])
|
||||
if metadata.get("executable") is True:
|
||||
target.chmod(target.stat().st_mode | 0o111)
|
||||
|
||||
|
||||
def integrity(content: bytes, block_size: int) -> dict[str, Any]:
|
||||
blocks = [
|
||||
hashlib.sha256(content[offset : offset + block_size]).hexdigest()
|
||||
for offset in range(0, len(content), block_size)
|
||||
]
|
||||
if not blocks:
|
||||
blocks = [hashlib.sha256(b"").hexdigest()]
|
||||
return {
|
||||
"algorithm": "SHA256",
|
||||
"hash": hashlib.sha256(content).hexdigest(),
|
||||
"blockSize": block_size,
|
||||
"blocks": blocks,
|
||||
}
|
||||
|
||||
|
||||
def rebuild(archive: Path, source: Path, output: Path) -> None:
|
||||
header, _, _ = read_archive(archive)
|
||||
content_chunks: list[bytes] = []
|
||||
offset = 0
|
||||
|
||||
for relative, metadata in entries(header["files"]):
|
||||
if metadata.get("unpacked") is True or "link" in metadata:
|
||||
continue
|
||||
target = output_path(source, relative)
|
||||
if not target.is_file():
|
||||
raise AsarError(f"missing extracted file: {'/'.join(relative)}")
|
||||
content = target.read_bytes()
|
||||
metadata["size"] = len(content)
|
||||
metadata["offset"] = str(offset)
|
||||
if "integrity" in metadata:
|
||||
block_size = int(metadata["integrity"].get("blockSize", 4 * 1024 * 1024))
|
||||
metadata["integrity"] = integrity(content, block_size)
|
||||
content_chunks.append(content)
|
||||
offset += len(content)
|
||||
|
||||
json_payload = json.dumps(
|
||||
header, ensure_ascii=False, separators=(",", ":")
|
||||
).encode("utf-8")
|
||||
padded_json_size = align4(len(json_payload))
|
||||
header_payload_size = 4 + padded_json_size
|
||||
prefix = struct.pack(
|
||||
"<IIII",
|
||||
4,
|
||||
4 + header_payload_size,
|
||||
header_payload_size,
|
||||
len(json_payload),
|
||||
)
|
||||
result = (
|
||||
prefix
|
||||
+ json_payload
|
||||
+ b"\0" * (padded_json_size - len(json_payload))
|
||||
+ b"".join(content_chunks)
|
||||
)
|
||||
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if output.resolve() == archive.resolve():
|
||||
descriptor, temporary_name = tempfile.mkstemp(
|
||||
prefix=f".{output.name}.", dir=output.parent
|
||||
)
|
||||
temporary = Path(temporary_name)
|
||||
try:
|
||||
with os.fdopen(descriptor, "wb") as stream:
|
||||
stream.write(result)
|
||||
temporary.chmod(archive.stat().st_mode)
|
||||
temporary.replace(output)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
else:
|
||||
output.write_bytes(result)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
extract_parser = subparsers.add_parser("extract")
|
||||
extract_parser.add_argument("archive", type=Path)
|
||||
extract_parser.add_argument("destination", type=Path)
|
||||
|
||||
rebuild_parser = subparsers.add_parser("rebuild")
|
||||
rebuild_parser.add_argument("archive", type=Path)
|
||||
rebuild_parser.add_argument("source", type=Path)
|
||||
rebuild_parser.add_argument("output", type=Path, nargs="?")
|
||||
|
||||
arguments = parser.parse_args()
|
||||
if arguments.command == "extract":
|
||||
extract(arguments.archive, arguments.destination)
|
||||
else:
|
||||
rebuild(
|
||||
arguments.archive,
|
||||
arguments.source,
|
||||
arguments.output or arguments.archive,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
After Width: | Height: | Size: 482 KiB |
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
// This file only adapts upstream patch discovery to the current hashed bundle
|
||||
// name. Computer Use behavior remains sourced from codex-desktop-linux.
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const linuxPortRoot = path.resolve(process.argv[2] ?? "");
|
||||
const extractedApp = path.resolve(process.argv[3] ?? "");
|
||||
if (!fs.existsSync(linuxPortRoot) || !fs.existsSync(extractedApp)) {
|
||||
console.error("usage: patch-computer-use.js LINUX_PORT_ROOT EXTRACTED_APP");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
process.env.CODEX_LINUX_ENABLE_COMPUTER_USE_UI = "1";
|
||||
|
||||
const { patchExtractedApp } = require(
|
||||
path.join(linuxPortRoot, "scripts/patches/runner.js"),
|
||||
);
|
||||
const implementation = require(
|
||||
path.join(linuxPortRoot, "scripts/patches/impl/computer-use.js"),
|
||||
);
|
||||
const coreRoot = path.join(
|
||||
linuxPortRoot,
|
||||
"scripts/patches/core/all-linux",
|
||||
);
|
||||
|
||||
patchExtractedApp(extractedApp, {
|
||||
corePatchRoot: path.join(coreRoot, "main-process/computer-use"),
|
||||
});
|
||||
patchExtractedApp(extractedApp, {
|
||||
corePatchRoot: path.join(coreRoot, "webview/computer-use-ui"),
|
||||
});
|
||||
|
||||
const assetsDirectory = path.join(extractedApp, "webview/assets");
|
||||
const javaScriptAssets = fs.readdirSync(assetsDirectory)
|
||||
.filter((name) => name.endsWith(".js"))
|
||||
.map((name) => path.join(assetsDirectory, name));
|
||||
|
||||
function applyToUniqueAsset(label, patchFunction, alreadyPatched, isCandidate) {
|
||||
const changed = [];
|
||||
const existing = [];
|
||||
for (const target of javaScriptAssets) {
|
||||
const source = fs.readFileSync(target, "utf8");
|
||||
if (alreadyPatched(source)) {
|
||||
existing.push(target);
|
||||
continue;
|
||||
}
|
||||
if (!isCandidate(source)) {
|
||||
continue;
|
||||
}
|
||||
const patched = patchFunction(source);
|
||||
if (patched !== source) {
|
||||
changed.push({ target, patched });
|
||||
}
|
||||
}
|
||||
if (existing.length === 1 && changed.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (existing.length !== 0 || changed.length !== 1) {
|
||||
throw new Error(
|
||||
`${label}: expected one target, found existing=${existing.length}, changed=${changed.length}`,
|
||||
);
|
||||
}
|
||||
fs.writeFileSync(changed[0].target, changed[0].patched, "utf8");
|
||||
console.log(`${label}: ${path.basename(changed[0].target)}`);
|
||||
}
|
||||
|
||||
applyToUniqueAsset(
|
||||
"Linux Computer Use host platform",
|
||||
implementation.applyLinuxComputerUseHostPlatformPatch,
|
||||
(source) =>
|
||||
/featureName:`computer_use`[\s\S]{0,2200}?isHostCompatiblePlatform:[A-Za-z_$][\w$]*===`linux`\|\|/.test(
|
||||
source,
|
||||
),
|
||||
(source) => source.includes("featureName:`computer_use`"),
|
||||
);
|
||||
applyToUniqueAsset(
|
||||
"Linux Computer Use install flow",
|
||||
implementation.applyLinuxComputerUseInstallFlowPatch,
|
||||
(source) =>
|
||||
source.includes("plugin detail query requires pluginName") &&
|
||||
/let [A-Za-z_$][\w$]*=[A-Za-z_$][\w$]*&&[A-Za-z_$][\w$]*!==`computer-use`,/.test(
|
||||
source,
|
||||
),
|
||||
(source) => source.includes("plugin detail query requires pluginName"),
|
||||
);
|
||||
|
||||
const iconCandidates = fs.readdirSync(assetsDirectory)
|
||||
.filter((name) => /^computer-use-plugin-icon-[^.]+\.png$/.test(name))
|
||||
.filter((name) => name !== "computer-use-plugin-icon-linux.png");
|
||||
if (iconCandidates.length !== 1) {
|
||||
throw new Error(
|
||||
`Computer Use icon: expected one source, found ${iconCandidates.length}`,
|
||||
);
|
||||
}
|
||||
let iconReferences = 0;
|
||||
for (const target of javaScriptAssets) {
|
||||
const source = fs.readFileSync(target, "utf8");
|
||||
if (!source.includes("computer-use-plugin-icon-linux.png")) {
|
||||
continue;
|
||||
}
|
||||
fs.writeFileSync(
|
||||
target,
|
||||
source.replaceAll(
|
||||
"computer-use-plugin-icon-linux.png",
|
||||
iconCandidates[0],
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
iconReferences += 1;
|
||||
}
|
||||
if (iconReferences !== 1) {
|
||||
throw new Error(
|
||||
`Computer Use icon: expected one patched reference, found ${iconReferences}`,
|
||||
);
|
||||
}
|
||||
|
||||
const mainBundle = fs.readdirSync(path.join(extractedApp, ".vite/build"))
|
||||
.filter((name) => /^main-[^.]+\.js$/.test(name));
|
||||
if (mainBundle.length !== 1) {
|
||||
throw new Error(`main bundle: expected one target, found ${mainBundle.length}`);
|
||||
}
|
||||
const mainSource = fs.readFileSync(
|
||||
path.join(extractedApp, ".vite/build", mainBundle[0]),
|
||||
"utf8",
|
||||
);
|
||||
for (const marker of [
|
||||
"codexLinuxNativeDesktopApps(",
|
||||
"codexLinuxRegisterComputerUseCursorHandler",
|
||||
"platform!==`darwin`&&",
|
||||
]) {
|
||||
if (!mainSource.includes(marker)) {
|
||||
throw new Error(`main bundle: missing upstream marker ${marker}`);
|
||||
}
|
||||
}
|
||||
console.log("Linux Computer Use upstream patches applied");
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
sandbox=/opt/codex-app/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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
[inherit]
|
||||
@@ -0,0 +1,106 @@
|
||||
# Сопровождение Nivora
|
||||
|
||||
## Инварианты
|
||||
|
||||
- В репозитории ровно 19 каталогов с `Staplerfile`.
|
||||
- Каталог совпадает с `name` и командой в README.
|
||||
- Upstream-версия не меняется из-за патча рецепта; для этого повышается `release`.
|
||||
- Desktop-id, AppStream component-id, units и пути данных не меняются без отдельной миграции.
|
||||
- `provides` и `conflicts` остаются пустыми, а `replaces` содержит только собственное
|
||||
базовое имя пакета.
|
||||
|
||||
Stapler сам добавляет текущее `name` в generated `Provides` и `Conflicts`. Рецепты не
|
||||
добавляют переходные package ID и не зависят от других Stapler-каталогов.
|
||||
|
||||
## Названия
|
||||
|
||||
| Package ID | Основание |
|
||||
|:--|:--|
|
||||
| `clash-verge-rev` | Upstream-репозиторий и release title: Clash Verge Rev |
|
||||
| `claude-desktop` | Upstream DEB: `Package: claude-desktop`; desktop-id `com.anthropic.Claude` сохранён |
|
||||
| `codex` | Отображаемое имя `Codex`; команда и desktop-id `codex-app` сохранены |
|
||||
| `github-desktop` | Официальный upstream `desktop/desktop`; Linux-сборка без стороннего форка |
|
||||
| `nivora-stplr` | Собственный helper Nivora |
|
||||
|
||||
## Локальные проверки
|
||||
|
||||
```bash
|
||||
tools/run_checks.sh
|
||||
tools/package_updates.sh check-all
|
||||
tools/verify_artifacts.sh --all
|
||||
tools/test_package_lifecycle.sh
|
||||
```
|
||||
|
||||
`run_checks.sh` выполняет `bash -n`, ShellCheck, Python compile, unit-тесты, validator и чтение
|
||||
всех `Staplerfile` через `stplr-spec`.
|
||||
|
||||
`verify_artifacts.sh` сопоставляет готовые RPM с `files()`, проверяет владельцев путей,
|
||||
права, desktop-файлы, systemd units, иконки, лицензии и метаданные совместимости.
|
||||
|
||||
`test_package_lifecycle.sh` собирает настоящие DEB текущей версии и использует настоящие
|
||||
RPM из clean-build. Минимальные fixtures изображают предыдущую версию того же пакета Nivora.
|
||||
В одноразовых Ubuntu и ALT-контейнерах через APT проверяются:
|
||||
|
||||
1. обновление с предыдущей версии Nivora на текущую;
|
||||
2. `Provides`, `Replaces` и `Conflicts`;
|
||||
3. наличие команды, desktop-файла или systemd unit;
|
||||
4. сохранение пользовательского состояния после обновления и удаления.
|
||||
|
||||
Полный жизненный цикл проверяется для восьми критичных пакетов. Остальные пакеты
|
||||
покрываются validator, clean-build и проверкой payload.
|
||||
|
||||
Локально DEB собираются в привилегированном контейнере. На GitHub-hosted runner используется
|
||||
`NIVORA_DEB_BUILD_MODE=host`: закреплённый stplr запускается непосредственно на одноразовом
|
||||
Ubuntu runner, потому что вложенный sandbox stplr запрещён внутри Docker. Ubuntu 24.04 может
|
||||
дополнительно блокировать непривилегированные user namespaces через AppArmor: тест временно
|
||||
снимает только это ограничение, проверяет полный набор namespaces перед сборкой и
|
||||
восстанавливает исходное значение при завершении. Для совместимости с моделью привилегий
|
||||
Stapler временный builder включается в группу `wheel`, отсутствующую в Ubuntu по умолчанию.
|
||||
Транзакционные сценарии в обоих режимах остаются изолированными в контейнерах.
|
||||
|
||||
## Обновление пакета
|
||||
|
||||
```bash
|
||||
stplr-spec update-package package
|
||||
stplr-spec verify-checksums --path package/Staplerfile
|
||||
tools/run_checks.sh
|
||||
tools/clean_build.sh package
|
||||
```
|
||||
|
||||
Нестандартная логика обнаружения версий находится в `tools/package_updates.sh`, а каждый
|
||||
`.stapler/update-check` вызывает его для своего package ID.
|
||||
|
||||
Плановый workflow обновляет пакеты автономно и отправляет проверенные изменения
|
||||
прямо в `main`. Он запускается ежедневно в 03:00 по Владивостоку
|
||||
(`17:00 UTC`). Каждый пакет обрабатывается в отдельном временном worktree, поэтому
|
||||
несовместимое обновление одного upstream не блокирует остальные. При сбое workflow
|
||||
сохраняет на 30 дней диагностический artifact с полным логом, фазой сбоя, diff и
|
||||
получившимся `Staplerfile`; успешно собранные пакеты всё равно публикуются. Для
|
||||
каждого несовместимого пакета создаётся один постоянный issue: повторные сбои
|
||||
обновляют его, а успешное восстановление автоматически закрывает. Ожидаемый сбой
|
||||
отдельного пакета помечается предупреждением и не делает весь этап обновления
|
||||
неуспешным.
|
||||
|
||||
## Clean-build
|
||||
|
||||
```bash
|
||||
tools/clean_build.sh package
|
||||
tools/clean_build.sh --all
|
||||
tools/verify_artifacts.sh --all
|
||||
```
|
||||
|
||||
Скрипт всегда выполняет сборку в собственном одноразовом контейнере ALT и не
|
||||
подключает сторонние Stapler-каталоги.
|
||||
|
||||
## Проверка жизненного цикла
|
||||
|
||||
Для каждого критичного пакета нужно:
|
||||
|
||||
1. Собрать fixture предыдущей версии и текущий RPM/DEB.
|
||||
2. Создать тестовый файл в каталоге данных.
|
||||
3. Обновить пакет до текущей версии.
|
||||
4. Проверить `Provides/Replaces/Conflicts`, payload и тестовый файл.
|
||||
5. Проверить удаление пакета без удаления пользовательского состояния.
|
||||
|
||||
Автоматизированная проверка выполняется командой `tools/test_package_lifecycle.sh` в
|
||||
одноразовых контейнерах, а не на рабочей системе сопровождающего.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Claude Desktop
|
||||
|
||||
Пакет `claude-desktop` переупаковывает DEB из APT-источника Anthropic и сохраняет upstream
|
||||
desktop-id `com.anthropic.Claude`.
|
||||
|
||||
```bash
|
||||
sudo stplr install nivora/claude-desktop
|
||||
```
|
||||
|
||||
## Два независимых приложения
|
||||
|
||||
- `claude-desktop` запускает основное приложение Claude.
|
||||
- `claude-alt` и `claude-code-alt` запускают отдельное приложение ClaudeAlt.
|
||||
- `claude-desktop-account2` — дополнительное имя запуска ClaudeAlt.
|
||||
- Команда `claude` не создаётся, чтобы не конфликтовать с Claude Code CLI.
|
||||
|
||||
ClaudeAlt имеет отдельные executable/resources tree, Electron `productName`, Wayland `app_id`,
|
||||
X11 `WM_CLASS`, desktop-файл, Chromium-профиль, Cowork VM socket, оконную и
|
||||
tray-иконки. В системном лотке используется штатный глиф Claude: оранжевый у
|
||||
основного приложения и бирюзовый у ClaudeAlt. Большие app-иконки окон при этом
|
||||
остаются отдельными. Поэтому окружение рабочего стола группирует Claude и ClaudeAlt
|
||||
как разные приложения, а `requestSingleInstanceLock()` и Cowork runtime каждого
|
||||
приложения работают независимо.
|
||||
|
||||
Новая установка ClaudeAlt хранит cookies, OAuth, IndexedDB и Chromium locks в
|
||||
`${XDG_CONFIG_HOME:-~/.config}/ClaudeAlt`. Если уже существует прежний каталог
|
||||
`${XDG_CONFIG_HOME:-~/.config}/Claude-Account-2`, launcher использует его, чтобы после обновления
|
||||
не потерять авторизацию второго аккаунта. Глобальные данные Claude Code в `~/.claude` могут
|
||||
по-прежнему использоваться обоими приложениями.
|
||||
|
||||
## Настройка
|
||||
|
||||
| Переменная | Назначение |
|
||||
|:--|:--|
|
||||
| `CLAUDE_ALT_DATA_DIR` | Явно задать каталог профиля ClaudeAlt |
|
||||
| `CLAUDE_DESKTOP_ACCOUNT2_DIR` | Совместимое имя переменной прежнего второго профиля |
|
||||
| `CLAUDE_ALT_EXECUTABLE` | Переопределить executable ClaudeAlt для диагностики |
|
||||
|
||||
Основной Claude продолжает использовать upstream desktop-id `com.anthropic.Claude`, а ClaudeAlt
|
||||
использует `com.anthropic.ClaudeAlt`.
|
||||
|
||||
## Сетевая изоляция
|
||||
|
||||
Claude и ClaudeAlt имеют разные Chromium-профили, single-instance sockets,
|
||||
cookies, browser storage, Crashpad-каталоги и Cowork VM sockets. Временные OAuth
|
||||
callback-серверы по умолчанию получают свободный loopback-порт от ОС.
|
||||
|
||||
Приложения не являются сетевыми песочницами: оба выходят в Интернет с
|
||||
одного хоста и публичного IP-адреса. Общий `claude://` URL handler намеренно
|
||||
остаётся за основным Claude. Если в обоих приложениях вручную задать
|
||||
один и тот же фиксированный OAuth callback-порт и запустить авторизацию
|
||||
одновременно, второе приложение получит `EADDRINUSE`.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Codex
|
||||
|
||||
Пакет устанавливает неофициальную Linux-перепаковку Codex Desktop и
|
||||
предусмотренную Linux-реализацию Computer Use из проектов
|
||||
[`codex-desktop-linux`](https://github.com/ilysenko/codex-desktop-linux) и
|
||||
[`computer-use-linux`](https://github.com/agent-sh/computer-use-linux).
|
||||
|
||||
В пакет входят:
|
||||
|
||||
- приложение `codex-app`;
|
||||
- bundled plugin `computer-use@openai-bundled`;
|
||||
- нативный backend `codex-computer-use-linux`, собранный из исходников для
|
||||
версии glibc текущего дистрибутива;
|
||||
- интеграция с AT-SPI, XDG Desktop Portal и доступными средствами ввода Linux.
|
||||
|
||||
Патчи не включают серверную возможность в обход Codex. Они добавляют Linux в
|
||||
список поддерживаемых локальных платформ и сохраняют проверку feature gate
|
||||
`computer_use`, которую возвращает сервис.
|
||||
|
||||
## Первичная настройка
|
||||
|
||||
После установки полностью перезапустите Codex. Состояние backend проверяется
|
||||
без изменения системы:
|
||||
|
||||
```bash
|
||||
codex-computer-use-linux doctor
|
||||
```
|
||||
|
||||
Рекомендуемую настройку доступности и ввода выполняет upstream-мастер:
|
||||
|
||||
```bash
|
||||
codex-computer-use-linux setup
|
||||
```
|
||||
|
||||
Для получения списка окон в GNOME может понадобиться отдельная настройка
|
||||
расширения:
|
||||
|
||||
```bash
|
||||
codex-computer-use-linux setup-window-targeting
|
||||
```
|
||||
|
||||
Команда `setup` идемпотентно включает AT-SPI в текущем пользовательском сеансе;
|
||||
она изменяет пользовательскую настройку доступности без отдельного запроса.
|
||||
Разрешения на захват экрана и управление вводом выдаются пользователем через
|
||||
системные диалоги портала.
|
||||
|
||||
## Диагностика
|
||||
|
||||
```bash
|
||||
codex-computer-use-linux doctor
|
||||
codex-computer-use-linux apps
|
||||
codex-computer-use-linux windows
|
||||
```
|
||||
|
||||
Если интерфейс всё ещё показывает недоступность, сначала убедитесь, что
|
||||
установлен пакет Nivora с `release=2` или новее, затем закройте все процессы
|
||||
Codex и запустите приложение заново.
|
||||
@@ -0,0 +1,27 @@
|
||||
# GitHub Desktop
|
||||
|
||||
Пакет собирает оригинальный GitHub Desktop для Linux непосредственно из
|
||||
официального репозитория [`desktop/desktop`](https://github.com/desktop/desktop).
|
||||
Готовые артефакты, патчи и исходники `shiftkey/desktop` либо других форков не
|
||||
используются.
|
||||
|
||||
GitHub официально выпускает установщики только для macOS и Windows. Linux-сборка
|
||||
в Nivora поэтому является неофициальным пакетом, но код приложения и закреплённые
|
||||
submodule-исходники берутся только из официальных репозиториев GitHub.
|
||||
|
||||
Тяжёлая сборка выполняется workflow `Сборка GitHub Desktop для Linux` на
|
||||
нативных GitHub-hosted runner’ах `x64` и `arm64`. Workflow публикует
|
||||
детерминированно упакованные tar.gz и `SHA256SUMS` в релизе Nivora, а рецепт
|
||||
скачивает эти готовые артефакты с закреплёнными контрольными суммами.
|
||||
|
||||
Установка:
|
||||
|
||||
```bash
|
||||
sudo stplr install nivora/github-desktop
|
||||
```
|
||||
|
||||
Команда запуска:
|
||||
|
||||
```bash
|
||||
github-desktop
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
# Nivora Stapler Helper
|
||||
|
||||
`nivora-stplr` добавляет короткие команды для Stapler.
|
||||
|
||||
```bash
|
||||
sudo stplr install nivora/nivora-stplr
|
||||
```
|
||||
|
||||
## Команды
|
||||
|
||||
| Alias | Вызов Stapler |
|
||||
|:--|:--|
|
||||
| `sli` | `sudo stplr install` |
|
||||
| `slr` | `sudo stplr remove` |
|
||||
| `slu` | `sudo stplr up` |
|
||||
| `slf` | `sudo stplr fix` |
|
||||
| `slref` | `sudo stplr refresh` |
|
||||
| `sls` | `stplr search` |
|
||||
| `slii` | `stplr info` |
|
||||
| `sll` | `stplr list` |
|
||||
| `sl` | Диспетчер тех же команд |
|
||||
|
||||
Примеры:
|
||||
|
||||
```bash
|
||||
sli --repo nivora parsec
|
||||
slii nivora/codex
|
||||
slu --clean
|
||||
```
|
||||
|
||||
Короткое имя пакета не привязывается к Nivora по умолчанию. Префикс задаётся явно:
|
||||
|
||||
```bash
|
||||
export NIVORA_STPLR_REPO=nivora
|
||||
```
|
||||
|
||||
| Переменная | Назначение |
|
||||
|:--|:--|
|
||||
| `NIVORA_STPLR_REPO` | Префикс для коротких package names |
|
||||
| `NIVORA_STPLR_SUDO` | Команда повышения привилегий, по умолчанию `sudo` |
|
||||
| `NIVORA_STPLR_QUIET=1` | Не печатать команду перед запуском |
|
||||
|
||||
Helper не передаёт произвольные shell-строки через `sudo`.
|
||||
@@ -0,0 +1,16 @@
|
||||
# OpenCode
|
||||
|
||||
Пакет устанавливает официальный Linux desktop-артефакт OpenCode без изменений его кода и ресурсов.
|
||||
|
||||
```bash
|
||||
sudo stplr install nivora/opencode
|
||||
```
|
||||
|
||||
## Запуск
|
||||
|
||||
- из меню приложений — `OpenCode`;
|
||||
- из терминала — `opencode-desktop`.
|
||||
|
||||
Команда `opencode-desktop` является прямой ссылкой на upstream-бинарник
|
||||
`/opt/OpenCode/ai.opencode.desktop`: wrapper, лимиты памяти, GPU-fallback и другие поведенческие
|
||||
патчи не применяются.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Ventoy
|
||||
|
||||
Пакет устанавливает официальный нативный Linux GUI Ventoy и показывает его в
|
||||
меню приложений под коротким названием **Ventoy**.
|
||||
|
||||
## Запуск
|
||||
|
||||
```bash
|
||||
ventoy
|
||||
```
|
||||
|
||||
При запуске Ventoy запрашивает административные права через `pkexec`, потому
|
||||
что программа размечает накопители и записывает загрузочные структуры напрямую.
|
||||
|
||||
## Важно
|
||||
|
||||
- Перед установкой Ventoy внимательно проверьте выбранное устройство.
|
||||
- Первичная установка стирает данные на выбранном накопителе.
|
||||
- Обновление уже подготовленного Ventoy-накопителя сохраняет раздел с образами,
|
||||
однако резервная копия важных данных всё равно рекомендуется.
|
||||
- Пакет использует официальный архив upstream и поддерживает `amd64` и `arm64`.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Модель доверия
|
||||
|
||||
Nivora — независимый community-репозиторий. Он не контролируется командой Stapler или
|
||||
разработчиками упакованных приложений.
|
||||
|
||||
## Что можно проверить
|
||||
|
||||
- `Staplerfile` описывает sources, зависимости, сборку и состав payload.
|
||||
- Локальные wrappers и hooks хранятся рядом с рецептом.
|
||||
- Каждому source соответствует SHA-256.
|
||||
- Валидатор запрещает HTTP, `SKIP`, отсутствующие локальные sources и незакреплённые Git URL.
|
||||
|
||||
## Что не гарантирует SHA-256
|
||||
|
||||
Контрольная сумма обнаруживает замену файла после фиксации рецепта. Она не доказывает:
|
||||
|
||||
- отсутствие уязвимостей в upstream-приложении;
|
||||
- корректность сетевого сервиса;
|
||||
- соответствие файла исходному коду без воспроизводимой upstream-сборки.
|
||||
|
||||
## Проприетарные пакеты
|
||||
|
||||
Для `Custom` и `Proprietary` Nivora переупаковывает опубликованный артефакт, но не изменяет
|
||||
условия его владельца. Учётные записи, подписки, API и сетевые сервисы в пакет не входят.
|
||||
|
||||
## Данные пользователя
|
||||
|
||||
Пакетные scripts не должны удалять каталоги в `$HOME`, выполнять logout или сбрасывать учётную
|
||||
запись при обычном удалении. Отдельные команды очистки Tailscale и NetBird требуют `--yes`.
|
||||
|
||||
## Границы CI
|
||||
|
||||
CI проверяет структуру, shell/Python, metadata, checksums и выбранные сборки. Разница в версиях
|
||||
библиотек, драйверах, desktop-окружениях и внешних API остаётся ограничением.
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,12 @@
|
||||
Сведения о лицензии пакета Fisher
|
||||
|
||||
Лицензия: MIT.
|
||||
Исходный проект: https://github.com/jorgebucaran/fisher
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет устанавливает Fisher — менеджер плагинов для Fish shell.
|
||||
- Код распространяется на условиях MIT License.
|
||||
- Пакет не добавляет закрытых бинарных компонентов.
|
||||
|
||||
Полный официальный текст MIT:
|
||||
https://opensource.org/licenses/MIT
|
||||
@@ -0,0 +1,54 @@
|
||||
name='fisher'
|
||||
version='4.4.8'
|
||||
release=3
|
||||
summary='Fish plugin manager'
|
||||
summary_ru='Менеджер плагинов для Fish shell'
|
||||
desc='Fisher is a plugin manager for Fish shell — zero configuration, pure Fish, blazing fast concurrent downloads.'
|
||||
desc_ru='Fisher — менеджер плагинов для Fish shell. Ноль конфигурации, чистый Fish, молниеносная параллельная загрузка.'
|
||||
group='Shells'
|
||||
homepage='https://github.com/jorgebucaran/fisher'
|
||||
maintainer='Cheviiot <https://github.com/Cheviiot>'
|
||||
|
||||
architectures=('all')
|
||||
|
||||
license=('MIT')
|
||||
|
||||
provides=()
|
||||
replaces=('fisher')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=(
|
||||
'fish'
|
||||
)
|
||||
|
||||
deps_debian=('fish')
|
||||
deps_ubuntu=('fish')
|
||||
deps_fedora=('fish')
|
||||
deps_arch=('fish')
|
||||
deps_altlinux=('fish')
|
||||
deps_opensuse=('fish')
|
||||
|
||||
sources=(
|
||||
"https://raw.githubusercontent.com/jorgebucaran/fisher/${version}/functions/fisher.fish"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
checksums=(
|
||||
'0fb6c81ae3003e95b5671766fa6c25c3597066e29965b7772f6c1b007387356d'
|
||||
'567ce81f1514e7338d5c1a1e5c2d8ef772c1ff01d77d7b76501e67c148b14dbb'
|
||||
)
|
||||
|
||||
package() {
|
||||
install -Dm644 "${srcdir}/fisher.fish" \
|
||||
"${pkgdir}/usr/share/fish/vendor_functions.d/fisher.fish"
|
||||
install-license "${srcdir}/LICENSE" "fisher/LICENSE.nivora"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find \
|
||||
"/usr/share/fish/vendor_functions.d/fisher.fish"
|
||||
files-find-license
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[inherit]
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,109 @@
|
||||
name='github-desktop'
|
||||
version='3.6.3'
|
||||
release=2
|
||||
summary='GitHub Desktop built from the official source'
|
||||
summary_ru='GitHub Desktop, собранный из официальных исходников'
|
||||
group='Development/Tools'
|
||||
desc='The original GitHub Desktop application, built for Linux by Nivora CI directly from the official desktop/desktop source tree without third-party forks.'
|
||||
desc_ru='Оригинальное приложение GitHub Desktop, собранное для Linux в CI Nivora напрямую из официального дерева desktop/desktop без сторонних форков.'
|
||||
homepage='https://github.com/desktop/desktop'
|
||||
maintainer='GitHub <https://github.com/desktop>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('MIT')
|
||||
|
||||
provides=()
|
||||
replaces=('github-desktop')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('ca-certificates' 'git' 'libasound2' 'libatk-bridge2.0-0' 'libatk1.0-0' 'libatspi2.0-0' 'libcairo2' 'libcups2' 'libdbus-1-3' 'libexpat1' 'libgbm1' 'libglib2.0-0' 'libgtk-3-0' 'libnspr4' 'libnss3' 'libpango-1.0-0' 'libsecret-1-0' 'libudev1' 'libx11-6' 'libxcb1' 'libxcomposite1' 'libxdamage1' 'libxext6' 'libxfixes3' 'libxkbcommon0' 'libxrandr2' 'libxss1' 'libxtst6' 'xdg-utils')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('ca-certificates' 'git' 'alsa-lib' 'atk' 'at-spi2-atk' 'at-spi2-core' 'cairo' 'cups-libs' 'dbus-libs' 'expat' 'mesa-libgbm' 'glib2' 'gtk3' 'nspr' 'nss' 'pango' 'libsecret' 'systemd-libs' 'libX11' 'libxcb' 'libXcomposite' 'libXdamage' 'libXext' 'libXfixes' 'libxkbcommon' 'libXrandr' 'libXScrnSaver' 'libXtst' 'xdg-utils')
|
||||
deps_arch=('ca-certificates' 'git' 'alsa-lib' 'atk' 'at-spi2-atk' 'at-spi2-core' 'cairo' 'cups' 'dbus' 'expat' 'libgbm' 'glib2' 'gtk3' 'nspr' 'nss' 'pango' 'libsecret' 'systemd-libs' 'libx11' 'libxcb' 'libxcomposite' 'libxdamage' 'libxext' 'libxfixes' 'libxkbcommon' 'libxrandr' 'libxss' 'libxtst' 'xdg-utils')
|
||||
deps_opensuse=('ca-certificates' 'git' 'alsa' 'libatk-1_0-0' 'libatk-bridge-2_0-0' 'libatspi0' 'libcairo2' 'cups-libs' 'libdbus-1-3' 'libexpat1' 'libgbm1' 'glib2' 'gtk3' 'mozilla-nspr' 'mozilla-nss' 'pango' 'libsecret-1-0' 'libudev1' 'libX11-6' 'libxcb1' 'libXcomposite1' 'libXdamage1' 'libXext6' 'libXfixes3' 'libxkbcommon0' 'libXrandr2' 'libXss1' 'libXtst6' 'xdg-utils')
|
||||
deps_altlinux=('ca-certificates' 'git' 'libalsa' 'at-spi2-atk' 'libat-spi2-core' 'libatk' 'libcairo' 'libcups' 'libdbus' 'libexpat' 'libgbm' 'glib2' 'libgtk+3' 'libnspr' 'libnss' 'libpango' 'libsecret' 'libudev0' 'libX11' 'libxcb' 'libXcomposite' 'libXdamage' 'libXext' 'libXfixes' 'libxkbcommon' 'libXrandr' 'libXScrnSaver' 'libXtst' 'xdg-utils')
|
||||
|
||||
build_deps=('binutils')
|
||||
|
||||
sources=(
|
||||
"https://github.com/Cheviiot/Nivora/releases/download/github-desktop-${version}-linux/github-desktop-${version}-linux-x64.tar.gz?~archive=false&~name=github-desktop.tar.gz"
|
||||
"https://raw.githubusercontent.com/desktop/desktop/release-${version}/LICENSE?~archive=false&~name=LICENSE"
|
||||
"https://raw.githubusercontent.com/desktop/desktop/release-${version}/app/static/logos/prod/icon-logo.icon/Assets/1%20%E2%80%93%20Layer%203.svg?~archive=false&~name=github-desktop.svg"
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://github.com/Cheviiot/Nivora/releases/download/github-desktop-${version}-linux/github-desktop-${version}-linux-arm64.tar.gz?~archive=false&~name=github-desktop.tar.gz"
|
||||
"https://raw.githubusercontent.com/desktop/desktop/release-${version}/LICENSE?~archive=false&~name=LICENSE"
|
||||
"https://raw.githubusercontent.com/desktop/desktop/release-${version}/app/static/logos/prod/icon-logo.icon/Assets/1%20%E2%80%93%20Layer%203.svg?~archive=false&~name=github-desktop.svg"
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:78d55b126a200c3585c7d0e3a32b95fe5d0d19e71921169c3e376af520d9a179'
|
||||
'sha256:891d678cd6aa67c0712f663b5fee690f24d11d360795300814f7bf2eb91ba530'
|
||||
'sha256:182efd5af36dccaa389086695e38140ff42af0befd2caaa6661e4cc0ffe614b7'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'sha256:b1934f1d8d621ee879ef6b7d7dabe9704134c4dc861813b776f42deedce5a038'
|
||||
'sha256:891d678cd6aa67c0712f663b5fee690f24d11d360795300814f7bf2eb91ba530'
|
||||
'sha256:182efd5af36dccaa389086695e38140ff42af0befd2caaa6661e4cc0ffe614b7'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
tar -xzf github-desktop.tar.gz
|
||||
[[ -x "${srcdir}/github-desktop/desktop" ]]
|
||||
|
||||
install -dm755 "${pkgdir}/opt/github-desktop"
|
||||
cp -a "${srcdir}/github-desktop/." "${pkgdir}/opt/github-desktop/"
|
||||
chmod 0755 "${pkgdir}/opt/github-desktop/desktop"
|
||||
if [[ -f "${pkgdir}/opt/github-desktop/chrome-sandbox" ]]; then
|
||||
chmod 0755 "${pkgdir}/opt/github-desktop/chrome-sandbox"
|
||||
fi
|
||||
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
ln -sfn /opt/github-desktop/desktop "${pkgdir}/usr/bin/github-desktop"
|
||||
|
||||
install -Dm644 github-desktop.svg \
|
||||
"${pkgdir}/usr/share/icons/hicolor/scalable/apps/github-desktop.svg"
|
||||
install -Dm644 /dev/stdin \
|
||||
"${pkgdir}/usr/share/applications/github-desktop.desktop" <<'EOF'
|
||||
[Desktop Entry]
|
||||
Name=GitHub Desktop
|
||||
Comment=Simple collaboration from your desktop
|
||||
Comment[ru]=Графический клиент для работы с GitHub
|
||||
Exec=/usr/bin/github-desktop --protocol-launcher %U
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=github-desktop
|
||||
Categories=Development;RevisionControl;
|
||||
MimeType=x-scheme-handler/x-github-desktop-auth;x-scheme-handler/x-github-client;x-scheme-handler/github-mac;
|
||||
StartupWMClass=desktop
|
||||
EOF
|
||||
|
||||
install-license LICENSE "github-desktop/LICENSE"
|
||||
install -Dm644 "${srcdir}/github-desktop/LICENSE" \
|
||||
"${pkgdir}/usr/share/licenses/github-desktop/LICENSE.electron.txt"
|
||||
install -Dm644 "${srcdir}/github-desktop/LICENSES.chromium.html" \
|
||||
"${pkgdir}/usr/share/licenses/github-desktop/LICENSES.chromium.html"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find "/opt/github-desktop/**/*"
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-license
|
||||
files-find "/usr/share/icons/hicolor/*/apps/github-desktop.svg"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
sandbox=/opt/github-desktop/chrome-sandbox
|
||||
if [[ -f "$sandbox" ]]; then
|
||||
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
|
||||
fi
|
||||
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database -q /usr/share/applications || true
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
gtk-update-icon-cache -f -q /usr/share/icons/hicolor || true
|
||||
fi
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database -q /usr/share/applications || true
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
gtk-update-icon-cache -f -q /usr/share/icons/hicolor || true
|
||||
fi
|
||||
@@ -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")"
|
||||
@@ -0,0 +1,12 @@
|
||||
Сведения о лицензии пакета Happ
|
||||
|
||||
Лицензия: Custom.
|
||||
Официальный сайт: https://happ.su/
|
||||
Условия лицензии: https://happ.su/license
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный deb-артефакт Happ Desktop для Linux.
|
||||
- Happ является проприетарным приложением FlyFrog LLC.
|
||||
- В составе приложения могут использоваться проприетарные компоненты и сетевые функции, связанные с xray-core/sing-box-сценариями.
|
||||
- Использование клиента, подписок и подключений регулируется условиями Happ и провайдера сервиса.
|
||||
- Пакет помечен как несвободный через `nonfree=1`.
|
||||
@@ -0,0 +1,336 @@
|
||||
name='happ'
|
||||
version=3.3.6
|
||||
release=4
|
||||
summary='User-friendly GUI proxy client for xray-core'
|
||||
summary_ru='Удобный GUI-прокси-клиент для xray-core'
|
||||
group='Networking/Other'
|
||||
desc='Happ is a GUI client for xray-core with TUN/VPN mode via sing-box, anti-censorship tools, QR code import and subscription management.'
|
||||
desc_ru='Happ — GUI-клиент для xray-core с режимом TUN/VPN через sing-box, средствами обхода блокировок, импортом QR-кодов и управлением подписками.'
|
||||
homepage='https://happ.su/'
|
||||
maintainer='FlyFrog LLC <support@happ.su>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('Custom')
|
||||
nonfree=1
|
||||
nonfree_msg='Happ является проприетарным приложением FlyFrog LLC. Использование клиента, подписок и сетевых функций регулируется условиями Happ.'
|
||||
nonfree_url='https://happ.su/license'
|
||||
|
||||
provides=()
|
||||
replaces=('happ')
|
||||
conflicts=()
|
||||
|
||||
# auto_req отключен — Qt-приложение поставляется с bundled библиотеками
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('libc6' 'libssl3 | libssl3t64')
|
||||
deps_ubuntu=('libc6' 'libssl3 | libssl3t64')
|
||||
deps_fedora=('glibc' 'openssl-libs')
|
||||
deps_arch=('glibc' 'openssl')
|
||||
deps_opensuse=('glibc' 'libopenssl1_1')
|
||||
deps_altlinux=('glibc' 'libssl3')
|
||||
|
||||
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')
|
||||
|
||||
sources=(
|
||||
"https://github.com/Happ-proxy/happ-desktop/releases/download/${version}/Happ.linux.x64.deb?~archive=false&~name=happ.deb"
|
||||
'local:///patch-tray-icons.py'
|
||||
'local:///happ-launcher'
|
||||
'local:///tray-idle.svg'
|
||||
'local:///tray-connected.svg'
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://github.com/Happ-proxy/happ-desktop/releases/download/${version}/Happ.linux.arm64.deb?~archive=false&~name=happ.deb"
|
||||
'local:///patch-tray-icons.py'
|
||||
'local:///happ-launcher'
|
||||
'local:///tray-idle.svg'
|
||||
'local:///tray-connected.svg'
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'a7dac51277387bfe1049b1ad40f40f2e74af233a5eab020b5be1a622effc46a4'
|
||||
'5b0106dda286b039d6bdce45208f44ea3eb7a2deb4e00455190ce7a07177dde0'
|
||||
'fa4d741a4e2df82b0a19291e2c7bad4a8753a1e59237b2bfe5fef7de8c22efd2'
|
||||
'af47a069497a8063b9028549dc6db111776589818c6165c7a46cb8b25a822ead'
|
||||
'7e105f63e4535676e15e4d613426d6af843376a09fd25674bbbf367778668ae5'
|
||||
'5f0fbc1b0b0e8399c8a04425512168e3dd87960381e85a7cb62a41d55b054b0f'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'a4d3d6dcab1db61db23cdf0f86bce736014887262b570927befe48cb49f14aa6'
|
||||
'5b0106dda286b039d6bdce45208f44ea3eb7a2deb4e00455190ce7a07177dde0'
|
||||
'fa4d741a4e2df82b0a19291e2c7bad4a8753a1e59237b2bfe5fef7de8c22efd2'
|
||||
'af47a069497a8063b9028549dc6db111776589818c6165c7a46cb8b25a822ead'
|
||||
'7e105f63e4535676e15e4d613426d6af843376a09fd25674bbbf367778668ae5'
|
||||
'5f0fbc1b0b0e8399c8a04425512168e3dd87960381e85a7cb62a41d55b054b0f'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/happ-extracted"
|
||||
ar x happ.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/happ-extracted"
|
||||
|
||||
cp -a "${srcdir}/happ-extracted/opt" "${pkgdir}/opt"
|
||||
python3 patch-tray-icons.py \
|
||||
"${pkgdir}/opt/happ/bin/Happ" \
|
||||
tray-idle.svg \
|
||||
tray-connected.svg
|
||||
|
||||
install-systemd "${srcdir}/happ-extracted/etc/systemd/system/happd.service"
|
||||
|
||||
sed -i 's|^Exec=/opt/happ/bin/Happ|Exec=/usr/bin/happ|' \
|
||||
"${srcdir}/happ-extracted/usr/share/applications/Happ.desktop"
|
||||
install-desktop "${srcdir}/happ-extracted/usr/share/applications/Happ.desktop"
|
||||
|
||||
python3 - \
|
||||
"${srcdir}/happ-extracted/usr/share/icons/hicolor/256x256/apps/happ.png" \
|
||||
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/happ.png" <<'PY'
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
PNG_SIG = b"\x89PNG\r\n\x1a\n"
|
||||
|
||||
|
||||
def read_png(path):
|
||||
data = Path(path).read_bytes()
|
||||
if not data.startswith(PNG_SIG):
|
||||
raise ValueError("not a PNG")
|
||||
|
||||
pos = len(PNG_SIG)
|
||||
width = height = bit_depth = color_type = None
|
||||
idat = bytearray()
|
||||
while pos < len(data):
|
||||
length = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
chunk_type = data[pos:pos + 4]
|
||||
pos += 4
|
||||
chunk = data[pos:pos + length]
|
||||
pos += length + 4
|
||||
if chunk_type == b"IHDR":
|
||||
width, height, bit_depth, color_type, _, _, _ = struct.unpack(
|
||||
">IIBBBBB", chunk
|
||||
)
|
||||
elif chunk_type == b"IDAT":
|
||||
idat.extend(chunk)
|
||||
elif chunk_type == b"IEND":
|
||||
break
|
||||
|
||||
if bit_depth != 8 or color_type not in (2, 6):
|
||||
raise ValueError("unsupported PNG format")
|
||||
|
||||
channels = 4 if color_type == 6 else 3
|
||||
raw = zlib.decompress(bytes(idat))
|
||||
stride = width * channels
|
||||
rows = []
|
||||
prev = [0] * stride
|
||||
index = 0
|
||||
|
||||
for _ in range(height):
|
||||
filter_type = raw[index]
|
||||
index += 1
|
||||
encoded = list(raw[index:index + stride])
|
||||
index += stride
|
||||
row = [0] * stride
|
||||
|
||||
for x, value in enumerate(encoded):
|
||||
left = row[x - channels] if x >= channels else 0
|
||||
up = prev[x]
|
||||
upper_left = prev[x - channels] if x >= channels else 0
|
||||
|
||||
if filter_type == 0:
|
||||
decoded = value
|
||||
elif filter_type == 1:
|
||||
decoded = (value + left) & 255
|
||||
elif filter_type == 2:
|
||||
decoded = (value + up) & 255
|
||||
elif filter_type == 3:
|
||||
decoded = (value + ((left + up) // 2)) & 255
|
||||
elif filter_type == 4:
|
||||
predictor = left + up - upper_left
|
||||
distances = (
|
||||
abs(predictor - left),
|
||||
abs(predictor - up),
|
||||
abs(predictor - upper_left),
|
||||
)
|
||||
decoded = (value + (left, up, upper_left)[distances.index(min(distances))]) & 255
|
||||
else:
|
||||
raise ValueError("unsupported PNG filter")
|
||||
|
||||
row[x] = decoded
|
||||
|
||||
prev = row
|
||||
rows.append(row)
|
||||
|
||||
pixels = []
|
||||
for row in rows:
|
||||
out_row = []
|
||||
for x in range(width):
|
||||
offset = x * channels
|
||||
if channels == 4:
|
||||
out_row.append(tuple(row[offset:offset + 4]))
|
||||
else:
|
||||
out_row.append((row[offset], row[offset + 1], row[offset + 2], 255))
|
||||
pixels.append(out_row)
|
||||
|
||||
return width, height, pixels
|
||||
|
||||
|
||||
def write_png(path, width, height, pixels):
|
||||
def chunk(chunk_type, payload):
|
||||
crc = zlib.crc32(chunk_type + payload) & 0xFFFFFFFF
|
||||
return struct.pack(">I", len(payload)) + chunk_type + payload + struct.pack(">I", crc)
|
||||
|
||||
raw = bytearray()
|
||||
for y in range(height):
|
||||
raw.append(0)
|
||||
for x in range(width):
|
||||
raw.extend(bytes(pixels[y][x]))
|
||||
|
||||
ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(path).write_bytes(
|
||||
PNG_SIG + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9))
|
||||
+ chunk(b"IEND", b"")
|
||||
)
|
||||
|
||||
|
||||
def rounded_rect_coverage(px, py, size, margin, radius, samples=4):
|
||||
covered = 0
|
||||
left = top = margin
|
||||
right = bottom = size - margin
|
||||
|
||||
for sy in range(samples):
|
||||
for sx in range(samples):
|
||||
x = px + (sx + 0.5) / samples
|
||||
y = py + (sy + 0.5) / samples
|
||||
if x < left or x > right or y < top or y > bottom:
|
||||
continue
|
||||
cx = min(max(x, left + radius), right - radius)
|
||||
cy = min(max(y, top + radius), bottom - radius)
|
||||
if (x - cx) ** 2 + (y - cy) ** 2 <= radius ** 2:
|
||||
covered += 1
|
||||
|
||||
return covered / (samples * samples)
|
||||
|
||||
|
||||
def sample_alpha(alpha, x, y):
|
||||
height = len(alpha)
|
||||
width = len(alpha[0])
|
||||
if x < 0 or y < 0 or x > width - 1 or y > height - 1:
|
||||
return 0.0
|
||||
|
||||
x0 = int(math.floor(x))
|
||||
y0 = int(math.floor(y))
|
||||
x1 = min(x0 + 1, width - 1)
|
||||
y1 = min(y0 + 1, height - 1)
|
||||
dx = x - x0
|
||||
dy = y - y0
|
||||
|
||||
top = alpha[y0][x0] * (1 - dx) + alpha[y0][x1] * dx
|
||||
bottom = alpha[y1][x0] * (1 - dx) + alpha[y1][x1] * dx
|
||||
return top * (1 - dy) + bottom * dy
|
||||
|
||||
|
||||
src_path, dest_path = sys.argv[1], sys.argv[2]
|
||||
src_width, src_height, source = read_png(src_path)
|
||||
background = 43
|
||||
|
||||
logo_alpha = []
|
||||
for y in range(src_height):
|
||||
row = []
|
||||
for x in range(src_width):
|
||||
r, g, b, a = source[y][x]
|
||||
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
row.append(max(0.0, min(1.0, (luminance - background) / (255 - background))) * (a / 255.0))
|
||||
logo_alpha.append(row)
|
||||
|
||||
xs = [x for y in range(src_height) for x in range(src_width) if logo_alpha[y][x] > 0.04]
|
||||
ys = [y for y in range(src_height) for x in range(src_width) if logo_alpha[y][x] > 0.04]
|
||||
left, right = min(xs), max(xs)
|
||||
top, bottom = min(ys), max(ys)
|
||||
crop_width = right - left + 1
|
||||
crop_height = bottom - top + 1
|
||||
cropped = [
|
||||
[logo_alpha[y][x] for x in range(left, right + 1)]
|
||||
for y in range(top, bottom + 1)
|
||||
]
|
||||
|
||||
size = 512
|
||||
margin = 38
|
||||
radius = 116
|
||||
logo_height = 292
|
||||
logo_width = round(crop_width * logo_height / crop_height)
|
||||
logo_x = (size - logo_width) // 2
|
||||
logo_y = (size - logo_height) // 2 + 6
|
||||
|
||||
pixels = [[(0, 0, 0, 0) for _ in range(size)] for _ in range(size)]
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
coverage = rounded_rect_coverage(x, y, size, margin, radius)
|
||||
if coverage:
|
||||
pixels[y][x] = (43, 43, 43, round(255 * coverage))
|
||||
|
||||
for y in range(logo_height):
|
||||
source_y = (y + 0.5) * crop_height / logo_height - 0.5
|
||||
target_y = logo_y + y
|
||||
if target_y < 0 or target_y >= size:
|
||||
continue
|
||||
|
||||
for x in range(logo_width):
|
||||
source_x = (x + 0.5) * crop_width / logo_width - 0.5
|
||||
target_x = logo_x + x
|
||||
if target_x < 0 or target_x >= size:
|
||||
continue
|
||||
|
||||
alpha = sample_alpha(cropped, source_x, source_y)
|
||||
if alpha <= 0:
|
||||
continue
|
||||
|
||||
dr, dg, db, da = pixels[target_y][target_x]
|
||||
da_float = da / 255.0
|
||||
out_alpha = alpha + da_float * (1 - alpha)
|
||||
out_rgb = round((255 * alpha + dr * da_float * (1 - alpha)) / out_alpha)
|
||||
pixels[target_y][target_x] = (out_rgb, out_rgb, out_rgb, round(out_alpha * 255))
|
||||
|
||||
write_png(dest_path, size, size, pixels)
|
||||
PY
|
||||
|
||||
install -Dm644 "${srcdir}/happ-extracted/usr/share/mime/packages/happ-mime.xml" \
|
||||
"${pkgdir}/usr/share/mime/packages/happ-mime.xml"
|
||||
|
||||
install -Dm755 "${srcdir}/happ-launcher" "${pkgdir}/usr/bin/happ"
|
||||
install-license "${srcdir}/LICENSE" "happ/LICENSE.nivora"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find "/opt/happ/**/*"
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-systemd
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/share/icons/hicolor/512x512/apps/happ.png" \
|
||||
"/usr/share/mime/packages/happ-mime.xml"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Happ's bundled Qt falls back to the generic GNOME platform theme, which
|
||||
# reports Qt::ColorScheme::Unknown. The bundled portal plugin follows the
|
||||
# desktop color-scheme setting and emits updates when it changes.
|
||||
QT_QPA_PLATFORMTHEME="${QT_QPA_PLATFORMTHEME:-xdgdesktopportal}"
|
||||
export QT_QPA_PLATFORMTHEME
|
||||
|
||||
exec /opt/happ/bin/Happ "$@"
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
UPSTREAM_RESOURCES = {
|
||||
"idle": (
|
||||
"c0939b3a81a59983f0db07cdfa08eae90d6ddc211b7b790c425beb72628355ea",
|
||||
b'<svg width="64" height="64"',
|
||||
),
|
||||
"connected": (
|
||||
"5e1703683c6263b5a2cdda83b034dec23fe31e72497f6644a3853b97c6fdef85",
|
||||
b'<svg width="15" height="15"',
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def replace_resource(
|
||||
binary: bytearray,
|
||||
kind: str,
|
||||
replacement: bytes,
|
||||
expected_start: int | None = None,
|
||||
) -> tuple[int, int]:
|
||||
expected_hash, marker = UPSTREAM_RESOURCES[kind]
|
||||
matches: list[tuple[int, int]] = []
|
||||
offset = 0
|
||||
|
||||
while True:
|
||||
start = binary.find(marker, offset)
|
||||
if start < 0:
|
||||
break
|
||||
offset = start + 1
|
||||
if start < 4:
|
||||
continue
|
||||
|
||||
length = struct.unpack_from(">I", binary, start - 4)[0]
|
||||
if length <= 0 or start + length > len(binary):
|
||||
continue
|
||||
payload = bytes(binary[start:start + length])
|
||||
if (
|
||||
hashlib.sha256(payload).hexdigest() == expected_hash
|
||||
and (expected_start is None or start == expected_start)
|
||||
):
|
||||
matches.append((start, length))
|
||||
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError(
|
||||
f"expected exactly one upstream {kind} tray resource, found {len(matches)}"
|
||||
)
|
||||
|
||||
start, capacity = matches[0]
|
||||
if len(replacement) > capacity:
|
||||
raise RuntimeError(
|
||||
f"{kind} tray replacement is too large: {len(replacement)} > {capacity}"
|
||||
)
|
||||
|
||||
struct.pack_into(">I", binary, start - 4, len(replacement))
|
||||
binary[start:start + len(replacement)] = replacement
|
||||
binary[start + len(replacement):start + capacity] = b"\0" * (
|
||||
capacity - len(replacement)
|
||||
)
|
||||
return start, capacity
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 4:
|
||||
print(
|
||||
"usage: patch-tray-icons.py <Happ binary> <idle.svg> <connected.svg>",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
binary_path = Path(sys.argv[1])
|
||||
binary = bytearray(binary_path.read_bytes())
|
||||
connected_start, connected_capacity = replace_resource(
|
||||
binary, "connected", Path(sys.argv[3]).read_bytes()
|
||||
)
|
||||
replace_resource(
|
||||
binary,
|
||||
"idle",
|
||||
Path(sys.argv[2]).read_bytes(),
|
||||
connected_start + connected_capacity + 4,
|
||||
)
|
||||
binary_path.write_bytes(binary)
|
||||
print("Patched Happ tray icons")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Обновляем MIME-базу данных
|
||||
if command -v update-mime-database &>/dev/null; then
|
||||
optional_refresh "MIME-базу" update-mime-database /usr/share/mime
|
||||
fi
|
||||
|
||||
# Обновляем desktop базу данных
|
||||
if command -v update-desktop-database &>/dev/null; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
|
||||
# Обновляем кэш иконок
|
||||
if command -v gtk-update-icon-cache &>/dev/null; then
|
||||
optional_refresh "кэш иконок" gtk-update-icon-cache -f -q /usr/share/icons/hicolor
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now happd.service
|
||||
fi
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
unit=/usr/lib/systemd/system/happd.service
|
||||
if [[ ! -e "$unit" ]] && command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
if systemctl is-active --quiet happd.service; then
|
||||
systemctl stop happd.service
|
||||
fi
|
||||
if systemctl is-enabled --quiet happd.service; then
|
||||
systemctl disable happd.service
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
# Обновляем MIME-базу данных
|
||||
if command -v update-mime-database &>/dev/null; then
|
||||
optional_refresh "MIME-базу" update-mime-database /usr/share/mime
|
||||
fi
|
||||
|
||||
# Обновляем desktop базу данных
|
||||
if command -v update-desktop-database &>/dev/null; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
|
||||
# Обновляем кэш иконок
|
||||
if command -v gtk-update-icon-cache &>/dev/null; then
|
||||
optional_refresh "кэш иконок" gtk-update-icon-cache -f -q /usr/share/icons/hicolor
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
[inherit]
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><linearGradient id="g" x1="9" y1="7" x2="55" y2="57" gradientUnits="userSpaceOnUse"><stop stop-color="#35432E"/><stop offset="1" stop-color="#1D261A"/></linearGradient></defs>
|
||||
<rect x="3" y="3" width="58" height="58" rx="16" fill="url(#g)" stroke="#A8FF5D" stroke-width="2.5"/>
|
||||
<path d="M27.7 28.3l2.2-12.8L18 27.3l-3.7 21.9 11.9-11.8.3-1.8H28l7.4-7.3h-7.7ZM46 37.3l3.7-21.8-11.9 11.8-.2 1h-.8l-7.4 7.3h7l-2.3 13.6L46 37.3ZM36.1 49.2l-.7 4.4h9.9L48 37.4 36.1 49.2ZM28.4 15.3l.8-4.9h-10l-2.8 16.7 12-11.8Z" fill="#FAFAFB"/>
|
||||
<path d="M28.1 37.4 16.2 49.2l-.8 4.4h10l2.7-16.2ZM36.2 27.1l11.9-11.8.8-4.9H39l-2.8 16.7Z" fill="#A8FF5D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 737 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><linearGradient id="g" x1="9" y1="7" x2="55" y2="57" gradientUnits="userSpaceOnUse"><stop stop-color="#3B3D44"/><stop offset="1" stop-color="#202126"/></linearGradient></defs>
|
||||
<rect x="3" y="3" width="58" height="58" rx="16" fill="url(#g)" stroke="#62656E" stroke-width="2"/>
|
||||
<path d="M28.1 37.4 16.2 49.2l-.8 4.4h10l2.7-16.2ZM27.7 28.3l2.2-12.8L18 27.3l-3.7 21.9 11.9-11.8.3-1.8H28l7.4-7.3h-7.7ZM46 37.3l3.7-21.8-11.9 11.8-.2 1h-.8l-7.4 7.3h7l-2.3 13.6L46 37.3ZM36.1 49.2l-.7 4.4h9.9L48 37.4 36.1 49.2ZM28.4 15.3l.8-4.9h-10l-2.8 16.7 12-11.8ZM36.2 27.1l11.9-11.8.8-4.9H39l-2.8 16.7Z" fill="#FAFAFB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
@@ -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")"
|
||||
@@ -0,0 +1,16 @@
|
||||
Сведения о лицензии пакета NetBird
|
||||
|
||||
Лицензии: BSD-3-Clause и AGPL-3.0-only.
|
||||
Исходный проект: https://github.com/netbirdio/netbird
|
||||
Официальный сайт: https://netbird.io/
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный deb-артефакт NetBird CLI/daemon для Linux.
|
||||
- Основные части репозитория NetBird распространяются под BSD-3-Clause.
|
||||
- Отдельные серверные компоненты исходного репозитория, включая management, signal, relay и combined, лицензированы под AGPL-3.0.
|
||||
- Данный пакет устанавливает клиентский бинарник и systemd-сервис; серверная инфраструктура NetBird в пакет не входит.
|
||||
- Подключение к облачному или собственному management-серверу регулируется условиями соответствующего сервиса.
|
||||
|
||||
Официальные тексты лицензий:
|
||||
- https://opensource.org/license/bsd-3-clause
|
||||
- https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
@@ -0,0 +1,90 @@
|
||||
name='netbird'
|
||||
version=0.75.0
|
||||
release=1
|
||||
summary='WireGuard-based mesh VPN client'
|
||||
summary_ru='Mesh VPN-клиент на базе WireGuard'
|
||||
group='Networking/Other'
|
||||
desc='NetBird is a WireGuard-based mesh VPN client with SSO, MFA and policy-based access control for connecting devices and private networks.'
|
||||
desc_ru='NetBird — mesh VPN-клиент на базе WireGuard с SSO, MFA и политиками доступа для соединения устройств и приватных сетей.'
|
||||
homepage='https://netbird.io/'
|
||||
maintainer='NetBird GmbH <dev@netbird.io>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('BSD-3-Clause' 'AGPL-3.0-only')
|
||||
|
||||
provides=()
|
||||
replaces=('netbird')
|
||||
conflicts=()
|
||||
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('ca-certificates' 'iproute2' 'iptables')
|
||||
deps_ubuntu=("${deps_debian[@]}")
|
||||
deps_fedora=('ca-certificates' 'iproute' 'iptables')
|
||||
deps_arch=('ca-certificates' 'iproute2' 'iptables')
|
||||
deps_opensuse=('ca-certificates' 'iproute2' 'iptables')
|
||||
deps_altlinux=('ca-certificates' 'iproute2' 'iptables')
|
||||
|
||||
build_deps=('binutils')
|
||||
|
||||
sources=(
|
||||
"https://github.com/netbirdio/netbird/releases/download/v${version}/netbird_${version}_linux_amd64.deb?~archive=false&~name=netbird.deb"
|
||||
"local:///netbird.png"
|
||||
"local:///netbird.service"
|
||||
"local:///netbird-purge-data"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://github.com/netbirdio/netbird/releases/download/v${version}/netbird_${version}_linux_arm64.deb?~archive=false&~name=netbird.deb"
|
||||
"local:///netbird.png"
|
||||
"local:///netbird.service"
|
||||
"local:///netbird-purge-data"
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'sha256:b65f65d7f32831639ebdd44c241e7a2310287723b64dbc03c93292853042ee6f'
|
||||
'sha256:71b133636e91eb67f221f64592f64fa04a3b2337f4d616be36492efae0cff643'
|
||||
'sha256:abd8064281462a4ce12c2fdf2e9436d9ae6932ff3b366668a175f2a556169012'
|
||||
'sha256:bb5c44ab330db0d63a82b582f2c508681a6922ea65f3bf2becaeed87693215b1'
|
||||
'sha256:11555e8308e949c3965a681f48b83f7f2488cff03b09772d8af4a20d376464e7'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'sha256:9e57e854da0bbaf392cc6f99e37db75809e21c3effde0fca9f3bc9dc90245c02'
|
||||
'sha256:71b133636e91eb67f221f64592f64fa04a3b2337f4d616be36492efae0cff643'
|
||||
'sha256:abd8064281462a4ce12c2fdf2e9436d9ae6932ff3b366668a175f2a556169012'
|
||||
'sha256:bb5c44ab330db0d63a82b582f2c508681a6922ea65f3bf2becaeed87693215b1'
|
||||
'sha256:11555e8308e949c3965a681f48b83f7f2488cff03b09772d8af4a20d376464e7'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/extracted"
|
||||
ar x netbird.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/extracted"
|
||||
|
||||
install-binary "${srcdir}/extracted/usr/bin/netbird"
|
||||
install-binary "${srcdir}/netbird-purge-data"
|
||||
install-systemd "${srcdir}/netbird.service"
|
||||
install-license "${srcdir}/LICENSE" "netbird/LICENSE.nivora"
|
||||
install -Dm644 "${srcdir}/netbird.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/netbird.png"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find-binary
|
||||
files-find-systemd
|
||||
files-find-license
|
||||
files-find "/usr/share/icons/hicolor/256x256/apps/netbird.png"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" != "--yes" || "$#" -ne 1 ]]; then
|
||||
echo "Использование: sudo netbird-purge-data --yes" >&2
|
||||
echo "Команда отменит регистрацию узла NetBird и безвозвратно удалит локальное состояние." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Ошибка: очистку данных NetBird нужно запускать от root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v netbird >/dev/null 2>&1; then
|
||||
if ! netbird deregister; then
|
||||
echo "Предупреждение: NetBird не подтвердил deregister; локальные данные всё равно будут удалены." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
systemctl stop netbird.service
|
||||
fi
|
||||
|
||||
rm -rf -- /var/lib/netbird /var/cache/netbird /var/log/netbird
|
||||
rm -f -- /run/netbird.sock /var/run/netbird.sock
|
||||
|
||||
echo "Локальные данные NetBird удалены."
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=NetBird mesh network client
|
||||
Documentation=https://docs.netbird.io/
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=-/etc/sysconfig/netbird
|
||||
ExecStart=/usr/bin/netbird service run --log-level info --daemon-addr unix:///run/netbird.sock --log-file /var/log/netbird/client.log
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
StateDirectory=netbird
|
||||
StateDirectoryMode=0700
|
||||
LogsDirectory=netbird
|
||||
LogsDirectoryMode=0750
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
legacy_unit=/etc/systemd/system/netbird.service
|
||||
if [[ -f "$legacy_unit" ]] && grep -Fq 'ExecStart=/usr/bin/netbird "service" "run"' "$legacy_unit"; then
|
||||
rm -f -- "$legacy_unit"
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now netbird.service
|
||||
fi
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
unit=/usr/lib/systemd/system/netbird.service
|
||||
|
||||
if [[ ! -e "$unit" ]] && command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
if systemctl is-active --quiet netbird.service; then
|
||||
systemctl stop netbird.service
|
||||
fi
|
||||
if systemctl is-enabled --quiet netbird.service; then
|
||||
systemctl disable netbird.service
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
include = "../stapler-repo.toml"
|
||||
@@ -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")"
|
||||