771 lines
27 KiB
Bash
Executable file
771 lines
27 KiB
Bash
Executable file
#!/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
|