feat: create autonomous Nivora package repository

Maintain a validated cross-distribution package catalog.
Automate upstream updates, isolated builds, diagnostics, and direct publication.
Build the official GitHub Desktop sources for Linux with working OAuth.
This commit is contained in:
Александр
2026-07-25 00:49:32 +10:00
commit f405ae2bcf
168 changed files with 8483 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -euo pipefail
package_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
repo_root="$(cd "${package_dir}/.." && pwd)"
exec "${repo_root}/tools/package_updates.sh" check "$(basename "$package_dir")"
+21
View File
@@ -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
+176
View File
@@ -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"
}
+211
View File
@@ -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())
Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

+139
View File
@@ -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");
+37
View File
@@ -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
+25
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
[inherit]