commit 9bc27e0fb837b112324a001fa022090d848f4c1b Author: Alexander Zhirov Date: Sat Oct 18 23:26:25 2025 +0300 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..965c77f --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# iPXE tool + +Скрипт выполняет два основных набора задач: + +1. **Работа с образом Alpine (`disk.img`)** — команда `image` с подкомандами: + + * `image build` — создание и первичное наполнение образа; + * `image chroot` — вход в chroot; + * `image overlay` — вход в chroot с наложением OverlayFS (изменения не сохраняются). +2. **Сборка iPXE в режиме overlay** с временными изменениями и копированием артефактов на хост — команда `build`. + +> Все команды требуют прав `root`. + +--- + +## Требования + +* Поддержка loop-устройств и доступ к утилитам: `losetup`, `parted`, `mkfs.ext4`, `mount`, `umount`, `curl`, `tar`, `chroot`. +* Для создания файла: одна из утилит `fallocate`/`truncate`/`dd`. +* Для OverlayFS: поддержка `overlay` в ядре (`/proc/filesystems`) и доступен `modprobe` (если модуль загружается динамически). +* Интернет-доступ для загрузки `alpine-minirootfs`. +* Доступные каталоги: + + * Образ по умолчанию: `/var/ipxe/disk.img` + * Внутри образа при первой сборке создаётся `/develop` с файлами: + + * `/develop/build.sh` — сценарий сборки iPXE; + * `/develop/certtrust.patch` — патч к исходникам iPXE (по необходимости). + +--- + +## Быстрый старт + +### 1) Создание образа Alpine + +```bash +sudo ./ipxe.sh image build +``` + +По умолчанию: + +* URL minirootfs: `https://dl-cdn.alpinelinux.org/.../alpine-minirootfs-3.22.2-x86_64.tar.gz` +* Путь к образу: `/var/ipxe/disk.img` +* Размер: `512` МБ (диапазон: `512..1024` МБ) + +Полезные флаги: + +* `--url URL` — другой источник minirootfs +* `--img PATH` — другой путь к образу +* `--size-mb N` — размер образа (512..1024) +* `--no-bootstrap` — без установки пакетов/репозитория iPXE +* `--force` — перезапись существующего файла +* `--debug` — подробный вывод + +Пример: + +```bash +sudo ./ipxe.sh image build --img /var/ipxe/disk.img --size-mb 768 --force +``` + +### 2) Вход в chroot (с сохранением изменений) + +```bash +sudo ./ipxe.sh image chroot --img /var/ipxe/disk.img +``` + +Опционально можно выполнить команду сразу: + +```bash +sudo ./ipxe.sh image chroot --img /var/ipxe/disk.img -- 'apk add htop' +``` + +### 3) Вход в chroot в режиме OverlayFS (изменения не сохраняются) + +```bash +sudo ./ipxe.sh image overlay --img /var/ipxe/disk.img +``` + +Опционально задать размер tmpfs для верхнего слоя: + +```bash +sudo ./ipxe.sh image overlay --img /var/ipxe/disk.img --overlay-size-mb 512 +``` + +### 4) Сборка iPXE в overlay и копирование артефактов на хост + +```bash +sudo ./ipxe.sh build --img /var/ipxe/disk.img --out-dir /tmp/out -- --efi --legacy --iso --default --patch +``` + +* Все флаги после `--` передаются **внутреннему** `/develop/build.sh`. +* Артефакты копируются в `--out-dir` (по умолчанию текущий каталог) **только если были собраны**: + + * `ipxe.efi` → `/root/ipxe/src/bin-x86_64-efi/ipxe.efi` + * `undionly.kpxe` → `/root/ipxe/src/bin-i386-pcbios/undionly.kpxe` + * `ipxe.iso` → `/root/ipxe/src/bin/ipxe.iso` + +--- + +## Поведение и детали + +* **Логи** скрипта нейтральные: «создание образа», «монтирование», «скачивание», «распаковка» и т. п. +* При `image build` выполняется разметка `msdos`, создаётся один раздел `ext4`, монтируется и наполняется содержимым `minirootfs`. +* При первой сборке внутрь образа добавляется `/develop/build.sh` и `/develop/certtrust.patch`. +* `image chroot` и `image overlay` автоматически монтируют необходимые файловые системы (`/dev`, `proc`, `sys`, `run`, `devpts`, `shm`), а при выходе — размонтируют. +* В `overlay`-режиме нижний слой (`disk.img`) монтируется **только для чтения**, верхний слой — во временный `tmpfs`; все изменения теряются при выходе. +* Команда `build`: + + * Разворачивает `overlay`, выполняет `/develop/build.sh` с переданными флагами; + * По коду возврата определяет, была ли сборка успешной, и при наличии артефактов копирует их на хост в `--out-dir`; + * После завершения всё размонтирует автоматически. + +--- + +## Часто используемые флаги `/develop/build.sh` + +Передаются через `--` в команду `build`, например: + +```bash +sudo ./ipxe.sh build -- --efi --default +``` + +* `--efi` — сборка `bin-x86_64-efi/ipxe.efi` +* `--legacy` — сборка `bin-i386-pcbios/undionly.kpxe` +* `--iso` — сборка `bin/ipxe.iso` +* Без флагов собираются **все** цели. +* `--default` — включение набора опций в `config/general.h` (PING_CMD, IPSTAT_CMD, CONSOLE_CMD и т. д.) +* `--patch` — применение `/develop/certtrust.patch` и включение HTTPS/CERT_CMD + +--- + +## Примеры + +Создание образа и последующая сборка всех артефактов iPXE в overlay с копированием на хост: + +```bash +sudo ./ipxe.sh image build --img /var/ipxe/disk.img --size-mb 512 +sudo ./ipxe.sh build --img /var/ipxe/disk.img --out-dir /srv/ipxe -- --efi --legacy --iso +``` + +Минимальная сборка только EFI, с патчем и дефолтными опциями: + +```bash +sudo ./ipxe.sh build --img /var/ipxe/disk.img --out-dir /srv/ipxe -- --efi --patch --default +``` + +Проверка окружения без сохранения изменений: + +```bash +sudo ./ipxe.sh image overlay --img /var/ipxe/disk.img --overlay-size-mb 256 -- 'apk info -vv' +``` + +--- + +## Диагностика + +* «образ не найден» — проверьте путь `--img` и выполните `image build`. +* «минимальный/максимальный размер» — скорректируйте `--size-mb` (диапазон 512..1024). +* «отсутствие поддержки overlayfs» — убедитесь в наличии модуля `overlay` в ядре и/или возможности загрузки через `modprobe`. +* При сетевых операциях используйте актуальный URL minirootfs (`--url`). + +При любой ошибке скрипт выполняет автоматическую очистку: размонтирование файловых систем и отвязку `loop`-устройств. + diff --git a/ipxe.sh b/ipxe.sh new file mode 100755 index 0000000..549b265 --- /dev/null +++ b/ipxe.sh @@ -0,0 +1,771 @@ +#!/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 <