This commit is contained in:
Alexander Zhirov 2025-10-18 23:26:25 +03:00
commit 9bc27e0fb8
Signed by: alexander
GPG key ID: C8D8BE544A27C511
2 changed files with 934 additions and 0 deletions

163
README.md Normal file
View file

@ -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`-устройств.

771
ipxe.sh Executable file
View file

@ -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 <stdlib.h>
+#include <errno.h>
#include <ipxe/crypto.h>
#include <ipxe/sha256.h>
#include <ipxe/x509.h>
@@ -31,6 +32,11 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/dhcp.h>
#include <ipxe/init.h>
#include <ipxe/rootcert.h>
+#include <ipxe/malloc.h>
+#include <string.h>
+
+#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 <ipxe/parseopt.h>
#include <usr/imgmgmt.h>
#include <usr/certmgmt.h>
+#include <ipxe/rootcert.h>
/** @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
+ *
+ * Принимает либо <uri|image>, либо ищет по --subject в certstore,
+ * как и прочие cert<xxx> команды.
+ */
+static struct cert_command_descriptor certtrust_cmd =
+ CERT_COMMAND_DESC ( struct cert_options, opts.certstore, 0, 1,
+ "[<uri|image>]", 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 <<EOF
usage:
$0 build [--img PATH] [--overlay-size-mb N] [--out-dir DIR] [--debug] [-- FLAGS для /develop/build.sh]
$0 image build [--url URL] [--img PATH] [--size-mb 512..1024] [--no-bootstrap] [--force] [--debug]
$0 image chroot [--img PATH] [--debug] [-- CMD ...]
$0 image overlay [--img PATH] [--overlay-size-mb N] [--debug] [-- CMD ...]
EOF
}
# ---------- входная точка ----------
cmd="${1:-}"
shift || true
case "$cmd" in
build) cmd_build "$@";;
image)
sub="${1:-}"; shift || true
case "$sub" in
build) cmd_image_build "$@";;
chroot) cmd_chroot "$@";;
overlay) cmd_overlay "$@";;
""|-h|--help) echo "usage: $0 image {build|chroot|overlay} [...]";;
*) die "неизвестная подкоманда image: $sub";;
esac
;;
""|-h|--help) usage;;
*) die "неизвестная подкоманда: $cmd";;
esac