#!/usr/bin/env bash set -Eeuo pipefail DEFAULT_URL="https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-minirootfs-3.22.2-x86_64.tar.gz" DEFAULT_IMG="/var/ipxe/disk.img" DEFAULT_SIZE_MB=512 # [512..1024] SIZE_MIN=512 SIZE_MAX=1024 die() { echo "error: $*" >&2; exit 1; } log() { echo "==> $*"; } need_root() { [ "$(id -u)" -eq 0 ] || die "нужны права root"; } need_cmd() { command -v "$1" >/dev/null 2>&1 || die "не найдена утилита: $1"; } is_mounted() { mountpoint -q "$1"; } umount_if() { is_mounted "$1" && umount "$1" || true; } wait_for_blk() { local dev="$1" tries="${2:-60}" i=0 while [ $i -lt "$tries" ]; do [ -b "$dev" ] && return 0 sleep 0.1; i=$((i+1)) done return 1 } # ---------- требования на хосте ---------- require_host_tools() { local tools=(losetup parted mkfs.ext4 mount umount curl tar chroot) command -v fallocate >/dev/null 2>&1 || \ command -v truncate >/dev/null 2>&1 || \ command -v dd >/dev/null 2>&1 || \ die "нужен fallocate/ truncate/ dd" for t in "${tools[@]}"; do need_cmd "$t" done command -v partprobe >/dev/null 2>&1 || \ command -v partx >/dev/null 2>&1 || true } # ---------- overlayfs: проверка поддержки и утилит ---------- require_overlay_support() { if grep -qw overlay /proc/filesystems; then return 0 fi if command -v modprobe >/dev/null 2>&1; then modprobe -q overlay 2>/dev/null || true fi if grep -qw overlay /proc/filesystems; then return 0 fi if ! command -v modprobe >/dev/null 2>&1; then die "отсутствие поддержки overlayfs: нет записи в /proc/filesystems и утилита modprobe недоступна" else die "отсутствие поддержки overlayfs: модуль overlay недоступен в ядре или заблокирован" fi } # ---------- монтирование окружения chroot ---------- mount_chroot_binds() { local root="$1" for d in dev dev/pts dev/shm proc sys run; do mkdir -p "$root/$d"; done if ! is_mounted "$root/dev"; then mount --bind /dev "$root/dev" fi if ! is_mounted "$root/proc"; then mount -t proc proc "$root/proc" -o nosuid,nodev,noexec,ro fi if ! is_mounted "$root/sys"; then mount -t sysfs sysfs "$root/sys" -o nosuid,nodev,noexec,ro fi if ! is_mounted "$root/run"; then mount -t tmpfs tmpfs "$root/run" -o nosuid,nodev,mode=755 fi if ! is_mounted "$root/dev/pts"; then mount -t devpts devpts "$root/dev/pts" -o gid=5,mode=620,newinstance 2>/dev/null || \ mount -t devpts devpts "$root/dev/pts" -o gid=5,mode=620 fi if ! is_mounted "$root/dev/shm"; then mount -t tmpfs tmpfs "$root/dev/shm" -o nosuid,nodev,mode=1777 fi } umount_chroot_binds() { local root="$1" umount_if "$root/dev/pts" umount_if "$root/dev/shm" umount_if "$root/run" umount_if "$root/proc" umount_if "$root/sys" umount_if "$root/dev" } # ---------- bootstrap в chroot ---------- bootstrap_in_chroot() { local mnt="$1" log "bootstrap: выполнение команд в chroot" chroot "$mnt" /usr/bin/env -i \ HOME=/root TERM="${TERM:-xterm-256color}" \ PATH=/usr/sbin:/usr/bin:/sbin:/bin \ /bin/ash -lc ' set -eux printf "%s\n" "nameserver 8.8.8.8" > /etc/resolv.conf apk update apk upgrade --no-cache apk add --no-cache musl-dev gcc make git perl xz-dev syslinux xorriso nano git clone https://github.com/ipxe/ipxe.git /root/ipxe || true ' } # ---------- image build (СБОРКА disk.img) ---------- cmd_image_build() { local url="$DEFAULT_URL" img="$DEFAULT_IMG" size_mb="$DEFAULT_SIZE_MB" local bootstrap=1 force=0 debug=0 while [ $# -gt 0 ]; do case "$1" in --url) url="${2:?}"; shift 2;; --img) img="${2:?}"; shift 2;; --size-mb) size_mb="${2:?}"; shift 2;; --no-bootstrap) bootstrap=0; shift;; --force) force=1; shift;; --debug) debug=1; shift;; -h|--help) echo "usage: $0 image build [--url URL] [--img PATH] [--size-mb 512..1024] [--no-bootstrap] [--force] [--debug]"; exit 0;; *) die "неизвестный аргумент: $1";; esac done [ "$debug" -eq 1 ] && set -x need_root require_host_tools [[ "$size_mb" =~ ^[0-9]+$ ]] || die "--size-mb: число" [ "$size_mb" -ge "$SIZE_MIN" ] || die "минимальный размер ${SIZE_MIN}MB" [ "$size_mb" -le "$SIZE_MAX" ] || die "максимальный размер ${SIZE_MAX}MB" mkdir -p "$(dirname "$img")" if [ -e "$img" ] && [ "$force" -ne 1 ]; then die "файл уже существует: $img (флаг --force)" fi log "создание образа: $img (${size_mb}MB)" if command -v fallocate >/dev/null 2>&1; then fallocate -v -l "${size_mb}M" "$img" elif command -v truncate >/dev/null 2>&1; then truncate -s "${size_mb}M" "$img" else dd if=/dev/zero of="$img" bs=1M count="$size_mb" status=progress fi log "назначение loop-устройства" LOOPDEV="$(losetup -f -P --show "$img")" log "loop-устройство: $LOOPDEV" cleanup() { set +e if [ -n "${MNT_DIR:-}" ]; then umount_chroot_binds "$MNT_DIR" || true umount_if "$MNT_DIR" || true rmdir "$MNT_DIR" 2>/dev/null || true fi if [ -n "${LOOPDEV:-}" ]; then losetup -d "$LOOPDEV" 2>/dev/null || true fi } trap cleanup EXIT log "разметка msdos: primary 2048s..100%" parted -s "$LOOPDEV" mklabel msdos parted -s "$LOOPDEV" mkpart primary ext4 2048s 100% command -v partprobe >/dev/null 2>&1 && partprobe "$LOOPDEV" || true command -v partx >/dev/null 2>&1 && partx -u "$LOOPDEV" || true PART="${LOOPDEV}p1" wait_for_blk "$PART" || die "отсутствие раздела: $PART" log "создание файловой системы ext4" mkfs.ext4 -F -L alpine-root "$PART" >/dev/null MNT_DIR="$(mktemp -d -p /mnt alpine-root.XXXXXX || mktemp -d)" log "монтирование $PART -> $MNT_DIR" mount "$PART" "$MNT_DIR" log "скачивание и распаковка minirootfs" curl -fsSL "$url" | tar -xzf - -C "$MNT_DIR" log "подготовка /develop: build.sh и certtrust.patch" mkdir -p "$MNT_DIR/develop" cat >"$MNT_DIR/develop/build.sh" <<'EOSH' #!/bin/ash set -eu usage() { cat <<'EOF' Использование: build-ipxe.sh [--efi] [--legacy] [--iso] [--default] [--patch] [-h|--help] Без флагов собираются все цели: --legacy -> make bin-i386-pcbios/undionly.kpxe EMBED=start.ipxe --efi -> make bin-x86_64-efi/ipxe.efi EMBED=start.ipxe --iso -> make bin/ipxe.iso EMBED=start.ipxe Дополнительно: --default -> включение дефолтных опций в config/general.h (PING_CMD, IPSTAT_CMD, CONSOLE_CMD, REBOOT_CMD, POWEROFF) --patch -> применение /develop/certtrust.patch к /root/ipxe (git apply) и активация HTTPS, CERT_CMD EOF } # Определение числа потоков без глобальных переменных jobs_n() { if [ -n "${JOBS:-}" ]; then printf '%s' "$JOBS" return fi nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1' } # ---- разбор флагов ---- BUILD_EFI=0 BUILD_LEGACY=0 BUILD_ISO=0 ANY=0 OPT_DEFAULT=0 OPT_PATCH=0 for arg in "$@"; do case "$arg" in --efi|-e) BUILD_EFI=1; ANY=1 ;; --legacy|-l) BUILD_LEGACY=1; ANY=1 ;; --iso|-i) BUILD_ISO=1; ANY=1 ;; --default) OPT_DEFAULT=1 ;; --patch) OPT_PATCH=1 ;; -h|--help) usage; exit 0 ;; *) echo "Неизвестный флаг: $arg" >&2 usage exit 2 ;; esac done # если флаги сборки не заданы — собрать всё if [ "$ANY" -eq 0 ]; then BUILD_EFI=1 BUILD_LEGACY=1 BUILD_ISO=1 fi # ---- пути ---- ROOT="/root/ipxe" SRC="$ROOT/src" FILE="$SRC/start.ipxe" CONFIG="$SRC/config/general.h" PATCH_FILE="/develop/certtrust.patch" # ---- проверки ---- [ -d "$SRC" ] || { echo "Отсутствует каталог: $SRC" >&2; exit 1; } command -v nano >/dev/null 2>&1 || { echo "Требуется nano" >&2; exit 1; } mkdir -p "$(dirname "$FILE")" "$(dirname "$CONFIG")" # ---- запись шаблона start.ipxe ---- cat >"$FILE" <<'IPXE' #!ipxe :start dhcp && goto next || prompt --key s --timeout 3000 Press "s" for the iPXE command line... && shell || goto start :next chain tftp://${next-server}/boot.ipxe IPXE # ---- предварительная настройка config.general.h (по флагам) ---- [ -f "$CONFIG" ] || : > "$CONFIG" if [ "$OPT_DEFAULT" -eq 1 ]; then echo "Включение опций по умолчанию: $CONFIG" sed -i 's|//[[:space:]]*#define[[:space:]]*PING_CMD|#define PING_CMD|' "$CONFIG" sed -i 's|//[[:space:]]*#define[[:space:]]*IPSTAT_CMD|#define IPSTAT_CMD|' "$CONFIG" sed -i 's|//[[:space:]]*#define[[:space:]]*CONSOLE_CMD|#define CONSOLE_CMD|' "$CONFIG" sed -i 's|//[[:space:]]*#define[[:space:]]*REBOOT_CMD|#define REBOOT_CMD|' "$CONFIG" sed -i 's|//[[:space:]]*#define[[:space:]]*POWEROFF|#define POWEROFF|' "$CONFIG" fi if [ "$OPT_PATCH" -eq 1 ]; then [ -f "$PATCH_FILE" ] || { echo "Отсутствует патч: $PATCH_FILE" >&2; exit 1; } command -v git >/dev/null 2>&1 || { echo "Требуется git для применения патча" >&2; exit 1; } echo "Применение патча (git apply -p1): $PATCH_FILE -> $ROOT" git -C "$ROOT" apply -p1 --whitespace=nowarn "$PATCH_FILE" echo "Активация HTTPS и CERT_CMD: $CONFIG" sed -i 's|#undef[[:space:]]*DOWNLOAD_PROTO_HTTPS|#define DOWNLOAD_PROTO_HTTPS|' "$CONFIG" sed -i 's|//[[:space:]]*#define[[:space:]]*CERT_CMD|#define CERT_CMD|' "$CONFIG" fi # ---- редактирование ---- echo "Открытие в nano: $FILE" nano "$FILE" [ -f "$FILE" ] || { echo "Отсутствует файл: $FILE; сборка прервана" >&2; exit 1; } echo "Открытие в nano: $CONFIG" nano "$CONFIG" # ---- сборка ---- cd "$SRC" if [ "$BUILD_LEGACY" -eq 1 ]; then echo "Сборка: legacy/PCBIOS -> bin-i386-pcbios/undionly.kpxe" make -j"$(jobs_n)" bin-i386-pcbios/undionly.kpxe EMBED=start.ipxe fi if [ "$BUILD_EFI" -eq 1 ]; then echo "Сборка: x86_64 EFI -> bin-x86_64-efi/ipxe.efi" make -j"$(jobs_n)" bin-x86_64-efi/ipxe.efi EMBED=start.ipxe fi if [ "$BUILD_ISO" -eq 1 ]; then echo "Сборка: ISO -> bin/ipxe.iso" make -j"$(jobs_n)" bin/ipxe.iso EMBED=start.ipxe fi echo "Готово." EOSH chmod +x "$MNT_DIR/develop/build.sh" cat >"$MNT_DIR/develop/certtrust.patch" <<'EOPATCH' diff --git a/src/crypto/rootcert.c b/src/crypto/rootcert.c index b198c1d95..ad884173a 100644 --- a/src/crypto/rootcert.c +++ b/src/crypto/rootcert.c @@ -24,6 +24,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include #include #include #include @@ -31,6 +32,11 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include +#include + +#undef ERRFILE +#define ERRFILE ERRFILE_cert_cmd /** @file * @@ -82,6 +88,58 @@ struct x509_root root_certificates = { .fingerprints = fingerprints, }; +/** + * Add a fingerprint to the root certificate store from a certificate + * + * @v cert X.509 certificate + * @ret rc Return status code + */ +int rootcert_add_fingerprint_from_cert ( struct x509_certificate *cert ) { + uint8_t fingerprint[FINGERPRINT_LEN]; + uint8_t *new_fingerprints; + size_t new_count; + size_t new_len; + void *ctx; + struct sha256_digest digest; + + /* Allocate context */ + ctx = malloc ( sha256_algorithm.ctxsize ); + if ( ! ctx ) { + DBGC ( &root_certificates, "ROOTCERT could not allocate context\n" ); + return -ENOMEM; + } + + /* Compute SHA256 fingerprint of the certificate */ + digest_init ( &sha256_algorithm, ctx ); + digest_update ( &sha256_algorithm, ctx, cert->raw.data, cert->raw.len ); + digest_final ( &sha256_algorithm, ctx, &digest ); + free ( ctx ); + + /* Copy digest to fingerprint array */ + memcpy ( fingerprint, &digest, FINGERPRINT_LEN ); + + /* Allocate new fingerprints array */ + new_count = root_certificates.count + 1; + new_len = new_count * FINGERPRINT_LEN; + new_fingerprints = realloc ( ( void * ) root_certificates.fingerprints, new_len ); + if ( ! new_fingerprints ) { + DBGC ( &root_certificates, "ROOTCERT could not allocate memory\n" ); + return -ENOMEM; + } + + /* Append new fingerprint */ + memcpy ( new_fingerprints + ( root_certificates.count * FINGERPRINT_LEN ), + fingerprint, FINGERPRINT_LEN ); + root_certificates.fingerprints = new_fingerprints; + root_certificates.count = new_count; + + DBGC ( &root_certificates, "ROOTCERT added fingerprint from cert, total %d:\n", + root_certificates.count ); + DBGC_HDA ( &root_certificates, 0, root_certificates.fingerprints, new_len ); + + return 0; +} + /** * Initialise root certificate * @@ -113,6 +171,10 @@ static void rootcert_init ( void ) { */ if ( ( len = fetch_raw_setting_copy ( NULL, &trust_setting, &external ) ) >= 0 ) { + if ( root_certificates.fingerprints != fingerprints ) { + /* Free existing external fingerprints if they exist */ + free ( ( void * ) root_certificates.fingerprints ); + } root_certificates.fingerprints = external; root_certificates.count = ( len / FINGERPRINT_LEN ); } diff --git a/src/hci/commands/cert_cmd.c b/src/hci/commands/cert_cmd.c index efa4c3c12..de6694e69 100644 --- a/src/hci/commands/cert_cmd.c +++ b/src/hci/commands/cert_cmd.c @@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include /** @file * @@ -209,6 +210,36 @@ static int certstat_payload ( struct x509_certificate *cert ) { return 0; } +/** + * "certtrust" payload + * + * Делает сертификат доверенным (добавляет его отпечаток в root_certificates) + */ +static int certtrust_payload ( struct x509_certificate *cert ) { + int rc = rootcert_add_fingerprint_from_cert ( cert ); + if ( rc != 0 ) { + printf ( "Could not add trust: %s\n", strerror ( rc ) ); + return rc; + } + return 0; +} + +/** "certtrust" command descriptor + * + * Принимает либо , либо ищет по --subject в certstore, + * как и прочие cert команды. + */ +static struct cert_command_descriptor certtrust_cmd = + CERT_COMMAND_DESC ( struct cert_options, opts.certstore, 0, 1, + "[]", certtrust_payload ); + +/** + * The "certtrust" command + */ +static int certtrust_exec ( int argc, char **argv ) { + return cert_exec ( argc, argv, &certtrust_cmd ); +} + /** "certstat" command descriptor */ static struct cert_command_descriptor certstat_cmd = CERT_COMMAND_DESC ( struct cert_options, opts.certstat, 0, 0, NULL, @@ -292,3 +323,4 @@ static int certfree_exec ( int argc, char **argv ) { COMMAND ( certstat, certstat_exec ); COMMAND ( certstore, certstore_exec ); COMMAND ( certfree, certfree_exec ); +COMMAND ( certtrust, certtrust_exec ); diff --git a/src/include/ipxe/rootcert.h b/src/include/ipxe/rootcert.h index d1a69723d..9768b2ecb 100644 --- a/src/include/ipxe/rootcert.h +++ b/src/include/ipxe/rootcert.h @@ -13,5 +13,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); extern const int allow_trust_override; extern struct x509_root root_certificates; +extern int rootcert_add_fingerprint_from_cert ( struct x509_certificate *cert ); #endif /* _IPXE_ROOTCERT_H */ EOPATCH mount_chroot_binds "$MNT_DIR" if [ "$bootstrap" -eq 1 ]; then bootstrap_in_chroot "$MNT_DIR" else log "пропуск этапа bootstrap" fi umount_chroot_binds "$MNT_DIR" sync log "завершение: образ готов — $img" } # ---------- chroot ---------- cmd_chroot() { local img="$DEFAULT_IMG" debug=0 run_cmd="" while [ $# -gt 0 ]; do case "$1" in --img) img="${2:?}"; shift 2;; --debug) debug=1; shift;; --) shift; run_cmd="$*"; break;; -h|--help) echo "usage: $0 image chroot [--img PATH] [--debug] [-- CMD ...]"; exit 0;; *) die "неизвестный аргумент: $1";; esac done [ "$debug" -eq 1 ] && set -x need_root require_host_tools [ -f "$img" ] || die "образ не найден: $img" log "назначение loop-устройства" LOOPDEV="$(losetup -f -P --show "$img")" PART="${LOOPDEV}p1" command -v partprobe >/dev/null 2>&1 && partprobe "$LOOPDEV" || true command -v partx >/dev/null 2>&1 && partx -u "$LOOPDEV" || true wait_for_blk "$PART" || die "отсутствие раздела: $PART" MNT_DIR="$(mktemp -d -p /mnt alpine-chroot.XXXXXX || mktemp -d)" cleanup() { set +e if [ -n "${MNT_DIR:-}" ]; then umount_chroot_binds "$MNT_DIR" || true umount_if "$MNT_DIR" || true rmdir "$MNT_DIR" 2>/dev/null || true fi if [ -n "${LOOPDEV:-}" ]; then losetup -d "$LOOPDEV" 2>/dev/null || true fi } trap cleanup EXIT log "монтирование $PART -> $MNT_DIR" mount "$PART" "$MNT_DIR" mount_chroot_binds "$MNT_DIR" mkdir -p "$MNT_DIR/etc" printf "%s\n" "nameserver 8.8.8.8" > "$MNT_DIR/etc/resolv.conf" if [ -n "$run_cmd" ]; then log "выполнение команды в chroot" chroot "$MNT_DIR" /usr/bin/env -i HOME=/root TERM="${TERM:-xterm-256color}" PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/ash -lc "$run_cmd" else log "запуск интерактивной оболочки /bin/ash" chroot "$MNT_DIR" /usr/bin/env -i HOME=/root TERM="${TERM:-xterm-256color}" PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/ash --login || true fi log "выход из chroot: очистка" } # ---------- overlay (эфемерный RW поверх RO) ---------- cmd_overlay() { local img="$DEFAULT_IMG" debug=0 run_cmd="" overlay_size_mb="" while [ $# -gt 0 ]; do case "$1" in --img) img="${2:?}"; shift 2;; --overlay-size-mb) overlay_size_mb="${2:?}"; shift 2;; --debug) debug=1; shift;; --) shift; run_cmd="$*"; break;; -h|--help) echo "usage: $0 image overlay [--img PATH] [--overlay-size-mb N] [--debug] [-- CMD ...]"; exit 0;; *) die "неизвестный аргумент: $1";; esac done [ "$debug" -eq 1 ] && set -x need_root require_host_tools require_overlay_support [ -f "$img" ] || die "образ не найден: $img" log "назначение loop-устройства" LOOPDEV="$(losetup -f -P --show "$img")" PART="${LOOPDEV}p1" command -v partprobe >/dev/null 2>&1 && partprobe "$LOOPDEV" || true command -v partx >/dev/null 2>&1 && partx -u "$LOOPDEV" || true wait_for_blk "$PART" || die "отсутствие раздела: $PART" MNT_LOWER="$(mktemp -d -p /mnt alpine-lower.XXXXXX || mktemp -d)" MNT_MERGED="$(mktemp -d -p /mnt alpine-merged.XXXXXX || mktemp -d)" OVL_BASE="$(mktemp -d -p /mnt alpine-ovl.XXXXXX || mktemp -d)" cleanup() { set +e umount_chroot_binds "$MNT_MERGED" 2>/dev/null || true umount_if "$MNT_MERGED" || true umount_if "$MNT_LOWER" || true umount_if "$OVL_BASE" || true rmdir "$MNT_MERGED" "$MNT_LOWER" "$OVL_BASE" 2>/dev/null || true if [ -n "${LOOPDEV:-}" ]; then losetup -d "$LOOPDEV" 2>/dev/null || true fi } trap cleanup EXIT log "монтирование нижнего слоя (RO)" mount -o ro,noload "$PART" "$MNT_LOWER" 2>/dev/null || mount -o ro "$PART" "$MNT_LOWER" log "подготовка overlay workspace (tmpfs)" if [ -n "$overlay_size_mb" ]; then [[ "$overlay_size_mb" =~ ^[0-9]+$ ]] || die "--overlay-size-mb: число" mount -t tmpfs -o "size=${overlay_size_mb}M,nosuid,nodev" tmpfs "$OVL_BASE" else mount -t tmpfs -o "nosuid,nodev" tmpfs "$OVL_BASE" fi mkdir -p "$OVL_BASE/upper" "$OVL_BASE/work" log "монтирование overlay (RW поверх RO)" mount -t overlay overlay -o "lowerdir=$MNT_LOWER,upperdir=$OVL_BASE/upper,workdir=$OVL_BASE/work" "$MNT_MERGED" mount_chroot_binds "$MNT_MERGED" mkdir -p "$MNT_MERGED/etc" printf "%s\n" "nameserver 8.8.8.8" > "$MNT_MERGED/etc/resolv.conf" if [ -n "$run_cmd" ]; then log "выполнение команды в chroot (overlay)" chroot "$MNT_MERGED" /usr/bin/env -i HOME=/root TERM="${TERM:-xterm-256color}" PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/ash -lc "$run_cmd" else log "запуск интерактивной оболочки /bin/ash (overlay)" chroot "$MNT_MERGED" /usr/bin/env -i HOME=/root TERM="${TERM:-xterm-256color}" PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/ash --login || true fi log "выход из chroot (overlay): очистка" } # ---------- build (overlay + /develop/build.sh + копирование артефактов) ---------- cmd_build() { local img="$DEFAULT_IMG" debug=0 overlay_size_mb="" out_dir="." local build_flags="" while [ $# -gt 0 ]; do case "$1" in --img) img="${2:?}"; shift 2;; --overlay-size-mb) overlay_size_mb="${2:?}"; shift 2;; --out-dir) out_dir="${2:?}"; shift 2;; --debug) debug=1; shift;; --) shift; build_flags="$*"; break;; -h|--help) echo "usage: $0 build [--img PATH] [--overlay-size-mb N] [--out-dir DIR] [--debug] [-- FLAGS для /develop/build.sh]"; exit 0;; *) die "неизвестный аргумент: $1";; esac done [ "$debug" -eq 1 ] && set -x need_root require_host_tools require_overlay_support [ -f "$img" ] || die "образ не найден: $img" log "назначение loop-устройства" LOOPDEV="$(losetup -f -P --show "$img")" PART="${LOOPDEV}p1" command -v partprobe >/dev/null 2>&1 && partprobe "$LOOPDEV" || true command -v partx >/dev/null 2>&1 && partx -u "$LOOPDEV" || true wait_for_blk "$PART" || die "отсутствие раздела: $PART" MNT_LOWER="$(mktemp -d -p /mnt alpine-lower.XXXXXX || mktemp -d)" MNT_MERGED="$(mktemp -d -p /mnt alpine-merged.XXXXXX || mktemp -d)" OVL_BASE="$(mktemp -d -p /mnt alpine-ovl.XXXXXX || mktemp -d)" cleanup() { set +e umount_chroot_binds "$MNT_MERGED" 2>/dev/null || true umount_if "$MNT_MERGED" || true umount_if "$MNT_LOWER" || true umount_if "$OVL_BASE" || true rmdir "$MNT_MERGED" "$MNT_LOWER" "$OVL_BASE" 2>/dev/null || true if [ -n "${LOOPDEV:-}" ]; then losetup -d "$LOOPDEV" 2>/dev/null || true fi } trap cleanup EXIT log "монтирование нижнего слоя (RO)" mount -o ro,noload "$PART" "$MNT_LOWER" 2>/dev/null || mount -o ro "$PART" "$MNT_LOWER" log "подготовка overlay workspace (tmpfs)" if [ -n "$overlay_size_mb" ]; then [[ "$overlay_size_mb" =~ ^[0-9]+$ ]] || die "--overlay-size-mb: число" mount -t tmpfs -o "size=${overlay_size_mb}M,nosuid,nodev" tmpfs "$OVL_BASE" else mount -t tmpfs -o "nosuid,nodev" tmpfs "$OVL_BASE" fi mkdir -p "$OVL_BASE/upper" "$OVL_BASE/work" log "монтирование overlay (RW поверх RO)" mount -t overlay overlay -o "lowerdir=$MNT_LOWER,upperdir=$OVL_BASE/upper,workdir=$OVL_BASE/work" "$MNT_MERGED" mount_chroot_binds "$MNT_MERGED" mkdir -p "$MNT_MERGED/etc" printf "%s\n" "nameserver 8.8.8.8" > "$MNT_MERGED/etc/resolv.conf" log "выполнение /develop/build.sh в chroot (overlay)" set +e if [ -n "$build_flags" ]; then chroot "$MNT_MERGED" /usr/bin/env -i HOME=/root TERM="${TERM:-xterm-256color}" PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/ash /develop/build.sh $build_flags else chroot "$MNT_MERGED" /usr/bin/env -i HOME=/root TERM="${TERM:-xterm-256color}" PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/ash /develop/build.sh fi CH_STATUS=$? set -e mkdir -p "$out_dir" if [ "$CH_STATUS" -eq 0 ]; then if [ -f "$MNT_MERGED/root/ipxe/src/bin-x86_64-efi/ipxe.efi" ]; then log "копирование ipxe.efi" cp -f "$MNT_MERGED/root/ipxe/src/bin-x86_64-efi/ipxe.efi" "$out_dir/" fi if [ -f "$MNT_MERGED/root/ipxe/src/bin-i386-pcbios/undionly.kpxe" ]; then log "копирование undionly.kpxe" cp -f "$MNT_MERGED/root/ipxe/src/bin-i386-pcbios/undionly.kpxe" "$out_dir/" fi if [ -f "$MNT_MERGED/root/ipxe/src/bin/ipxe.iso" ]; then log "копирование ipxe.iso" cp -f "$MNT_MERGED/root/ipxe/src/bin/ipxe.iso" "$out_dir/" fi else log "сборка пропущена: код возврата $CH_STATUS" fi log "завершение build: размонтирование и очистка" } usage() { cat <