ipxe-tool/ipxe.sh
2025-10-18 23:26:25 +03:00

771 lines
27 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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