Этот пост для тех, кто уже работал с live-bild и знаком с этим делом.
Для себя я создал скрипты, формирующие основу сборки.
Скрипт mk-lb-bookworm создаёт основу для создания сборки дистрибутива.
Shell
#!/bin/bash
# ==============================================================================
# Создание загрузочного образа дистрибутива Debian 12 (Bookworm)
# Скрипт mk-lb-bookworm
# ==============================================================================
set -e
mkdir -p auto config
# Скрипт auto/config
cat << 'EOF' > auto/config
#!/bin/bash
# ============================================================================ #
# КОНФИГУРАЦИЯ СБОРКИ
# ============================================================================ #
# Настройки для Debian Bookworm
# ============================================================================ #
# ===== НАСТРОЙКИ ЗЕРКАЛ =====
# Раскомментируй нужное зеркало для ускорения загрузки
#mirror="http://deb.debian.org/debian/"
mirror="https://mirror.yandex.ru/debian/"
#mirror="https://mirror.litnet.lt/debian/"
#mirror="https://debian.mirror.vu.lt/debian/"
#mirror="https://debian.balt.net/debian/"
#mirror="http://ftp.lt.debian.org/debian/"
version="bookworm"
arch="amd64"
# ===== ЛОКАЛИЗАЦИЯ =====
LOCALE_SETTINGS="timezone=Europe/Moscow locales=ru_RU.UTF-8 keyboard-layouts=us,ru keyboard-variants=, keyboard-model=pc105 keyboard-options=grp:alt_shift_toggle"
# ===== ОСНОВНАЯ КОНФИГУРАЦИЯ =====
lb config noauto \
--distribution "${version}" \
--parent-distribution "${version}" \
--debian-installer "false" \
--archive-areas "main contrib non-free non-free-firmware" \
--debootstrap-options "--include=apt-transport-https,ca-certificates,openssl,curl,wget" \
--firmware-binary "true" \
--firmware-chroot "true" \
--memtest "none" \
--backports "true" \
--bootappend-live "boot=live components quiet splash ${LOCALE_SETTINGS}" \
--image-name "debian-${version}" \
--iso-application "Debian" \
--linux-packages "linux-image" \
--linux-flavours "amd64" \
--architecture "${arch}" \
--iso-preparer "Custom Build Script" \
--iso-publisher "Debian Live Project" \
--iso-volume "Debian Live" \
--mirror-bootstrap "${mirror}" \
--mirror-binary "${mirror}" \
--mirror-chroot "${mirror}" \
"${@}"
# ===== РЕЗЮМЕ =====
echo "✅ Базовая конфигурация Debian ${version} создана"
echo "✅ Используется зеркало: ${mirror:-default}"
echo "✅ Сборка будет использовать пакеты из main/contrib/non-free/non-free-firmware"
EOF
# Скрипт auto/clean
cat << 'EOF' > auto/clean
#!/bin/bash
# ============================================================================ #
# ПОЛНАЯ ОЧИСТКА ПРОЕКТА
# ============================================================================ #
# Удаляет:
# - все временные файлы live-build
# - временные логи
# Исходное содержимое config/ и ссылки модулей не удаляются.
# ============================================================================ #
echo "=== Очистка проекта live-build ==="
# Стандартная очистка lb (noauto предотвращает рекурсию)
lb clean noauto "$@"
# Удаление временных логов, создаваемых live-build
echo "Удаление временных логов live-build..."
rm -f binary.log prepare.log wget-log*
# Удаление каталогов .build/ (кэш и временные файлы)
rm -rf .build/
echo "Очистка завершена"
EOF
# Скрипт auto/build
cat << 'EOF' > auto/build
#!/bin/bash
# ============================================================================ #
# СБОРКА ПРОЕКТА Debian Bookworm
# ============================================================================ #
set -o pipefail
TIMESTAMP=$(date +%Y_%m_%d:%H_%M_%S)
LOG_FILE="build-${TIMESTAMP}.log"
echo "🚀 === Начало сборки Debian Bookworm: $(date) ===" | tee -a "$LOG_FILE"
if sudo lb build noauto "${@}" 2>&1 | tee -a "$LOG_FILE"; then
echo "🎉 === Сборка успешно завершена: $(date) ===" | tee -a "$LOG_FILE"
ISO_FILE=$(ls -t *.iso 2>/dev/null | head -1)
if [ -n "$ISO_FILE" ]; then
echo "💽 Создан ISO образ: $ISO_FILE" | tee -a "$LOG_FILE"
ls -lh "$ISO_FILE" | tee -a "$LOG_FILE"
fi
else
echo "🚨 === ОШИБКА сборки! Смотри лог: $LOG_FILE ===" | tee -a "$LOG_FILE"
exit 1
fi
EOF
cat << 'EOF' > 01-start.sh
#!/bin/bash
# ============================================================================ #
# ПОЛНЫЙ ЦИКЛ СБОРКИ
# ============================================================================ #
# Запускает: очистку -> конфигурацию -> сборку
# Автоматически замеряет время выполнения
# ============================================================================ #
# Засекаем время старта
SECONDS=0
echo "=================================================="
echo "🔄 ЗАПУСК ПОЛНОГО ЦИКЛА СБОРКИ"
echo "=================================================="
echo "📅 Старт: $(date)"
echo ""
# Шаг 1: Очистка
echo "🧹 [1/3] Очистка проекта..."
sudo ./auto/clean
# Проверяем успешность очистки
if [ $? -eq 0 ]; then
echo "✅ Очистка выполнена успешно!"
else
echo "❌ Ошибка при очистке!"
exit 1
fi
echo ""
# Шаг 2: Конфигурация
echo "⚙️ [2/3] Создание конфигурации..."
./auto/config
# Проверяем успешность конфигурации
if [ $? -eq 0 ]; then
echo "✅ Конфигурация создана успешно!"
else
echo "❌ Ошибка при создании конфигурации!"
exit 1
fi
echo ""
# Шаг 3: Сборка
echo "🔨 [3/3] Запуск сборки..."
./auto/build
# Проверяем успешность сборки
if [ $? -eq 0 ]; then
echo "✅ Сборка выполнена успешно! 🎉"
else
echo "❌ Ошибка при сборке!"
exit 1
fi
# Подсчёт времени
DURATION=$((SECONDS / 60))
REMAINDER=$((SECONDS % 60))
echo ""
echo "=================================================="
echo "🏁 СБОРКА ЗАВЕРШЕНА"
echo "=================================================="
echo "⏱️ Время выполнения: ${DURATION} мин ${REMAINDER} сек"
echo "📅 Завершение: $(date)"
echo "=================================================="
# Проверяем наличие ISO файла
ISO_FILE=$(ls -t *.iso 2>/dev/null | head -1)
if [ -n "$ISO_FILE" ]; then
echo "📀 Создан ISO: $ISO_FILE"
echo "💾 Размер: $(du -h "$ISO_FILE" | cut -f1)"
echo "📁 Путь: $(pwd)/$ISO_FILE"
echo "=================================================="
fi
# Запись в лог
echo "$(date): Сборка завершена за ${DURATION} мин ${REMAINDER} сек" >> build-stats.log
echo "📝 Лог сохранён в build-stats.log"
echo "=================================================="
echo "✨ Готово! Можно запускать тестирование: ./04-test.sh"
echo "=================================================="
EOF
cat << 'EOF' > 02-clean.sh
#!/bin/bash
# ============================================================================ #
# БЫСТРАЯ ОЧИСТКА
# ============================================================================ #
# Стандартная очистка без удаления конфигурации
# ============================================================================ #
echo "Очистка проекта (стандартная)..."
sudo lb clean noauto
echo "📅 Готово"
EOF
cat << 'EOF' > 03-deep-clean.sh
#!/bin/bash
# ============================================================================ #
# ГЛУБОКАЯ ОЧИСТКА
# ============================================================================ #
# Полная очистка + удаление кэша и логов
# ============================================================================ #
echo "=================================================="
echo "🧹 ГЛУБОКАЯ ОЧИСТКА ПРОЕКТА"
echo "=================================================="
echo "📅 Старт: $(date)"
echo ""
# Полная очистка через auto/clean
echo "🗑️ [1/3] Запуск полной очистки..."
sudo ./auto/clean
# Проверяем успешность очистки
if [ $? -eq 0 ]; then
echo "✅ Очистка выполнена успешно!"
else
echo "❌ Ошибка при очистке!"
exit 1
fi
echo ""
# Удаление кэша
echo "🧊 [2/3] Удаление кэша пакетов..."
if [ -d "cache/" ]; then
sudo rm -rf cache/
echo "✅ Кэш удалён!"
else
echo "ℹ️ каталог cache/ не найден (пропускаем)"
fi
echo ""
# Удаление всех логов сборки
echo "📝 [3/3] Удаление логов сборки..."
LOGS_REMOVED=0
# Удаляем логи, если они существуют
for log_file in build-*.log build.log build-stats.log; do
if [ -f "$log_file" ]; then
rm -f "$log_file"
echo " 🗑️ Удалён: $log_file"
((LOGS_REMOVED++))
fi
done
if [ $LOGS_REMOVED -eq 0 ]; then
echo "ℹ️ Логи не найдены (чисто)"
else
echo "✅ Удалено логов: $LOGS_REMOVED"
fi
echo ""
# Проверка свободного места
echo "💾 Свободное место:"
df -h . | tail -1 | awk '{print " 📊 Использовано: " $3 " из " $2 " (" $5 ")"}'
echo ""
echo "=================================================="
echo "✅ ГЛУБОКАЯ ОЧИСТКА ЗАВЕРШЕНА! 🎉"
echo "=================================================="
echo "📅 Завершение: $(date)"
echo "✨ Проект полностью очищен и готов к новой сборке"
echo "=================================================="
# Запись в лог очистки
echo "$(date): Выполнена глубокая очистка" >> clean.log
echo "📝 Лог очистки сохранён в clean.log"
echo "=================================================="
EOF
cat << 'EOF' > 04-test.sh
#!/bin/bash
# ============================================================================ #
# ТЕСТИРОВАНИЕ ISO В QEMU
# ============================================================================ #
# Запускает последний созданный ISO в виртуальной машине
# ============================================================================ #
# Функция проверки QEMU
check_qemu() {
echo "🔍 Проверка наличия QEMU..."
if command -v qemu-system-x86_64 &> /dev/null; then
echo "✅ QEMU уже установлен! 🎉"
return 0
else
echo "❌ QEMU не найден!"
echo "Установи его командой:"
echo " sudo apt install qemu-system-x86 qemu-utils"
exit 1
fi
}
# Проверяем наличие QEMU
check_qemu
# Находим самый свежий ISO
ISO_FILE=$(ls -t *.iso 2>/dev/null | head -1)
if [ -z "$ISO_FILE" ]; then
echo "❌ ОШИБКА: ISO образ не найден!"
echo "📝 Сначала выполни сборку: ./01-start.sh"
exit 1
fi
echo ""
echo "=================================================="
echo "🚀 ЗАПУСК ISO В QEMU"
echo "=================================================="
echo "📀 ISO файл: $ISO_FILE"
echo "💾 Размер: $(du -h "$ISO_FILE" | cut -f1)"
echo ""
echo "🖥️ Запуск виртуальной машины..."
echo "ℹ️ (Закрой окно QEMU для выхода)"
echo "=================================================="
echo ""
# Проверяем поддержку KVM
if [ -e /dev/kvm ]; then
# Проверяем права доступа к /dev/kvm
if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
echo "✅ KVM доступен - использую аппаратную виртуализацию 🚀"
QEMU_ACCEL=(-enable-kvm -cpu host)
else
echo "⚠️ Нет прав на /dev/kvm"
echo "ℹ️ Использую программную эмуляцию (медленнее)"
QEMU_ACCEL=(-cpu max)
fi
else
echo "⚠️ KVM не доступен - использую программную эмуляцию (медленнее) 🐢"
QEMU_ACCEL=(-cpu max)
fi
# Запуск QEMU
qemu-system-x86_64 \
"${QEMU_ACCEL[@]}" \
-m 2048 \
-smp 2 \
-cdrom "$ISO_FILE" \
-vga std \
-net nic -net user
EOF
# Делаем скрипты исполняемыми
chmod +x 01-start.sh 02-clean.sh 03-deep-clean.sh 04-test.sh auto/config auto/clean auto/build
Естественно, каталог config/ пуст.
Для заполнения его служит скрипт make_project.
Shell
#!/usr/bin/env bash
set -Eeuo pipefail
# Создаёт каталог нового проекта, а при повторном запуске формирует в нём
# install.sh и uninstall.sh.
#
# Каталог проекта должен повторять содержимое каталога config/ сборщика:
#
# my-project/
# ├── hooks/normal/0100-example.hook.chroot
# ├── includes.chroot/etc/example.conf
# └── README.md
#
# После запуска install.sh файлы попадут соответственно в:
#
# config/hooks/normal/0100-example.hook.chroot
# config/includes.chroot/etc/example.conf
die() {
printf 'Ошибка: %s\n' "$*" >&2
exit 1
}
usage() {
printf 'Использование: %s [имя_проекта]\n' "${0##*/}"
}
case $# in
0)
# В графической сессии запрашиваем имя через YAD. Если YAD недоступен,
# остаётся обычный ввод из терминала.
if [[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]] && command -v yad >/dev/null 2>&1; then
if ! PROJ_NAME=$(yad \
--entry \
--title='Создание модуля live-build' \
--window-icon='folder-new' \
--text='Введите название нового модуля и заполните его файлами' \
--button='Отмена:1' \
--button='Создать:0' \
--width=480); then
exit 0
fi
elif [[ -t 0 ]]; then
read -r -p 'Введите название нового модуля: ' PROJ_NAME
else
usage
die "имя проекта не указано, а интерактивный ввод недоступен."
fi
;;
1)
PROJ_NAME=$1
;;
*)
usage
die "укажите не более одного имени проекта."
;;
esac
# Имя проекта не должно превращаться в путь за пределами каталога проектов.
case "$PROJ_NAME" in
''|.|..|*/*)
die "недопустимое имя проекта: $PROJ_NAME"
;;
esac
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd -P)
PROJECT_DIR="$SCRIPT_DIR/$PROJ_NAME"
# === ПЕРВЫЙ ЗАПУСК: создаём пустой каталог проекта ===
if [[ ! -e "$PROJECT_DIR" ]]; then
mkdir -p -- "$PROJECT_DIR"
printf 'Проект «%s» создан. Заполните его файлами для установки.\n' "$PROJ_NAME"
exit 0
fi
[[ -d "$PROJECT_DIR" ]] || die "путь уже существует, но не является каталогом: $PROJECT_DIR"
# === ПОВТОРНЫЙ ЗАПУСК: генерируем установщик и деинсталлятор ===
INSTALL_SCRIPT="$PROJECT_DIR/install.sh"
UNINSTALL_SCRIPT="$PROJECT_DIR/uninstall.sh"
printf 'Генерация скриптов проекта «%s»...\n' "$PROJ_NAME"
# --- install.sh ---
cat > "$INSTALL_SCRIPT" <<'EOF_INSTALL'
#!/usr/bin/env bash
set -Eeuo pipefail
die() {
printf 'Ошибка: %s\n' "$*" >&2
exit 1
}
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd -P)
BUILD_DIR=$(cd -- "$SCRIPT_DIR/../.." && pwd -P)
CONFIG_DIR="$BUILD_DIR/config"
command -v rsync >/dev/null 2>&1 || die "не найдена программа rsync."
mkdir -p -- "$CONFIG_DIR"
# Содержимое проекта копируется в config/ сборщика. Служебные файлы самого
# проекта в сборку не попадают.
rsync -a --itemize-changes \
--exclude='/install.sh' \
--exclude='/uninstall.sh' \
--exclude='/README.md' \
--exclude='/.project-files' \
-- "$SCRIPT_DIR/" "$CONFIG_DIR/"
printf 'Установка проекта завершена: %s\n' "$CONFIG_DIR"
EOF_INSTALL
# --- uninstall.sh: сначала заголовок ---
cat > "$UNINSTALL_SCRIPT" <<'EOF_UNINSTALL'
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd -P)
BUILD_DIR=$(cd -- "$SCRIPT_DIR/../.." && pwd -P)
CONFIG_DIR="$BUILD_DIR/config"
[[ -d "$CONFIG_DIR" ]] || {
printf 'Каталог config не найден: %s\n' "$CONFIG_DIR" >&2
exit 1
}
# Удаляются только файлы и ссылки, перечисленные в данном проекте.
EOF_UNINSTALL
# Команды удаления формируются по текущему содержимому проекта.
# -print0 позволяет корректно обработать пробелы, кавычки и табуляцию в именах.
while IFS= read -r -d '' file; do
relative=${file#./}
printf 'rm -f -- "$CONFIG_DIR"/%q\n' "$relative" >> "$UNINSTALL_SCRIPT"
done < <(
cd -- "$PROJECT_DIR"
find . \( -type f -o -type l \) \
! -name 'install.sh' \
! -name 'uninstall.sh' \
! -name 'README.md' \
! -name '.project-files' \
-print0
)
# Удаляем только те каталоги, которые представлены в проекте, причём от самых
# глубоких к верхним. Непустые каталоги и чужие файлы остаются нетронутыми.
while IFS= read -r -d '' directory; do
relative=${directory#./}
[[ "$relative" == '.' ]] && continue
printf 'rmdir -- "$CONFIG_DIR"/%q 2>/dev/null || true\n' "$relative" >> "$UNINSTALL_SCRIPT"
done < <(
cd -- "$PROJECT_DIR"
find . -depth -type d -print0
)
cat >> "$UNINSTALL_SCRIPT" <<'EOF_UNINSTALL_END'
printf 'Удаление файлов проекта завершено.\n'
EOF_UNINSTALL_END
chmod +x -- "$INSTALL_SCRIPT" "$UNINSTALL_SCRIPT"
printf 'Скрипты install.sh и uninstall.sh созданы или обновлены в:\n%s\n' "$PROJECT_DIR"
Допустим, вы хотите добавить списки устанавливаемых пакетов в config/package-list/*
Заходите в каталог lbpkg и просто даёте команду ./make_project
Всплывшее окошко запросит название модуля.
Вы вводите название, допустим, add-mc
Будет создан каталог, который вы должны заполнить файлами.
Это и есть модуль.
Наше желание, установить дополнительно в систему файловый менеджер mc.
Создаём такую структуру:
Shell
.
└── add_mc
├── hooks
│ └── normal
│ └── 9110-configure-mc-mdview.hook.chroot
├── package-lists
│ └── mc.list.chroot
└── packages.chroot
└── mdview-mod_1.8.0_amd64.debmdview-mod_1.8.0_amd64.deb - локально устанавливаемый пакет, обеспечивает просмотр markdown файлов в браузере.
Модифицирован мною, в плане добавления *.desktop и иконки. В поисковике, если что, ищите mdview_1.8.0_amd64.deb.
9110-configure-mc-mdview.hook.chroot - патч для mc(с помощью sed), чтобы он по клавише enter открывал *.md в браузере.
После чего даём команду: ./make_project mdview_mc
Shell
.
├── make_project
└── mdview_mc
├── hooks
│ └── normal
│ └── 9110-configure-mc-mdview.hook.chroot
├── install.sh
├── package-lists
│ └── mc.list.chroot
├── packages.chroot
│ └── mdview-mod_1.8.0_amd64.deb
└── uninstall.shМодуль можно устанавливать, а можно удалять из проекта. Это главное, для чего он создавался.
На тупое критиканство - не отвечаю. Я только делюсь.
Примерно так.