mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-05 20:32:15 +00:00
fix(monitor): avoid foreign Borg binary in ARM builds and smoke-test launcher
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
"""Reject foreign ELF files in a native AppDir without executing any binary."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import struct
|
||||
|
||||
|
||||
def check(directory, architecture):
|
||||
expected = {'x86_64': 62, 'aarch64': 183}[architecture]
|
||||
checked = 0
|
||||
for path in Path(directory).rglob('*'):
|
||||
if path.is_symlink() or not path.is_file():
|
||||
continue
|
||||
with path.open('rb') as stream:
|
||||
header = stream.read(20)
|
||||
if not header.startswith(b'\x7fELF'):
|
||||
continue
|
||||
if len(header) != 20 or header[5] not in (1, 2):
|
||||
raise ValueError(f'Invalid ELF header: {path}')
|
||||
machine = struct.unpack('<H' if header[5] == 1 else '>H', header[18:20])[0]
|
||||
if machine != expected:
|
||||
raise ValueError(f'Foreign ELF machine {machine} in {architecture} AppDir: {path}')
|
||||
checked += 1
|
||||
if not checked:
|
||||
raise ValueError('No native ELF files found in AppDir')
|
||||
return checked
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('directory', type=Path)
|
||||
parser.add_argument('architecture', choices=('x86_64', 'aarch64'))
|
||||
args = parser.parse_args()
|
||||
print(f'PASS: {check(args.directory, args.architecture)} native ELF files')
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# Exercise the extracted launcher and its real native Python environment,
|
||||
# intercepting only the final server exec so no host-monitoring server starts.
|
||||
set -eu
|
||||
appdir="$1"
|
||||
export PMX_SMOKE_PYTHON
|
||||
PMX_SMOKE_PYTHON="$(command -v python)"
|
||||
fixture_bin="$(mktemp -d)"
|
||||
trap 'rm -rf "$fixture_bin"' EXIT
|
||||
cat > "$fixture_bin/python3" <<'WRAPPER'
|
||||
#!/bin/sh
|
||||
case "$1" in
|
||||
*/flask_server.py)
|
||||
exec "$PMX_SMOKE_PYTHON" -c 'import gevent, greenlet, geventwebsocket, zope.interface; from PIL import Image; from gevent import pywsgi; print("PASS: extracted AppRun native runtime")'
|
||||
;;
|
||||
*) exec "$PMX_SMOKE_PYTHON" "$@" ;;
|
||||
esac
|
||||
WRAPPER
|
||||
chmod +x "$fixture_bin/python3"
|
||||
PATH="$fixture_bin:$PATH" bash "$appdir/AppRun"
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
- '.github/workflows/test-appimage-native.yml'
|
||||
- 'AppImage/scripts/build_appimage.sh'
|
||||
- 'AppImage/scripts/appimage_arch.sh'
|
||||
- 'AppImage/scripts/borg_native_arm.sh'
|
||||
- '.github/scripts/smoke_native_apprun.sh'
|
||||
- '.github/scripts/check_appimage_elf.py'
|
||||
- 'AppImage/scripts/AppRun'
|
||||
- 'tests/test_appimage_arch.py'
|
||||
workflow_dispatch:
|
||||
@@ -45,6 +48,8 @@ jobs:
|
||||
run: sudo apt-get update
|
||||
- name: Build native AppImage without publishing it
|
||||
run: bash AppImage/scripts/build_appimage.sh
|
||||
- name: Reject foreign ELF payloads
|
||||
run: python .github/scripts/check_appimage_elf.py /tmp/proxmenux_build/ProxMenux.AppDir '${{ matrix.arch }}'
|
||||
- name: Extract native AppImage without FUSE
|
||||
run: |
|
||||
mkdir /tmp/proxmenux-native-extract
|
||||
@@ -59,11 +64,15 @@ jobs:
|
||||
- name: Import bundled Python 3.11 native WebSocket runtime
|
||||
env:
|
||||
PYTHONPATH: /tmp/proxmenux-native-extract/squashfs-root/usr/lib/python3/vendor/cp311:/tmp/proxmenux-native-extract/squashfs-root/usr/lib/python3/dist-packages
|
||||
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from PIL import Image; from gevent import pywsgi; print(gevent.__version__)'
|
||||
run: |
|
||||
python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from PIL import Image; from gevent import pywsgi; print(gevent.__version__)'
|
||||
bash .github/scripts/smoke_native_apprun.sh /tmp/proxmenux-native-extract/squashfs-root
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
- name: Import bundled Python 3.13 native WebSocket runtime
|
||||
env:
|
||||
PYTHONPATH: /tmp/proxmenux-native-extract/squashfs-root/usr/lib/python3/vendor/cp313:/tmp/proxmenux-native-extract/squashfs-root/usr/lib/python3/dist-packages
|
||||
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from PIL import Image; from gevent import pywsgi; print(gevent.__version__)'
|
||||
run: |
|
||||
python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from PIL import Image; from gevent import pywsgi; print(gevent.__version__)'
|
||||
bash .github/scripts/smoke_native_apprun.sh /tmp/proxmenux-native-extract/squashfs-root
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
# Borg 1.2.8 has no official ARM64 standalone release. Never ship or run
|
||||
# its AMD64 executable on ARM; use the host's native distro package instead.
|
||||
if [ -x /usr/bin/borg ]; then
|
||||
exec /usr/bin/borg "$@"
|
||||
fi
|
||||
echo 'Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.' >&2
|
||||
exit 127
|
||||
@@ -37,6 +37,14 @@ echo "🚀 Building ProxMenux Monitor AppImage v${VERSION} with hardware monitor
|
||||
# denied → build aborted before doing any actual work).
|
||||
APPIMAGETOOL_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/proxmenux-build/appimagetool-${PMX_APPIMAGE_ARCH}"
|
||||
|
||||
APPIMAGETOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${PMX_APPIMAGE_ARCH}.AppImage"
|
||||
if [ "$PMX_APPIMAGE_ARCH" = aarch64 ]; then
|
||||
# The old AppImageKit ARM build does not accept ARCH=aarch64 reliably.
|
||||
# Use a versioned tool; leave the existing AMD64 tool unchanged.
|
||||
APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-aarch64.AppImage"
|
||||
APPIMAGETOOL_CACHE="${APPIMAGETOOL_CACHE}-1.9.1"
|
||||
fi
|
||||
|
||||
# Architecture-specific caches never reuse an executable from a different CPU.
|
||||
|
||||
# Clean and create work directory
|
||||
@@ -52,7 +60,7 @@ if [ -s "$APPIMAGETOOL_CACHE" ]; then
|
||||
fi
|
||||
if [ ! -s "$WORK_DIR/appimagetool" ]; then
|
||||
echo "📥 Downloading appimagetool..."
|
||||
wget --tries=3 --timeout=60 "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${PMX_APPIMAGE_ARCH}.AppImage" -O "$WORK_DIR/appimagetool" || true
|
||||
wget --tries=3 --timeout=60 "$APPIMAGETOOL_URL" -O "$WORK_DIR/appimagetool" || true
|
||||
if [ ! -s "$WORK_DIR/appimagetool" ]; then
|
||||
echo "❌ Failed to download appimagetool" >&2
|
||||
exit 1
|
||||
@@ -62,6 +70,10 @@ if [ ! -s "$WORK_DIR/appimagetool" ]; then
|
||||
cp -f "$WORK_DIR/appimagetool" "$APPIMAGETOOL_CACHE"
|
||||
fi
|
||||
|
||||
if [ "$PMX_APPIMAGE_ARCH" = aarch64 ]; then
|
||||
echo "f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158 $WORK_DIR/appimagetool" | sha256sum -c -
|
||||
fi
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p "$APP_DIR/usr/bin"
|
||||
mkdir -p "$APP_DIR/usr/lib/python3/dist-packages"
|
||||
@@ -240,6 +252,7 @@ fi
|
||||
# hb_ensure_borg used to download on demand — kept in lockstep so both
|
||||
# code paths see the same version semantics. SHA256 is the upstream
|
||||
# release checksum; bump both together.
|
||||
if [ "$PMX_APPIMAGE_ARCH" = x86_64 ]; then
|
||||
BORG_VERSION="1.2.8"
|
||||
BORG_URL="https://github.com/borgbackup/borg/releases/download/${BORG_VERSION}/borg-linux64"
|
||||
BORG_SHA256="cfa50fb704a93d3a4fa258120966345fddb394f960dca7c47fcb774d0172f40b"
|
||||
@@ -275,6 +288,12 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "⚠️ No official Borg 1.2.8 ARM64 standalone binary; using native-host adapter"
|
||||
cp "$SCRIPT_DIR/borg_native_arm.sh" "$APP_DIR/usr/bin/borg"
|
||||
chmod +x "$APP_DIR/usr/bin/borg"
|
||||
fi
|
||||
|
||||
# Copy Next.js build
|
||||
echo "📋 Copying web dashboard..."
|
||||
if [ -d "$APPIMAGE_ROOT/out" ]; then
|
||||
|
||||
@@ -13,7 +13,8 @@ Python/pip, APT and the usual AppImage build dependencies. AMD64 keeps the exist
|
||||
|
||||
The native validation workflow builds both architectures on GitHub's native
|
||||
Ubuntu 22.04 runners, extracts each AppImage without FUSE, and imports its bundled
|
||||
CPython 3.11 and 3.13 WebSocket/image runtimes. It does not upload artifacts,
|
||||
CPython 3.11 and 3.13 WebSocket/image runtimes, including the extracted launcher
|
||||
with only its final server execution intercepted. It does not upload artifacts,
|
||||
change release assets, or modify an installed Monitor.
|
||||
|
||||
This build lane is a prerequisite for ARM distribution, not a declaration of
|
||||
@@ -21,3 +22,16 @@ PXVIRT or Raspberry Pi hardware compatibility. ARM release/checksum publication,
|
||||
installer selection and validation on the maintainer's PXVIRT lab remain separate
|
||||
steps before ARM installations can use a published image. The current release
|
||||
and installer distribution remain unchanged.
|
||||
|
||||
## Borg host backups on ARM64
|
||||
|
||||
Borg 1.2.8 publishes an AMD64 standalone executable but no ARM64 equivalent.
|
||||
ARM64 builds therefore include a small adapter that uses `/usr/bin/borg` from
|
||||
the host's native `borgbackup` package. Without that package it exits with a
|
||||
clear error; it never runs the AMD64 binary. The host-backup helper likewise
|
||||
refuses AMD64 cached/downloaded binaries on non-AMD64 hosts. The existing AMD64
|
||||
standalone binary and checksum remain unchanged. Operators must install their
|
||||
native distro package separately; this build does not install it on the host.
|
||||
|
||||
ARM64 uses the official versioned appimagetool 1.9.1 with SHA256 verification;
|
||||
the existing AMD64 packaging tool remains unchanged.
|
||||
|
||||
@@ -4396,6 +4396,7 @@
|
||||
"Name or part of the option or of the application": "Name oder Teil der Option oder der Anwendung",
|
||||
"Name:": "Name:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Benannte Konten benötigen einen privaten Modus. Stoppen Sie den Container, setzen Sie public: false in /config/config.js, starten Sie ihn erneut und erstellen Sie jeden Benutzer mit: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "Eine native Borg-Version ist nicht verfügbar. Installiere das native Paket borgbackup auf diesem Host, um Borg-Sicherungen zu verwenden.",
|
||||
"Needed:": "Erforderlich:",
|
||||
"Needs": "Benötigt",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "Weder /etc/kernel/cmdline noch /etc/default/grub gefunden.",
|
||||
|
||||
@@ -4391,6 +4391,7 @@
|
||||
"Name or part of the option or of the application": "Nombre o parte de la opción o de la aplicación",
|
||||
"Name:": "Nombre:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Las cuentas con nombre necesitan el modo privado. Detén el contenedor, pon public: false en /config/config.js, vuelve a arrancarlo y crea cada usuario con: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "Borg nativo no está disponible. Instala el paquete borgbackup nativo en este host para usar las copias de seguridad de Borg.",
|
||||
"Needed:": "Necesario:",
|
||||
"Needs": "Necesita",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "No se encontraron /etc/kernel/cmdline ni /etc/default/grub.",
|
||||
|
||||
@@ -4396,6 +4396,7 @@
|
||||
"Name or part of the option or of the application": "Nom ou partie de l'option ou de l'application",
|
||||
"Name:": "Nom:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Les comptes nommés nécessitent un mode privé. Arrêter le conteneur, définir public: false in /config/config.js, recommencer et créer chaque utilisateur avec: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge ajouter <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "La version native de Borg n’est pas disponible. Installez le paquet natif borgbackup sur cet hôte pour utiliser les sauvegardes Borg.",
|
||||
"Needed:": "Nécessaire :",
|
||||
"Needs": "Nécessite",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "Ni /etc/kernel/cmdline ni /etc/default/grub n'ont été trouvés.",
|
||||
|
||||
@@ -4396,6 +4396,7 @@
|
||||
"Name or part of the option or of the application": "Nome dell'opzione o dell'applicazione, anche parziale",
|
||||
"Name:": "Nome:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Gli account nominativi richiedono la modalità privata. Arresta il container, imposta public: false in /config/config.js, riavvialo e crea ogni utente con: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "La versione nativa di Borg non è disponibile. Installa il pacchetto borgbackup nativo su questo host per utilizzare i backup Borg.",
|
||||
"Needed:": "Spazio richiesto:",
|
||||
"Needs": "Richiede",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "Non sono stati trovati né /etc/kernel/cmdline né /etc/default/grub.",
|
||||
|
||||
@@ -4396,6 +4396,7 @@
|
||||
"Name or part of the option or of the application": "Nome ou parte da opção ou do aplicativo",
|
||||
"Name:": "Nome:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Contas nomeadas precisam de modo privado. Pare o contêiner, defina público: false in /config/config.js, inicie-o novamente e crie cada usuário com: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "A versão nativa do Borg não está disponível. Instale o pacote borgbackup nativo neste host para utilizar as cópias de segurança do Borg.",
|
||||
"Needed:": "Necessário:",
|
||||
"Needs": "Requer",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "Nem /etc/kernel/cmdline nem /etc/default/grub foram encontrados.",
|
||||
|
||||
@@ -4396,6 +4396,7 @@
|
||||
"Name or part of the option or of the application": "Názov alebo časť možnosti alebo aplikácie",
|
||||
"Name:": "Názov:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Používateľské účty vyžadujú súkromný režim. Zastavte kontajner, v /config/config.js nastavte public: false, znova ho spustite a každého používateľa vytvorte príkazom: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "Natívna verzia Borg nie je dostupná. Ak chcete používať zálohy Borg, nainštalujte na tento hostiteľ natívny balík borgbackup.",
|
||||
"Needed:": "Potrebné:",
|
||||
"Needs": "Vyžaduje",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "Nenašiel sa /etc/kernel/cmdline ani /etc/default/grub.",
|
||||
|
||||
@@ -4396,6 +4396,7 @@
|
||||
"Name or part of the option or of the application": "Namn eller del av alternativet eller applikationen",
|
||||
"Name:": "Namn:",
|
||||
"Named accounts need private mode. Stop the container, set public: false in /config/config.js, start it again and create each user with: pct exec <CTID> -- env THELOUNGE_HOME=/config s6-setuidgid abc thelounge add <user>": "Namngivna konton behöver privat läge. Stoppa behållaren, ställ in offentligt: falskt i /config/config.js, starta den igen och skapa varje användare med: pct exec <CTID> - env THELOUNGE_HOME=/config s6-setuidgid abc thelounge addera <user>",
|
||||
"Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.": "En inbyggd version av Borg är inte tillgänglig. Installera paketet borgbackup för värdens arkitektur för att använda Borg-säkerhetskopior.",
|
||||
"Needed:": "Behövs:",
|
||||
"Needs": "Kräver",
|
||||
"Neither /etc/kernel/cmdline nor /etc/default/grub found.": "Varken /etc/kernel/cmdline eller /etc/default/grub hittades.",
|
||||
|
||||
@@ -2021,6 +2021,13 @@ hb_ensure_borg() {
|
||||
# without the new AppImage)
|
||||
command -v borg >/dev/null 2>&1 && { echo "borg"; return 0; }
|
||||
|
||||
# Cached/bundled/downloaded borg-linux64 below is AMD64 only. A native
|
||||
# distro Borg may be used above, but never try a foreign cached executable.
|
||||
if [[ "$(uname -m)" != x86_64 ]]; then
|
||||
msg_error "$(hb_translate "Native Borg is unavailable. Install the native borgbackup package on this host to use Borg backups.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local appimage_cache="$HB_STATE_DIR/borg"
|
||||
[[ -x "$appimage_cache" ]] && { echo "$appimage_cache"; return 0; }
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Exercise native architecture wiring without building or changing the host."""
|
||||
import json
|
||||
import importlib.util
|
||||
import struct
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
@@ -18,6 +19,23 @@ class AppImageArchitectureTests(unittest.TestCase):
|
||||
'printf "%s|%s|%s" "$PMX_APPIMAGE_ARCH" "$PMX_APPIMAGE_TRIPLET" "$PMX_APPIMAGE_SUFFIX"',
|
||||
'fixture', str(SCRIPTS / 'appimage_arch.sh'), machine], capture_output=True, text=True)
|
||||
|
||||
def test_elf_audit_rejects_foreign_binary_without_executing_it(self):
|
||||
spec = importlib.util.spec_from_file_location('appimage_elf_check', ROOT / '.github/scripts/check_appimage_elf.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
binary = Path(folder) / 'fixture-elf'
|
||||
header = bytearray(20)
|
||||
header[:6] = b'\x7fELF\x02\x01'
|
||||
header[18:20] = struct.pack('<H', 183)
|
||||
binary.write_bytes(header)
|
||||
self.assertEqual(module.check(folder, 'aarch64'), 1)
|
||||
with self.assertRaisesRegex(ValueError, 'Foreign ELF machine 183'):
|
||||
module.check(folder, 'x86_64')
|
||||
binary.write_text('#!/bin/sh\nexit 127\n')
|
||||
with self.assertRaisesRegex(ValueError, 'No native ELF files'):
|
||||
module.check(folder, 'aarch64')
|
||||
|
||||
def test_supported_native_architectures_and_aliases(self):
|
||||
for machine in ['x86_64', 'amd64']:
|
||||
self.assertEqual(self.configure(machine).stdout, 'x86_64|x86_64-linux-gnu|')
|
||||
@@ -73,5 +91,39 @@ class AppImageArchitectureTests(unittest.TestCase):
|
||||
self.assertIn(str(app / 'usr/lib/python3/vendor/cp311'), result.stdout)
|
||||
|
||||
|
||||
def test_arm_borg_adapter_reports_unavailable_or_uses_native_host_package(self):
|
||||
source = (SCRIPTS / 'borg_native_arm.sh').read_text()
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
native = Path(folder) / 'borg'
|
||||
adapter = Path(folder) / 'adapter'
|
||||
adapter.write_text(source.replace('/usr/bin/borg', str(native)))
|
||||
missing = subprocess.run(['sh', str(adapter), '--version'], capture_output=True, text=True)
|
||||
self.assertEqual(missing.returncode, 127)
|
||||
self.assertIn('Install the native borgbackup package', missing.stderr)
|
||||
native.write_text('#!/bin/sh\nprintf "native %s" "$*"\n')
|
||||
native.chmod(0o755)
|
||||
installed = subprocess.run(['sh', str(adapter), 'list', 'fixture-repo'], capture_output=True, text=True, check=True)
|
||||
self.assertEqual(installed.stdout, 'native list fixture-repo')
|
||||
|
||||
def test_arm_host_backup_never_selects_cached_amd64_borg_or_downloads_it(self):
|
||||
source = (ROOT / 'scripts/backup_restore/lib_host_backup_common.sh').read_text()
|
||||
function = source[source.index('hb_ensure_borg() {'):source.index('\nhb_borg_init_if_needed()')]
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
cached = Path(folder) / 'borg'
|
||||
cached.write_text('#!/bin/sh\nexit 0\n')
|
||||
cached.chmod(0o755)
|
||||
script = (function + '\n'
|
||||
'command() { if [ "$*" = "-v borg" ]; then return 1; fi; builtin command "$@"; }; '
|
||||
'uname() { echo aarch64; }; hb_translate() { printf "%s" "$1"; }; '
|
||||
'msg_error() { printf "%s" "$1" >&2; }; '
|
||||
'wget() { echo UNEXPECTED_DOWNLOAD >&2; return 99; }; '
|
||||
'HB_STATE_DIR="$1"; hb_ensure_borg')
|
||||
result = subprocess.run(['bash', '-c', script, 'fixture', folder], capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn('Native Borg is unavailable.', result.stderr)
|
||||
self.assertNotIn('UNEXPECTED_DOWNLOAD', result.stderr)
|
||||
self.assertEqual(result.stdout, '')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user