Однажды возникла необходимость в подчинённом, в рамках имеющейся инфраструктуры центре сертификации для 'выездного' использования — создания временных технологических сертификатов во время различных разъездов. Необходимо было сделать его мобильным и разумно, для данных целей, защищённым. Приемлемым был признан вариант с загрузочной флешкой с каким-нибудь *nix + openssl и типовым сценарием использования — имеющийся под рукой компьютер перезагружаем, вставляем флешку, загружаемся с неё, работаем, вытаскиваем флешку, перезагружаем компьютер. Секретные ключи данного ЦС, его файлы конфигурации, ключевые файлы для двухфакторной аутентификации могут быть вынесены на отдельные носители.

Начался выбора дистрибутива *nix. Требования были следующие:

  • дистрибутив должен быть поддерживаемым в актуальном состоянии, в том числе и OpenSSL
  • наличие i386 версии. Гигабайты памяти нам не понадобятся, а вот возможность запуска на нетбуке с x86 Intel Atom будет полезна
  • запуск системы из оперативной памяти, либо корректный запуск и работа с r/o разделов. В идеале, корректная работа с флешки при включенной защите от записи (при использовании Qumo серии Yin & Yan)
  • возможность компактной инсталляции
  • желателен достаточно большой срок поддержки релиза

В результате в финал вышли два дистрибутива — Alpine Linux и OpenBSD. Всё бы хорошо, и не было бы смысла писать эту статью, как ВНЕЗАПНО уточнились требования к операционной системе — потребовалась полная поддержка русского текста в Unicode в системной консоли. На ввод и на вывод.

Ну всё, приплыли. Оба финалиста выбыли из соревнования. Alpine Linux в силу используемой в нём компактной библиотеки C, а OpenBSD… ну, у неё другие сильные стороны. Однако это требование позволило иначе взглянуть на доступные дистрибутивы, и в фаворитах неожиданно оказалась FreeBSD. Консольный драйвер vt (ранее известный как newcons) поддерживает Unicode «из коробки», русские шрифты идут в комплекте, вышеописанные критерии в сумме исполняются достаточно полно. Бонусом можно реализовать возможность оффлайнового бинарного обновления системы на флешке.

Началось изучение возможности компактной инсталляции стандартными способами. Готовые наборы — tinybsd, nanobsd, picobsd произвели впечатление «старый, древний, ещё древнее». Одни только расчёты секторов, головок, цилиндров для разных типов флешек в picobsd… Прям времена MFM, RLL, ST-506…

Творения отдельных энтузиастов, различные miniBSD, easyBSD, mfsBSD выглядели поинтереснее, но в итоге всё равно было решено поиграть в доброго доктора Франкенштейна.

В данном примере мы сделаем загрузочную флешку с:

  • компактной инсталляцией FreeBSD 10.1
  • русифицированной системной консолью в Unicode
  • корневым разделом, монтирующимся в памяти
  • разделом для скриптов и файлов конфигурации нашего ЦС
  • шифрованным разделом для секретных ключей ЦС
  • FAT разделом для обмена с внешними системами запросами на сертификат и подписанными сертификатами
  • возможностью оффлайнового обновления системы
  • возможностью выбора ядра при загрузке (минимальное + GENERIC)
  • возможностью выбора образа системы при загрузке (эталонный + обновлённый до последней версии FreeBSD)



Подчеркну — пример демонстрационный, в него включено всё что можно. В реальной жизни вышеуказанные возможности применяются, естественно, выборочно. Нет особого смысла один раздел монтировать в памяти, а другой, с той же флешки — вживую. И да, это будет не слишком похоже на «ядро + systemd + шелл + openssl», но не ставилась же цель уместить всё на дискету. Времена не те. И с fdformat.com и 800.com не влезет… Поэтому помимо программ, без которых не обойтись, были оставлены программы, которые могут пригодиться — по работе с текстом, с дисками, с архивами. К тому же, для обеспечения заявленной возможности обновления системы, сильно её уродовать (свернуть всё в один crunchgen бинарник и засунуть в ядро) нельзя — а то freebsd-update не признает.

Забегая вперёд — размер системы в максимальной конфигурации (два ядра + два образа) у нас составит 48 Мб, а в минимальной (одно ядро + один образ) — 7? Мб. Без установленных пакетов.

Возможность выбора ядра при загрузке оказалась очень удобна для отладки — если что-то не работает, то попеременно загружаясь с разными ядрами и одним и тем же образом системы можно определить в сборке чего проблема — ядра или мира.

Итак, начнём.

Нашу флешку будем делать на основе FreeBSD 10.1-RELEASE. Можно, конечно, -STABLE, но тогда не будет работать freebsd-update с бинарными обновлениями. В процессе работы мы скомпилируем мир и ядро, установим их в отдельный каталог, настроим систему, удалим ненужные файлы, после чего сделаем .iso и .img. Ничего сложного.

Создадим рабочее окружение. Можно в железе, но проще в виртуальной машине. Скачиваем FreeBSD-10.1-RELEASE-i386, устанавливаем, при выборе системных компонент включив установку исходных текстов. Дерево портов и игры не нужны. Загружаемся, заходим как root.

Подготовка


Всё будем делать из-под root.

Для удобства установим несколько переменных окружения, они будут использоваться далее по тексту. У пользователя root шелл по умолчанию csh, поэтому в /root/.login добавляем:
$ echo "setenv BASE /root/caBSD" >> /root/.login
$ echo "setenv WORKDIR /root/caBSD/_work" >> /root/.login

Да, нашу поделку назовём ЦСДПБ caBSD

Обновим систему до последнего patchlevel
$ freebsd-update fetch
$ freebsd-update install

$ reboot

Ядро, мир и исходники обновились. Перезагружаемся.

Создадим каталог /root/caBSD и рабочие подкаталоги в нём.
$ mkdir -p ${BASE}/{conf,tools,pkg.local}
$ mkdir -p ${WORKDIR}/{vanilla,custom,mnt}

/root/caBSD/_work/ — основной рабочий каталог, ${WORK}. Подкаталоги в нём:
vanilla/ — тут мы настраиваем корень будущей системы, installworld, installkernel
mnt/ — временная точка монтирования
custom/ — из содержимого этого каталога будем создавать .iso/.img образы

Компилируем мир и ядро


Создаём файл конфигурации для нашего ядра. На этапе отладки используем конфигурацию, базирующуюся на GENERIC и состоящую из нескольких строчек:
tools/CABSD-DEV
#
# tools/CABSD-DEV

include		GENERIC

ident		CABSD-DEV

# Без модулей
makeoptions	NO_MODULES=1

# Без драйвера консоли sc
nodevice	sc
nodevice	vga

# Так как модули не компилируем, то включаем необходимые в ядро
options 	TMPFS		# Efficient memory filesystem
options 	GEOM_ELI	# Disk encryption.
device		crypto		# core crypto support

options 	NO_SWAPPING	# Disable swapping of stack pages


Здесь мы берём GENERIC ядро, отключаем syscons (vt становится драйвером консоли по умолчанию), включаем три модуля — для тестирования загрузки системы вполне хватит.

Потом, когда всё заработает можно использовать минимальное ядро. Для него берём файл GENERIC ядра, удаляем всё лишнее (периодически компилируя и проверяя — грузится .iso или нет), оставляем необходимое и непонятное. Файл конфигурации нашего рабочего ядра стал выглядеть так:
tools/CABSD
#
# tools/CABSD

cpu		I486_CPU
cpu		I586_CPU
cpu		I686_CPU

ident		CABSD

makeoptions	NO_MODULES=1		# Don't compile modules

options 	SCHED_ULE		# ULE scheduler
options 	PREEMPTION		# Enable kernel thread preemption

options 	FFS			# Berkeley Fast Filesystem
#options 	SOFTUPDATES		# Enable FFS soft updates support
#options 	UFS_ACL			# Support for access control lists
options 	MD_ROOT			# MD is a potential root device
options		ROOTDEVNAME=\"ufs:/dev/md0\"	# The root device and filesystem type can be compiled in
options 	MSDOSFS			# MSDOS Filesystem
options 	CD9660			# ISO 9660 Filesystem
options 	TMPFS			# Efficient memory filesystem
#options 	NULLFS			# NULL filesystem
#options 	PROCFS			# Process filesystem (requires PSEUDOFS)
#options 	PSEUDOFS		# Pseudo-filesystem framework

options 	GEOM_PART_GPT		# GUID Partition Tables.
options 	GEOM_LABEL		# Provides labelization
options 	GEOM_ELI		# Disk encryption.

options 	COMPAT_FREEBSD4		# Compatible with FreeBSD4
options 	COMPAT_FREEBSD5		# Compatible with FreeBSD5
options 	COMPAT_FREEBSD6		# Compatible with FreeBSD6
options 	COMPAT_FREEBSD7		# Compatible with FreeBSD7
#options 	SCSI_DELAY=5000		# Delay (in ms) before probing SCSI
#options 	SYSVSHM			# SYSV-style shared memory
options 	SYSVMSG			# SYSV-style message queues
options 	SYSVSEM			# SYSV-style semaphores
options 	_KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions
options 	KBD_INSTALL_CDEV	# install a CDEV entry in /dev
options 	PROCDESC		# Support for process descriptors
#options 	INCLUDE_CONFIG_FILE	# Include this file in kernel
options 	NO_SWAPPING		# Disable swapping of stack pages

# To make an SMP kernel, the next two lines are needed
options 	SMP			# Symmetric MultiProcessor Kernel
device		apic			# I/O APIC

device		cpufreq			# CPU frequency control

# Bus support.
device		acpi
device		pci
#device		eisa

# Floppy drives
#device		fdc

# USB support https://www.freebsd.org/doc/en/books/handbook/usb-disks.html
device		scbus			# SCSI bus (required for ATA/SCSI)
device		da			# Direct Access (disks)

device		uhci			# UHCI PCI->USB interface
device		ohci			# OHCI PCI->USB interface
device		ehci			# EHCI PCI->USB interface (USB 2.0)
device		xhci			# XHCI PCI->USB interface (USB 3.0)
device		usb			# USB Bus (required)
device		umass			# Disks/Mass storage - Requires scbus and da

device		cd			# CD

device		ukbd			# Keyboard

device		ahci			# AHCI-compatible SATA controllers
device		ata			# Legacy ATA/SATA controllers
options 	ATA_STATIC_ID		# Static device numbering
#device		mvs			# Marvell 88SX50XX/88SX60XX/88SX70XX/SoC SATA
#device		siis			# SiliconImage SiI3124/SiI3132/SiI3531 SATA

# atkbdc0 controls both the keyboard and the PS/2 mouse
device		atkbdc			# AT keyboard controller
device		atkbd			# AT keyboard

# Без этого не будет работать kbdcontrol (keymap, keyrate)
device		kbdmux			# keyboard multiplexer

# vt is the new video console driver
device		vt
device		vt_vga

options		TERMINAL_NORM_ATTR=(FG_GREEN|BG_BLACK)

options 	MAXCONS=4		# number of virtual consoles

# Number of initial kernel page table pages used for early bootstrap.
# This number should include enough pages to map the kernel, any
# modules or other data loaded with the kernel by the loader, and data
# structures allocated before the VM system is initialized such as the
# vm_page_t array.  Each page table page maps 4MB (2MB with PAE).
# Может понадобиться увеличить, но вроде работает и так
#options		NKPT=31

device		crypto			# core crypto support

# Pseudo devices.
device		loop			# Network loopback
device		random			# Entropy device
#device		padlock_rng		# VIA Padlock RNG
device		rdrand_rng		# Intel Bull Mountain RNG
#device		ether			# Ethernet support
device		md			# Memory "disks"

# EOF


Создаём файл настроек для компиляции мира. Читаем man src.conf, выписываем все WITHOUT_ в файл и вдумчиво изучаем. Приходит понимание, что понадобится не один, а целых два файла настроек — один для buildworld, второй плюсом для installworld.
tools/worldbuild.conf
#
# tools/worldbuild.conf
#
WITHOUT_ACCT=
WITHOUT_ACPI=
WITHOUT_AMD=
WITHOUT_APM=
WITHOUT_ARM_EABI=
WITHOUT_ASSERT_DEBUG=
WITHOUT_AT=
WITHOUT_ATM=
WITHOUT_AUDIT=
WITHOUT_AUTHPF=
# Эту опцию включаем в tools/worldinstall.conf
# На этапе компиляции она бесполезна
# WITHOUT_BINUTILS=
WITHOUT_BLUETOOTH=
WITHOUT_BMAKE=
WITHOUT_BSD_CPIO=
WITHOUT_BSNMP=
WITHOUT_CALENDAR=
WITHOUT_CAPSICUM=
WITHOUT_CDDL=
WITHOUT_CLANG=
WITHOUT_CLANG_EXTRAS=
WITHOUT_CLANG_FULL=
WITHOUT_CPP=
# Не включаем, так как на libcrypt завязаны geli и openssl
# WITHOUT_CRYPT=
WITHOUT_CTM=
WITHOUT_CXX=
WITHOUT_DICT=
WITHOUT_EXAMPLES=
WITHOUT_FDT=
WITHOUT_FLOPPY=
WITHOUT_FMTREE=
# Не вкючаем, иначе сломается загрузочное меню
# WITHOUT_FORTH=
# Не включаем, понадобится по условиям задачи
# WITHOUT_FREEBSD_UPDATE=
WITHOUT_GAMES=
WITHOUT_GCC=
WITHOUT_GCOV=
WITHOUT_GDB=
WITHOUT_GNU=
WITHOUT_GNUCXX=
WITHOUT_GNU_SUPPORT=
WITHOUT_GPIB=
WITHOUT_GPIO=
WITHOUT_GROFF=
WITHOUT_GSSAPI=
WITHOUT_HTML=
WITHOUT_HYPERV=
WITHOUT_ICONV=
WITHOUT_INET=
WITHOUT_INET_SUPPORT=
WITHOUT_INET6=
WITHOUT_INET6_SUPPORT=
WITHOUT_INFO=
# Эту опцию включаем в tools/worldinstall.conf
# WITHOUT_INSTALLLIB=
WITHOUT_IPFILTER=
WITHOUT_IPFW=
WITHOUT_IPX=
WITHOUT_IPX_SUPPORT=
WITHOUT_JAIL=
WITHOUT_KDUMP=
WITHOUT_KERBEROS=
WITHOUT_KERBEROS_SUPPORT=
WITHOUT_KERNEL_SYMBOLS=
WITHOUT_LDNS=
WITHOUT_LDNS_UTILS=
# Не включаем, системная консоль нужна
# WITHOUT_LEGACY_CONSOLE=
# А это имеет смысл только для amd64, а у нас i386
# WITHOUT_LIB32=
WITHOUT_LIBCPLUSPLUS=
# Не включаем, несколько локалей нам понадобятся.
# Лишние потом удалим, оставим только нужные
# WITHOUT_LOCALES=
WITHOUT_LOCATE=
WITHOUT_LPR=
WITHOUT_LS_COLORS=
WITHOUT_MAIL=
WITHOUT_MAILWRAPPER=
WITHOUT_MAKE=
WITHOUT_MAN=
WITHOUT_MAN_UTILS=
# libncurces используется редакторами /usr/bin/vi и jupp из пакетов.
# WITHOUT_NCURSESW=
WITHOUT_NDIS=
WITHOUT_NETCAT=
WITHOUT_NETGRAPH=
WITHOUT_NETGRAPH_SUPPORT=
WITHOUT_NIS=
WITHOUT_NLS=
WITHOUT_NLS_CATALOGS=
WITHOUT_NMTREE=
WITHOUT_NS_CACHING=
WITHOUT_NTP=
WITHOUT_OPENSSH=
# Если включить опцию, то openssl компилироваться не будет,
# придётся его ставить из пакетов. Но в нашем случае
# используем openssl из комплекта
# WITHOUT_OPENSSL=
WITHOUT_PAM_SUPPORT=
WITHOUT_PC_SYSINSTALL=
WITHOUT_PF=
WITHOUT_PKGBOOTSTRAP=
WITHOUT_PMC=
WITHOUT_PORTSNAP=
WITHOUT_PPP=
WITHOUT_PROFILE=
WITHOUT_QUOTAS=
WITHOUT_RCMDS=
WITHOUT_RCS=
# Тема /rescue будет рассмотрена отдельно
WITHOUT_RESCUE=
WITHOUT_ROUTED=
WITHOUT_SENDMAIL=
WITHOUT_SHAREDOCS=
WITHOUT_SOURCELESS=
WITHOUT_SOURCELESS_UCODE=
WITHOUT_SOURCELESS_HOST=
WITHOUT_SSP=
WITHOUT_SVNLITE=
# syscons нам не нужен, мы будем использовать newcons - vt(4)
WITHOUT_SYSCONS=
WITHOUT_SYSINSTALL=
# В качестве шелла используем tcsh
# WITHOUT_TCSH=
WITHOUT_TELNET=
WITHOUT_TESTS=
WITHOUT_TEXTPROC=
# Эту опцию включаем в tools/worldinstall.conf
# На этапе компиляции она бесполезна
# WITHOUT_TOOLCHAIN=
WITHOUT_UNBOUND=
WITHOUT_USB=
WITHOUT_UTMPX=
# Не включаем, vt нам как раз и нужен
# WITHOUT_VT=
WITHOUT_WIRELESS=
WITHOUT_WIRELESS_SUPPORT=
WITHOUT_WPA_SUPPLICANT_EAPOL=
WITHOUT_ZFS=
# Не включаем, файлы tz нам понадобятся.
# Лишние потом удалим, оставим только нужные
# WITHOUT_ZONEINFO=



tools/worldinstall.conf
#
# tools/worldinstall.conf
#

# Set to not install binutils (as, c++-filt, gconv, ld, nm, objcopy, objdump, readelf, size and strip)
WITHOUT_BINUTILS=

# Set this if you do not want to install optional libraries. For example when creating a nanobsd(8) image.
WITHOUT_INSTALLLIB=

# Set to not install programs used for program development, compilers, debuggers etc. The option does not work for build targets.
# When set, it also enforces the following options:
# WITHOUT_BINUTILS, WITHOUT_CLANG, WITHOUT_CLANG_EXTRAS, WITHOUT_CLANG_FULL, WITHOUT_GCC, WITHOUT_GDB
WITHOUT_TOOLCHAIN=


С такими опциями мы отключаем компиляцию как clang, так и gcc.

Приступаем к компиляции. На первом же шаге немного схитрим. При компиляции мира очень много времени уходит на компиляцию компилятора, но у нас же есть готовый, в базовой системе. К тому же только что обновлённый. Им мы и воспользуемся. Устанавливаем переменные

$ setenv CC `whereis -bq clang`
$ setenv CPP `whereis -bq clang-cpp`
$ setenv CXX `whereis -bq clang++`

Создаём символьную ссылку на файл конфигурации ядра CABSD в соответствующий каталог (в данном примере пропустим отладочный этап с CABSD-DEV, сразу будем делать конечный вариант ядра).

$ ln -sf ${BASE}/tools/CABSD /usr/src/sys/i386/conf

Компилируем. Дополнительные опции вида '-j N' по желанию

$ cd /usr/src

# rm -rf /usr/obj

# Мир
$ make buildworld USER=IT HOSTNAME=hq.example.net SRCCONF=${BASE}/tools/worldbuild.conf __MAKE_CONF=/dev/null

# Ядро
$ make buildkernel USER=IT HOSTNAME=hq.example.net SRCCONF=${BASE}/tools/worldbuild.conf __MAKE_CONF=/dev/null KERNCONF=CABSD

$ cd ${BASE}

Параметры USER и HOSTNAME декоративные — можно указать что-то своё, чтобы в новой системе при загрузке, при uname -a, да и в других местах выводилось гордое «IT@hq.example.net» а не «root@localhost».

Теперь устанавливаем систему в рабочий каталог. Обратите внимание — между этапами компиляции и установки не стоит посредством freebsd-update обновлять систему или выключать параметры в файле worldbuild.conf.

$ cd /usr/src

# Устанавливаем мир. Второй файл с параметрами мы указываем через параметр __MAKE_CONF - фактически он приплюсуется к первому.
$ make installworld DESTDIR=${WORKDIR}/vanilla SRCCONF=${BASE}/tools/worldbuild.conf __MAKE_CONF=${BASE}/tools/worldinstall.conf

# Устанавливаем etc
$ make distribution DESTDIR=${WORKDIR}/vanilla SRCCONF=${BASE}/tools/worldbuild.conf __MAKE_CONF=${BASE}/tools/worldinstall.conf

# Устанавливаем наше минимальное ядро
$ make installkernel DESTDIR=${WORKDIR}/vanilla SRCCONF=${BASE}/tools/worldbuild.conf __MAKE_CONF=${BASE}/tools/worldinstall.conf KERNCONF=CABSD

# Копируем второе ядро, GENERIC - берём его из /boot/kernel базовой системы
# Устанавливаем во временный каталог
$ mkdir -p ${WORKDIR}/vanilla/tmp/boot/kernel
# И почти 800 модулей в придачу. Потом удалим лишние.
$ cp -p /boot/kernel/{kernel,*.ko} ${WORKDIR}/vanilla/tmp/boot/kernel/

$ chflags -R noschg ${WORKDIR}/vanilla

$ cd ${BASE}

Если не включать опцию WITHOUT_RESCUE= при компиляции мира, то мы можем заменить файлы в bin, sbin, usr/bin, usr/sbin на их аналоги из rescue, хотя целесообразность этого не очевидна. Вся система у нас находится на одном разделе, при загрузке монтирующемся в памяти — поэтому варианты с несмонтировавшемся usr практически исключены, и смысл замены может быть лишь в возможности уменьшить размер системы за счёт удаления библиотек, ставших ненужными из-за того, что использующие их программы заменены на слинкованный статически rescue. Впрочем, экономию надо просчитывать, а в нашем примере мы rescue компилировать не будем.

Если будете заменять, то можно использовать скрипт (подсмотрен в mfsBSD)
tools/_linkrescue.sh
#!/bin/csh

#
# tools/_linkrescue.sh

cd ${WORKDIR}/vanilla

echo "Before:"

du -hc bin sbin usr/bin usr/sbin rescue

foreach FILE (`ls rescue`)
	if ( -f bin/${FILE} ) then
		ln -f rescue/${FILE} bin/${FILE}
	else if ( -f sbin/${FILE} ) then
		ln -f rescue/${FILE} sbin/${FILE}
	else if ( -f usr/bin/${FILE} ) then
		ln -f rescue/${FILE} usr/bin/${FILE}
	else if ( -f usr/sbin/${FILE} ) then
		ln -f rescue/${FILE} usr/sbin/${FILE}
	endif
end

echo "After:"

du -hc bin sbin usr/bin usr/sbin rescue

cd ${BASE}


Устанавливаем пакеты


Для удобства операторов нашего ЦС мы установим файловый менеджер. Популярный mc занимает 158 Мб, mc-light — 6 Мб, deco — 217 Mb со всеми зависимостями. Если ставить из портов, то, вероятно, можно было бы немного уменьшить размер с помощью опций компиляции, но в пакетах нашелся компактный консольный файловый менеджер clex — 0.3 Мб, который мы и установим.

Второй момент — редактор с поддержкой Unicode. Формально, по букве задания, поддержка была обеспечена — русский текст ввести в консоли в командной строке csh можно (echo привет), вывести на экран тоже (cat /usr/bin/help), но хотелось обеспечить более комфортную работу с текстами — отредактировать какой-нибудь файл, вести записи, мало ли. Комплектный /usr/bin/ee русский текст в Unicode файле показывает как "-PM-7M-PM-?M", /usr/bin/vi всё показывает корректно (правда он требует библиотеку libncursesw, поэтому придётся выключить опцию WITHOUT_NCURSESW= в tools/worldbuild.conf), /rescue/vi — "\xd0\xb7\xd0\xb0".

В пакетах нашлись несколько небольших редакторов с поддержкой Unicode, в частности: puff, отображающий текст как «запи~Аи в об~@аз» (в оригинале «записи в образ»); le — всем хорош, но требует библиотеки C++; и jupp, который всё показывает корректно, и, как и vi требует libncurses. Причём jupp представляет собой переработанную версию редактора joe, но если оригинальный joe из пакетов тянет за собой 49 Мб зависимостей, то jupp размером с vi.

Причём все проблемы у редакторов ee и puff лишь с отображением русских букв, при вводе «вслепую» всё сохраняется корректно.

В итоге проблема выбора между комплектным vi и jupp из пакетов была переложена на плечи потенциальных пользователей, которые проголосовали за jupp — понравилась подсветка синтаксиса и сравнительная дружелюбность редактора.

Устанавливаем misc/clex и editors/jupp. Какие-то пользовательские пакеты можно положить в каталог ${BASE}/pkg.local/, при установке они добавятся.

# Пакеты устанавливаем в два захода - сначала скачиваем их, добавляем
# свои, если есть, а затем устанавливаем в chroot окружении.

# Устанавливаем pkg в базовую систему
$ pkg -N || env ASSUME_ALWAYS_YES=YES pkg bootstrap

# БД пакетов держим в каталоге внутри ${WORKDIR}, чтобы не пересекалась с БД из базовой системы
$ mkdir -p ${WORKDIR}/pkg.db
$ setenv PKG_DBDIR ${WORKDIR}/pkg.db

# Обновим БД
$ pkg-static update

# Пакеты пока будут внутри ${WORKDIR}/vanilla, устанавливать их будем в chroot
$ mkdir -p ${WORKDIR}/vanilla/tmp/pkg.files/All

# Скачиваем пакеты, зависимости разрешаются автоматически.
$ pkg-static fetch --output ${WORKDIR}/vanilla/tmp/pkg.files --dependencies --yes misc/clex editors/jupp
$ unsetenv PKG_DBDIR

# Добавляем пакеты из локального каталога (${BASE}/pkg.local).
# Удовлетворение зависимостей - на совести пользователя
$ install -m 0644 ${BASE}/pkg.local/* ${WORKDIR}/vanilla/tmp/pkg.files/All

# Если это не первый запуск, то удалим БД с информацией об установленных пакетах
$ rm -f ${WORKDIR}/vanilla/var/db/pkg/local.sqlite

# Устанавливаем пакеты
$ cd ${WORKDIR}/vanilla
$ find tmp/pkg.files/All/* -type f -exec pkg-static -c ${WORKDIR}/vanilla add {} \;

$ cd ${BASE}

Настраиваем систему


В chroot окружении создаём пользователя 'ca'. Для возможности монтирования устройств добавляем в группу operator. В качестве шелла используем csh, опция WITHOUT_TCSH в tools/worldbuild.conf не включена.

$ chroot ${WORKDIR}/vanilla pw useradd ca -c "Certification authority operator" -m -G operator -s /bin/csh

# Получаем и сохраняем в файл числовые UID и GID (по идее 1001:1001, но мало ли...)
$ chroot ${WORKDIR}/vanilla id -u ca > ${WORKDIR}/ca.uid
$ chroot ${WORKDIR}/vanilla id -g ca > ${WORKDIR}/ca.gid

# Включаем пользователю UTF-8 в системной консоли
$ echo "setenv MM_CHARSET UTF-8" >> ${WORKDIR}/vanilla/home/ca/.login
$ echo "setenv LANG en_US.UTF-8" >> ${WORKDIR}/vanilla/home/ca/.login

# Добавляем в конец path каталог со скриптами ЦС (раздел с ними будем монтировать в каталог /ca)
# Необходимо убедиться, что в системе установлен лишь один openssl - либо из пакетов, либо системный.
$ echo "set path = (/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin /ca)" >> ${WORKDIR}/vanilla/home/ca/.cshrc

# vi мы не используем, устанавливаем свою переменную EDITOR
$ echo "setenv EDITOR jupp" >> ${WORKDIR}/vanilla/home/ca/.cshrc

# Можно добавить переменные, используемые в конфигурации ЦС
$ echo "setenv OPENSSL_CONF /ca/conf/ca.conf" >> ${WORKDIR}/vanilla/home/ca/.login
$ echo "setenv CA_OID 1.3.6.1.4.1.nnnnn" >> ${WORKDIR}/vanilla/home/ca/.login

Ещё у нас есть скрипт conf/ca.sh, который мы запустим в chroot окружении из-под пользователя 'ca' для выполнения некоторых действий в домашнем каталоге пользователя с его правами.

conf/ca.sh
#!/bin/sh -ex
##
# conf/ca.sh
##

cd ~

# Здесь мы из-под пользователя 'ca' делаем какие-нибудь вещи

# 1. Создадим файл конфигурации для файлового менеджера clex

mkdir -p ~/.config/clex

cat << EOF > ~/.config/clex/config
#
# CLEX configuration file
#
CMD_F3=more -- $f
CMD_F4=jupp -- $f
CMD_F5=cp -ir -- $f $2
CMD_F6=mv -i -- $f $2
CMD_F7=mkdir -- 
CMD_F8=rm -- $f
EOF

# 2. Создадим файл конфигурации для vi

#cat << EOF > ~/.nexrc
#set verbose showmode
#set nomesg
#EOF

# 3. Создадим файл .logout, он потребуется позже
touch .logout


Данный скрипт необходимо запустить до чистки системы, пока в ${WORKDIR}/vanilla ещё есть su.

# Устанавливаем conf/ca.sh с правами r-xr-xr-x в ${WORKDIR}/vanilla/home/ca
$ install -m 0555 -o root -g wheel conf/ca.sh ${WORKDIR}/vanilla/home/ca/

# Запускаем
$ chroot ${WORKDIR}/vanilla su - ca -c /home/ca/ca.sh

# Удаляем
$ rm -f ${WORKDIR}/vanilla/home/ca/ca.sh

Создаём файлы конфигурации. Все файлы лежат в каталоге ${BASE}/conf, откуда мы их потом установим в нужные места.
conf/fstab
##
# conf/fstab
##
/dev/md0		/	ufs	rw					0 0

# 50 Мб на /tmp.
tmpfs			/tmp	tmpfs	rw,mode=1777,size=50m,noexec 		0 0

# noauto
/dev/gpt/ca		/ca		ufs	rw,noauto,noatime		0 0
/dev/gpt/keys.eli	/ca.keys	ufs	ro,noauto,noatime		0 0
/dev/gpt/exchange	/ca.certs	msdosfs	rw,longnames,-u=ca,-g=ca,-m=0640,-M=0750,noauto,noatime	0 0


Корневой раздел у нас монтируется из mfs, а /tmp в tmpfs. Можно /tmp отдельно не монтировать, оставить в корневом разделе, но тогда при создании флешки необходимо выделить больше свободного места для него внутри образа системы. При обновлении системы /tmp используется freebsd-update для извлечения файлов, самый большой из них в нашей конфигурации это GENERIC ядро, около 17 Мб, поэтому сильно маленьким этот раздел лучше не делать.

Раздел /dev/gpt/ca предназначен для скриптов и файлов конфигурации нашего ЦС, /dev/gpt/keys.eli — шифрованный с помощью geli раздел /dev/gpt/keys с секретными ключами, /dev/gpt/exchange — FAT раздел для обмена с внешними системами. Его мы монтируем с поддержкой длинных имён, но без поддержки их перекодировки — принято принципиальное решение об использовании лишь ASCII символов в именах файлов на этом разделе. Поэтому и систему, и ядро компилируем без поддержки iconv.

Эти разделы мы автоматически при старте не монтируем (параметр noauto), так как при каких-то неполадках (трижды неправильно введённый пароль для geli, например, что не позволит создать раздел keys.eli) нас выкинет в однопользовательский режим. У оператора будет шок и моральная травма. Поэтому эти разделы будем монтировать в userspace пользователя 'ca', для чего при создании включим его в группу operator и настройками devfs и sysctl (ниже) предоставим необходимые полномочия.

Обратите внимание — в fstab раздел с ключами для ЦС монтируется в режиме только для чтения, поэтому перед созданием ключей надо не забыть перемонтировать его для записи (mount -u -o rw /dev/gpt/keys.eli).

# Добавляем автоматическое монтирование разделов ЦС. Применимо только для флешки,
# при загрузке с CD будут выскакивать ошибки об отсутствующих разделах
$ echo "mount /dev/gpt/ca" >> ${WORKDIR}/vanilla/home/ca/.login
$ echo "mount /dev/gpt/keys.eli" >> ${WORKDIR}/vanilla/home/ca/.login
$ echo "mount /dev/gpt/exchange" >> ${WORKDIR}/vanilla/home/ca/.login

# И размонтирование
$ echo "umount /dev/gpt/ca" >> ${WORKDIR}/vanilla/home/ca/.logout
$ echo "umount /dev/gpt/keys.eli" >> ${WORKDIR}/vanilla/home/ca/.logout
$ echo "umount /dev/gpt/exchange" >> ${WORKDIR}/vanilla/home/ca/.logout

Обратите внимание — если файлы .login и .cshrc уже существовали в каталоге пользователя 'ca', и мы могли дописывать туда без опасения порчи владельца или прав, то файл .logout при заведении пользователя не создавался. Поэтому, если мы не хотим, чтобы его владельцем оказался root:ca, необходимо создать его из-под пользователя ca, хотя бы с помощью скрипта conf/ca.sh

Теперь мы предоставим возможность нашему пользователю 'ca' возможность монтировать сторонние флешки и разделы на нашей флешке (с настройками ЦС, ключами, разделом обмена).

conf/devfs.rules
##
# conf/devfs.rules
##
[localrules=5]
# Правило для нашей мультифлешки
# '/dev/' не указываем
add path 'gpt/*' group operator mode 0660
# Правило для обычных флешек
add path 'da*' group operator mode 0660


Флешку будем разбивать с использованием GPT, её разделы адресовать по меткам вида /dev/gpt/label

conf/sysctl.conf
##
# conf/sysctl.conf
##

# allow regular users to mount file systems
vfs.usermount=1


Устанавливаем переменную в sysctl.conf

conf/ttys
##
# conf/ttys
##

#
# $FreeBSD: releng/10.1/etc/etc.i386/ttys 267236 2014-06-08 17:50:07Z nwhitehorn $
#	@(#)ttys	5.1 (Berkeley) 4/17/89
#
console	none				unknown	off secure

# Терминал с автологином 'ca'
ttyv0	"/usr/libexec/getty CA"		xterm	on  secure

# Терминал для 'root'
ttyv1	"/usr/libexec/getty Pc"		xterm	on  secure


Для системной консоли при старте запускаем два терминала (оба должны быть xterm). Первый — с автологином пользователя 'ca' (см. описание gettytab ниже), второй для 'root', обычный вход с запросом пароля.

conf/gettytab
##
# conf/gettytab
##

# $FreeBSD: releng/10.1/etc/gettytab 241708 2012-10-18 22:20:02Z peterj $
#	from: @(#)gettytab	5.14 (Berkeley) 3/27/91
#
default:	:cb:ce:ck:lc:fd#1000:im=\r\n%s/%m (%h) (%t)\r\n\r\n:sp#1200:	:if=/etc/issue:

P|Pc|Pc console:	:ht:np:sp#9600:

# Прописываем автологин для пользователя 'ca'
CA:	:al=ca:tc=Pc:


В настройках оставляем лишь несколько секций — default, Pc — для пользователя 'root' и создаём новую секцию CA, в которой прописываем автологин пользователя 'ca', а остальное наследуется из Pc и default.

Далее создаём файл issue, в котором сделаем напоминание о необходимости проверить установленное время на том компьютере, с которого загрузились. Интернет не предусмотрен by design, поэтому часики будем при необходимости устанавливать вручную. Благо, для данного ЦС точность плюс-минус минута абсолютно не критична.

conf/issue
====^^^=^^=^^=^^=^^==
== Проверьте время ==


Берём файл etc/defaults/rc.conf, копируем в наш conf/ и начинаем отключать всё лишнее.

conf/rc.conf
##
# conf/rc.conf
##

#
# $FreeBSD: releng/10.1/etc/defaults/rc.conf 273188 2014-10-16 22:00:24Z hrs $

##############################################################
###  Important initial Boot-time options  ####################
##############################################################

rc_debug="NO"		# Set to YES to enable debugging output from rc.d
rc_info="YES"		# Enables display of informational messages at boot.
devd_enable="NO" 	# Run devd, to trigger programs on device tree changes.
devd_flags=""		# Additional flags for devd(8).

gptboot_enable="YES"	# GPT boot success/failure reporting.

# GELI disk encryption configuration.
geli_devices="gpt/keys"	# List of devices to automatically attach in addition to
			# GELI devices listed in /etc/fstab.
geli_tries=""		# Number of times to attempt attaching geli device.
			# If empty, kern.geom.eli.tries will be used.
geli_default_flags=""	# Default flags for geli(8).
geli_autodetach="YES"	# Automatically detach on last close.
			# Providers are marked as such when all file systems are
			# mounted.
# Example use.
#geli_devices="da1 mirror/home"
#geli_da1_flags="-p -k /etc/geli/da1.keys"
#geli_da1_autodetach="NO"
#geli_mirror_home_flags="-k /etc/geli/home.keys"

fsck_y_enable="NO"	# Set to YES to do fsck -y if the initial preen fails.
fsck_y_flags=""		# Additional flags for fsck -y
# Разделы на флешке создали без поддержки снапшотов, поэтому параметр устанавливаем в "NO"
background_fsck="NO"	# Attempt to run fsck in the background where possible.

##############################################################
###  Network configuration sub-section  ######################
##############################################################

### Basic network and firewall/security options: ###
hostname="root2sub1"		# Set this!
hostid_enable="NO"		# Set host UUID.

#network_interfaces="lo0"	# List of network interfaces (or "auto").
#ifconfig_lo0="inet 127.0.0.1"	# default loopback device configuration.
network_interfaces=""		# List of network interfaces (or "auto").

### Network daemon (miscellaneous) ###
hostapd_enable="NO"		# Run hostap daemon.
syslogd_enable="NO"		# Run syslog daemon (or NO).

### IPv6 options: ###
ip6addrctl_enable="NO"		# Set to YES to enable default address selection
ipv6_network_interfaces="none"	# List of IPv6 network interfaces
				# (or "auto" or "none").

##############################################################
###  Mail Transfer Agent (MTA) options  ######################
##############################################################

# Settings for /etc/rc.sendmail and /etc/rc.d/sendmail:
sendmail_enable="NO"	# Run the sendmail inbound daemon (YES/NO).

##############################################################
###  Miscellaneous administrative options  ###################
##############################################################

# Это для того, чтоб юзер 'ca' мог монтировать флешки
devfs_rulesets="/etc/defaults/devfs.rules /etc/devfs.rules" # Files containing
							    # devfs(8) rules.
devfs_system_ruleset="localrules"	# The name (NOT number) of a ruleset to apply to /dev
devfs_set_rulesets=""	# A list of /mount/dev=ruleset_name settings to
			# apply (must be mounted already, i.e. fstab(5))
devfs_load_rulesets="YES"	# Enable to always load the default rulesets

cron_enable="NO"	# Run the periodic job daemon.
crashinfo_enable="NO"	# Automatically generate crash dump summary.

kern_securelevel_enable="NO"	# kernel security level (see security(7))
kern_securelevel="-1"	# range: -1..3 ; `-1' is the most insecure
			# Note that setting securelevel to 0 will result
			# in the system booting with securelevel set to 1, as
			# init(8) will raise the level when rc(8) completes.

update_motd="NO"	# update version info in /etc/motd (or NO)
entropy_file="NO"	# Set to NO to disable caching entropy through reboots.
			# /var/db/entropy-file is preferred if / is not avail.
dmesg_enable="YES"	# Save dmesg(8) to /var/run/dmesg.boot

newsyslog_enable="NO"	# Run newsyslog at startup.
mixer_enable="NO"	# Run the sound mixer.


##############################################################
###  System console options  #################################
##############################################################

#keyboard=""		# keyboard device to use (default /dev/kbd0).
keymap="ru.win"		# keymap in /usr/share/{syscons,vt}/keymaps/* (or NO).
keyrate="fast"		# keyboard rate to: slow, normal, fast (or NO).
#keybell="NO" 		# See kbdcontrol(1) for options.  Use "off" to disable.
#keychange="NO"		# function keys default values (or NO).
#cursor="NO"		# cursor type {normal|blink|destructive} (or NO).
#scrnmap="NO"		# screen map in /usr/share/syscons/scrnmaps/* (or NO).
#font8x14="NO"		# font 8x14 from /usr/share/{syscons,vt}/fonts/* (or NO).
#font8x8="NO"		# font 8x8 from /usr/share/{syscons,vt}/fonts/* (or NO).
blanktime="NO"		# blank time (in seconds) or "NO" to turn it off.
moused_nondefault_enable="NO" # Treat non-default mice as enabled unless
			       # specifically overriden in rc.conf(5).

# Сюда, при наличии своих шрифтов будет дописана строка font8x16="xxx.fnt"


Секция с параметрами geli_ понадобится позже. Прописываем hostname. Указываем параметры для devfs — чтобы подтягивались наши локальные правила для монтирования флешек. Стоит иметь в виду, что freebsd-update не работает при установке kern.securelevel > 0. И, напоследок, в секции System console options настроим переключение на русский и свойства клавиатуры. Файл русского шрифта здесь мы не указываем — если есть свой, то добавим позже, если своего нету — то пусть используется системный

conf/hosts
##
# conf/hosts
##
127.0.0.1	localhost


Пусть будет.

conf/termcap
#
# conf/termcap
#

#	@(#)termcap.src	8.2 (Berkeley) 11/17/93
# $FreeBSD: releng/10.1/share/termcap/termcap.src 267734 2014-06-22 16:48:21Z gavin $

xterm|X11 terminal emulator:	:tc=xterm-new:
# To add a termcap entry under FreeBSD for a new terminal type, insert
# the entry in the appropriate location in /etc/termcap then issue this
# command:
#
#  cap_mkdb /etc/termcap
#
xterm-clear:	:te=\E[?1049l:ti=\E[?1049h:	:tc=xterm-new:
xterm-new|modern xterm:	:@7=\EOF:@8=\EOM:F1=\E[23~:F2=\E[24~:K2=\EOE:Km=\E[M:	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:k6=\E[17~:	:k7=\E[18~:k8=\E[19~:k9=\E[20~:k;=\E[21~:kI=\E[2~:	:kN=\E[6~:kP=\E[5~:kd=\EOB:kh=\EOH:kl=\EOD:kr=\EOC:ku=\EOA:	:tc=xterm-basic:
#
# This chunk is used for building the VT220/Sun/PC keyboard variants.
xterm-basic|modern xterm common:	:am:bs:km:mi:ms:ut:xn:AX:	:Co#8:co#80:kn#12:li#24:pa#64:	:AB=\E[4%dm:AF=\E[3%dm:AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:	:DO=\E[%dB:LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:ae=\E(B:al=\E[L:	:as=\E(0:bl=^G:cd=\E[J:ce=\E[K:cl=\E[H\E[2J:	:cm=\E[%i%d;%dH:cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:	:ei=\E[4l:ho=\E[H:im=\E[4h:is=\E[!p\E[?3;4l\E[4l\E>:	:kD=\E[3~:kb=^H:ke=\E[?1l\E>:ks=\E[?1h\E=:kB=\E[Z:le=^H:md=\E[1m:	:me=\E[m:ml=\El:mr=\E[7m:mu=\Em:nd=\E[C:op=\E[39;49m:	:rc=\E8:rs=\E[!p\E[?3;4l\E[4l\E>:sc=\E7:se=\E[27m:sf=^J:	:so=\E[7m:sr=\EM:st=\EH:	:ue=\E[24m:up=\E[A:us=\E[4m:ve=\E[?12l\E[?25h:vi=\E[?25l:vs=\E[?12;25h:

#
# END OF TERMCAP
# ------------------------


Сделаем урезанную версию файла termcap, оставим там только xterm* терминалы — это позволит значительно уменьшить размер termcap.db.

conf/freebsd-update.conf
##
# conf/freebsd-update.conf
##
# $FreeBSD: releng/10.1/etc/freebsd-update.conf 258121 2013-11-14 09:14:33Z glebius $

# Trusted keyprint.
KeyPrint 800651ef4b4c71c27e60786d7b487188970f4b4169cc055784e21eb71d410cc5

# Server or server pool from which to fetch updates.
ServerName update.FreeBSD.org

# Components of the base system which should be kept updated.
Components world kernel

# Directory in which to store downloaded updates and temporary
# files used by FreeBSD Update.
WorkDir /mnt

# When installing a new kernel perform a backup of the old one first
# so it is possible to boot the old kernel in case of problems.
BackupKernel no


В нашем случае флешку с обновлениями мы будем монтировать в /mnt. При обновлении ядра старое не сохраняем.

Движимые человеколюбием к потенциальным операторам системы, которые вовсе не профессиональные пользователи FreeBSD, создадим ещё несколько файлов:

conf/motd
Для вызова справки введите 'help'

Переключение раскладки клавиатуры - Ctrl+Shift


И два файла — help и manpath, которые мы потом разместим в usr/bin

conf/help
#!/bin/sh
##
# conf/help
##

cat << EOF
Для записи изменений в образ mfs2:
$ mount /dev/gpt/system /mnt
$ mdconfig -a -t vnode -f /mnt/mfs2 -u 1
$ mount /dev/md1 /media
... Записываем в /mеdia ...
$ umount /media
$ mdconfig -d -u 1
$ umount /mnt

Для монтирования MS DOS
(as root) $ mount_msdosfs -u ca -g ca -m 0640 -M 0750 -l /dev/da1s1 /mnt
(as user) $ mount_msdosfs -m 0640 -M 0750 -l /dev/da1s1 ~/mnt

Для конвертации DOS строк (CR/LF) в Unix формат:
$ tr -d '\r' < in.txt > out.txt

Для установки времени
(as root) $ date ГГММДДччмм.сс
и проверить временную зону

Создать memory disk
$ mount -t tmpfs -o size=100m tmpfs ~/mnt
EOF


Так как man страниц в комплекте не будет, то сделаем небольшой help

conf/manpath
#!/bin/sh
##
# conf/manpath
##

# Так как систему компилировали с WITHOUT_MAN=, то утилита manpath отсутствует, а без неё не работает whereis

echo ""


Второй файл, manpath — это заглушка для обхода неправильного поведения утилиты whereis, которая, даже при указании поиска только по двоичным файлам ('-b') завершается с ошибкой при безуспешной попытке вызова утилиты manpath, которой нет в нашей системе в силу компиляции мира с опцией WITHOUT_MAN=.

Итак, устанавливаем наши файлы из conf/ в ${WORKDIR}/vanilla и продолжаем настройку

# в etc
# Разбито на две строки для лучшей читаемости
$ install -m 0644 -o root -g wheel conf/{devfs.rules,freebsd-update.conf,fstab,gettytab} ${WORKDIR}/vanilla/etc/
$ install -m 0644 -o root -g wheel conf/{hosts,issue,motd,rc.conf,sysctl.conf,ttys} ${WORKDIR}/vanilla/etc/

# в usr/bin
$ install -m 0555 -o root -g wheel conf/{help,manpath} ${WORKDIR}/vanilla/usr/bin/

# в usr/share/misc
$ install -m 0644 -o root -g wheel conf/termcap ${WORKDIR}/vanilla/usr/share/misc/

# Устанавливаем временную зону.
$ tzsetup -s -C ${WORKDIR}/vanilla Asia/Yekaterinburg

# Так как загружаться с флешки мы будем на компьютерах, у которых основная
# система Windows, то часы на них установлены по местному времени, а не UTC.
# Empty file. Its presence indicates that the machine's CMOS clock is set to local time.
$ touch ${WORKDIR}/vanilla/etc/wall_cmos_clock

# Устанавливаем пароль для root (по желанию)
$ pw -V ${WORKDIR}/vanilla/etc usermod root -h 0

Если хочется видеть в консоли другой шрифт, например старый добрый keyrus, то по ссылке с habrahabr.ru/post/137544 можно взять keyrus.bdf и добавить его в нашу систему.

$ vtfontcvt tools/keyrus.bdf keyrus.fnt
$ install -m 0444 -o root -g wheel keyrus.fnt ${WORKDIR}/vanilla/usr/share/vt/fonts/
$ echo 'font8x16="keyrus.fnt"' >> ${WORKDIR}/vanilla/etc/rc.conf

Настройка загрузки


Теперь настал черёд ответственного этапа по настройки загрузки системы. Ядра, модули, загрузчик, загрузочное меню. Последовательность загрузки описана в соответствующих разделах handbook, поэтому здесь подробно её освещать нет смысла. Важно то, что загрузчику — loader'у мы должны предъявить ядро, а ядру уже файл с образом нашей системы. Система в ${WORKDIR}/vanilla почти готова, поэтому переносим оттуда ядра и загрузочные файлы в каталог ${WORKDIR}/custom.

# Создаём каталоги в custom/boot/ для файла настроек loader_default.conf и два каталога для ядер
# boot/kernel/ - каталог для GENERIC ядра, а boot/kernel.cabsd/ - для нашего самодельного
$ mkdir -p ${WORKDIR}/custom/boot/{defaults,kernel,kernel.cabsd}

# Переносим ядра

# Основное (мини) ядро
$ cp -p ${WORKDIR}/vanilla/boot/kernel/kernel ${WORKDIR}/custom/boot/kernel.cabsd/

# Запасное (GENERIC) ядро
$ cp -p ${WORKDIR}/vanilla/tmp/boot/kernel/kernel ${WORKDIR}/custom/boot/kernel/

Нам придётся нарисовать загрузочное меню. У нас два ядра и будет два образа системы. В меню мы включим три варианта: mff, mmf, ffmm
  1. ядро caBSD с первым образом системы
  2. ядро GENERIC с модулями и первым образом системы
  3. ядро GENERIC с модулями и вторым образом системы

Первый вариант и будет конфигурацией по умолчанию, и предполагаем, что обычно в ней и будет работать пользователь.

У нас будет три файла конфигурации для загрузчика — loader.conf для первой конфигурации, loader_gen1.conf и loader_gen2.conf — для GENERIC ядра с первым и вторым образом соответственно, отличающиеся лишь именем файла образа. Итак,

conf/loader.conf
##
# conf/loader.conf
##

# Задержка перед автозагрузкой (сек)
autoboot_delay="5"

# Эти два параметра обязательные
mfs_load="YES"
mfs_type="md_image"

# Указываем загрузку с образа mfs1 (мы его сожмём gzip, но расширение .gz не указываем)
mfs_name="/mfs1"

# Полный путь к файлу ядра будет /boot/${kernel}/${bootfile}
kernel="kernel.cabsd"	# /boot sub-directory containing kernel and modules
#bootfile="kernel"	# Kernel name (possibly absolute path)
#kernel_options=""	# Flags to be passed to the kernel

# Указываем опции для нашего newcons:
# https://www.freebsd.org/cgi/man.cgi?query=vt(4)

# в ядро caBSD мы не включали sc, там есть только vt, поэтому параметр можно не указывать.
# Но для GENERIC ядер он понадобится, а то UTF-8 не будет.
# Поэтому включим здесь
kern.vty=vt

# Enable halt keyboard combination.
kern.vt.kbd_halt=1
# Enable power off key combination.
kern.vt.kbd_poweroff=1
# Enable reboot key combination, usually Ctrl+Alt+Del.
kern.vt.kbd_reboot=1
# Enable debug request key combination, usually Ctrl+Alt+Esc.
kern.vt.kbd_debug=0
# Enable panic key combination.
kern.vt.kbd_panic=0


В loader.conf мы указываем параметры для первого варианта загрузки — ядро caBSD и образ mfs1 (его мы создадим чуть позже). Необходимо иметь в виду, что при нашей конфигурации меню, параметры, указанные в этом файле, будут наследоваться файлами настроек для второго и третьего варианта. То есть, если в loader.conf будет прописано что-то странное, вроде kernel_options="-s" — загрузка в однопользовательском режиме, то в нём будут загружаться все три варианта. Поэтому сомнительные переменные, прописанные в loader.conf мы будем перезаписывать (обнулять) в loader_gen*.conf

conf/loader_gen1.conf
##
# conf/loader_gen1.conf
##
#
# Параметры для второго варианта загрузки - GENERIC ядро и образ mfs1.
# Параметры, установленные в loader.conf можно заново не указывать

mfs_name="/mfs1"

tmpfs_load="YES"
#geom_eli_load="YES"

exec=".( Loading GENERIC kernel and mfs1 root image ) cr"

kernel="kernel"		# /boot sub-directory containing kernel and modules
bootfile="kernel"	# Kernel name (possibly absolute path)
kernel_options=""	# Flags to be passed to the kernel



conf/loader_gen2.conf
##
# conf/loader_gen2.conf
##
#
# Параметры для третьего варианта загрузки - GENERIC ядро и образ mfs2.
# Параметры, установленные в loader.conf можно заново не указывать

mfs_name="/mfs2"

tmpfs_load="YES"
#geom_eli_load="YES"

exec=".( Loading GENERIC kernel and mfs2 root image ) cr"

kernel="kernel"		# /boot sub-directory containing kernel and modules
bootfile="kernel"	# Kernel name (possibly absolute path)
kernel_options=""	# Flags to be passed to the kernel


Переходим к загрузочному меню. Поверхностное изучение файлов настроек vanilla/boot/*.4th с конструкциями языка forth вызвало печаль и уныние — к чему все эти вывернутые конструкции, почему же нельзя было написать что-нибудь простое и понятное, например:

ПРОЦ СТАРТ();
ВКЛ МОДУЛЬ ЗАГЛМЕНЮ;
РИСОВАТЬ(«РАМКА1»);
ВЫВОД: «Welcome to caBSD»;

ВЫКЛ МОДУЛЬ ЗАГЛМЕНЮ;
КНЦ;

Эээх…

К счастью, добрые люди (да воздастся им при следующих реинкарнациях) положили в /usr/share/examples/bootforth простые примеры, незначительно изменив которые получились следующие loader.rc и menuconf.4th:

conf/loader.rc
\ 
\ conf/loader.rc

\ Example of the file which is automatically loaded by /boot/loader on startup.
\ $FreeBSD: releng/10.1/share/examples/bootforth/loader.rc 87636 2001-12-11 00:49:34Z jhb $

include /boot/loader.4th

s" /boot/screen.4th" O_RDONLY fopen dup fload fclose

s" /boot/frames.4th" O_RDONLY fopen dup fload fclose

s" /boot/menuconf.4th" O_RDONLY fopen dup fload fclose

initialize drop

cr
main_menu




conf/menuconf.4th
\ conf/menuconf.4th

\ Simple greeting screen, presenting basic options.
\ XXX This is far too trivial - I don't have time now to think about something more fancy... :-/
\ $FreeBSD: releng/10.1/share/examples/bootforth/menuconf.4th 65480 2000-09-05 16:30:09Z dcs $

: title
	f_single
\ Координаты вида - w h x y
	60 11 10 4 box
	30 4 at-xy
	."   Welcome to caBSD  "
	me
;

: menu
	2 fg
	15 7 at-xy 
	." 1.  Start caBSD kernel and first root image (auto)"
        15 8 at-xy
	." 2.  Start GENERIC kernel and first root image"
	15 9 at-xy
        ." 3.  Start GENERIC kernel and second root image"
	15 10 at-xy
	." 4.  Reboot"
	me
;

: tkey	( d -- flag | char )
	seconds +
	begin 1 while
	    dup seconds u< if
		drop
		-1
		exit
	    then
	    key? if
		drop
		key
		exit
	    then
	repeat
;

: prompt
	14 fg
	15 13 at-xy
	." Enter your option (1,2,3,4): "
	10 tkey
	dup 32 = if
	    drop key
	then
	dup 0< if
\ Дефолтным по таймауту стартует первый пункт меню.
	    drop 49
	then
	dup emit
	me
;

: help_text
        10 17 at-xy ." * Choose 1 to proceed with standard bootstrapping."
	10 18 at-xy ." * Choose 2 or 3 to run special configuration file."
	10 19 at-xy ." * Choose 4 in order to warm boot your machine."
	10 21 at-xy ." * Anyway you have few seconds to interrupt boot,"
	10 22 at-xy ."   change parameters and type 'boot' to continue"
;

: (reboot) 0 reboot ;

: main_menu
	begin 1 while
		clear
		f_double
		79 23 1 1 box
		title
		menu
		help_text
		prompt
		cr cr cr
		dup 49 = if
			drop
			1 25 at-xy cr
			." Proceeding with standard boot. Please wait..." cr
			0 boot-conf exit
		then
		dup 50 = if
			drop
			1 25 at-xy cr
			." Loading /boot/loader_gen1.conf. Please wait..." cr
			s" /boot/loader_gen1.conf" read-conf
			0 boot-conf exit
		then
		dup 51 = if
			drop
			1 25 at-xy cr
			." Loading /boot/loader_gen2.conf. Please wait..." cr
			s" /boot/loader_gen2.conf" read-conf
			0 boot-conf exit
		then
		dup 52 = if
			drop
			1 25 at-xy cr
			['] (reboot) catch abort" Error rebooting"
		then
		15 12 at-xy
		." Key " emit ."  is not a valid option!"
		15 13 at-xy
		." Press any key to continue..."
		key drop
	repeat
;


В результате у нас нарисовалось такое загрузочное меню:



Продолжаем установку. Забираем всё что может пригодиться из ${WORKDIR}/vanilla/boot

# Копируем загрузчик, скрипты и .4th файлы оптом (все они используют друг друга, кроме beastie.4th и brand.4th - их можно удалить)
$ cp -p ${WORKDIR}/vanilla/boot/{loader,loader.help,*.rc,*.4th} ${WORKDIR}/custom/boot
$ cp -p ${WORKDIR}/vanilla/boot/defaults/loader.conf ${WORKDIR}/custom/boot/defaults
$ rm -f ${WORKDIR}/custom/boot/{beastie.4th,brand.4th}

# Устанавливаем наши файлы loader*.conf из conf/ в boot
$ install -m 0644 -o root -g wheel conf/{loader,loader_gen1,loader_gen2}.conf ${WORKDIR}/custom/boot/
$ install -m 0644 -o root -g wheel conf/{loader.rc,menuconf.4th} ${WORKDIR}/custom/boot/

# Флешку будем разбивать с GPT, поэтому часть файлов с загрузчиками сохраняем.
$ cp -p ${WORKDIR}/vanilla/boot/{pmbr,gptboot,cdboot} ${WORKDIR}

Несколько слов про модули ядра. Наше ядро caBSD мы компилировали с опцией «makeoptions NO_MODULES=1», а все необходимые модули интегрировали в ядро. Но с GENERIC чуть посложнее. Умное ядро умеет не только автоматически загружать необходимые модули, но и выгружать их. Так, в случае с модулем geom_eli.ko, даже если мы укажем в boot/loader_gen1.conf параметр 'geom_eli_load=«YES»', то ядро загрузит его, но потом за ненадобностью выгрузит. Во всяком случае при нашем сценарии шифровании раздела. И когда ядро раскрутит mfs-образ системы, и загрузка дойдёт до скрипта mfs1:/etc/rc.d/geli, модуль будет уже выгружен. А загрузить его заново ядро не сможет — каталог /boot/kernel будет уже невидим изнутри образа mfs1:/.

Навскидку, есть несколько вариантов:

  1. Модули, используемые ядром при загрузке размещать в ${WORKDIR}/custom/boot/kernel, а используемые уже после запуска init, rc-скриптами — в ${WORKDIR}/vanilla/boot/modules (см. параметр 'module_path="/boot/modules"' в boot/defaults/loader.conf)
  2. (Умозрительный). Прописать в каком-нибудь rc-скрипте (выполняемом ранее etc/rc.d/geli) монтирование физического носителя (iso/img) и подмонтирование реального каталога /boot/kernel/ с модулями в mfs1:/boot/modules

Но проще всего выглядит первый способ. Реализуем его.

# Устанавливаем 'загрузочные' модули для GENERIC ядра. Необходимость модуля выясняется
# чаще всего опытным путём. Как минимум, tmpfs.ko нужен для монтирования mfs образа
$ cp -p ${WORKDIR}/vanilla/tmp/boot/kernel/tmpfs.ko ${WORKDIR}/custom/boot/kernel
$ strip ${WORKDIR}/custom/boot/kernel/tmpfs.ko

# Удаляем не нужный теперь /boot, куда был сделан installkernel первого ядра
$ rm -rf ${WORKDIR}/vanilla/boot

# И создаём пустой каталог для модулей
$ mkdir -p ${WORKDIR}/vanilla/boot/modules

# Устанавливаем 'послезагрузочные' модули - geom_eli.ko и зависимый от него crypto.ko
$ cp -p ${WORKDIR}/vanilla/tmp/boot/kernel/{geom_eli,crypto}.ko ${WORKDIR}/vanilla/boot/modules
$ strip ${WORKDIR}/vanilla/boot/modules/{geom_eli,crypto}.ko 

# Удаляем /boot, куда мы скопировали второе ядро и тучу модулей
$ rm -rf ${WORKDIR}/vanilla/tmp/boot

# Уменьшаем размеры ядер
$ ls -l ${WORKDIR}/custom/boot/{kernel,kernel.cabsd}/kernel
$ strip ${WORKDIR}/custom/boot/{kernel,kernel.cabsd}/kernel
$ strip --remove-section=.note --remove-section=.comment ${WORKDIR}/custom/boot/{kernel,kernel.cabsd}/kernel

# Сжимаем ядра
# freebsd-update не сможет обновить сжатое GENERIC ядро, поэтому будем иметь в виду -
# перед обновлением его надо будет разархивировать. Или не сжимать тут изначально. Да.
$ gzip -9 -f ${WORKDIR}/custom/boot/kernel.cabsd/kernel
# kgzip -o ${WORKDIR}/custom/boot/kernel.cabsd/kernel ${WORKDIR}/custom/boot/kernel.cabsd/kernel
# gzip -9 -f ${WORKDIR}/custom/boot/kernel/kernel
$ ls -l ${WORKDIR}/custom/boot/{kernel,kernel.cabsd}/kernel*

Небольшое замечание по сжатию. Ядра мы можем сжать с помощью gzip либо kgzip. В обоих случаях декомпрессия при загрузке происходит автоматически, без каких-то дополнительных настроек.

Для нашего маленького ядра caBSD результаты выглядят так: несжатое ядро — 3.9 Мб, сжатое этими утилитами — по 1.8 Мб, причём kgzip сжал на несколько килобайт эффективнее, но за счёт ~9 Кб загрузчика проиграл gzip.

Для GENERIC ядра немного поинтереснее: несжатое ядро — 17 Мб, сжатое gzip — 7.5 Мб, kgzip — 6.8 Мб. Заметно лучше. И это после удаления символов из ядра. Если их не удалять, то соотношение будет 19: 8.2: 6.8 Мб. Но это так, справочно. Всё равно мы GENERIC ядро из-за потенциальных обновлений сжимать не будем. Кстати, из-за них же можно и не применять strip для этого ядра.

Удаляем лишнее


Несмотря на заметную минимизацию нашей системы за счёт использования опций при компиляции мира, есть ещё много мест, куда мы можем приложить наши руки. И сделаем мы следующее:
  1. Укажем в файлах tools/files2delete_* файлы и каталоги, которые необходимо удалить
  2. Укажем в файле tools/files2keep исключения из п. 1 — файлы, которые необходимо сохранить
  3. Архивируем файлы из п. 2
  4. Удалим файлы и каталоги из п. 1
  5. Восстановим сохранённые файлы из архива

Очевидные плюсы от такой многоходовой комбинации:
  • Можем реализовать схему «удалить всё, кроме» (удалить весь usr/share/locale, кроме ru_RU.UTF-8)
  • При архивации файлов, если файл является символьной ссылкой, то в архив он попадёт как реальный файл. То есть мы можем для files2keep указать каталог /usr/share/locale/ru_RU.UTF-8 и не забивать голову тем, что ссылка на LC_COLLATE из него ведёт в ../la_LN.US-ASCII, LC_CTYPE ведёт в ../UTF-8, а LC_NUMERIC ведёт в ../ru_RU.CP866 — все ссылки сохранятся, а потом и восстановятся как реальные файлы

Сохраняемые файлы

tools/files2keep
#
# tools/files2keep
#
etc/rc.d/DAEMON
etc/rc.d/FILESYSTEMS
etc/rc.d/LOGIN
etc/rc.d/NETWORKING
etc/rc.d/SERVERS
etc/rc.d/adjkerntz
etc/rc.d/devfs
etc/rc.d/dmesg
etc/rc.d/geli
etc/rc.d/geli2
etc/rc.d/gptboot
etc/rc.d/hostname
etc/rc.d/initrandom
etc/rc.d/ldconfig
etc/rc.d/mountcritlocal
etc/rc.d/postrandom
etc/rc.d/random
etc/rc.d/root
etc/rc.d/securelevel
etc/rc.d/syscons
etc/rc.d/sysctl
etc/pam.d/login
#etc/pam.d/other
etc/pam.d/passwd
etc/pam.d/system
usr/libexec/getty
usr/share/locale/en_US.UTF-8
usr/share/locale/ru_RU.UTF-8
#usr/share/misc/init.ee
usr/share/misc/termcap
usr/share/vt/keymaps/ru.kbd
usr/share/vt/keymaps/ru.shift.kbd
usr/share/vt/keymaps/ru.win.kbd
usr/share/zoneinfo/Asia/Yekaterinburg
usr/share/zoneinfo/Europe/Moscow
usr/share/zoneinfo/UTC
usr/local/etc/joe/syntax/conf.jsf
usr/local/etc/joe/syntax/csh.jsf
usr/local/etc/joe/syntax/diff.jsf
usr/local/etc/joe/syntax/sh.jsf


На самом деле ценного очень мало. Здесь мы сохраняем:
  • нужные нам rc-скрипты запуска из etc/rc.d
  • актуальные в данном случае файлы конфигурации для PAM
  • getty из usr/libexec
  • две локали
  • установленный нами минимальный termcap из conf/termcap
  • все варианты переключения клавиатуры на русский
  • файлы актуальных для нас временных зон
  • несколько файлов настроек для редактора jupp

Теперь составим списки удаляемых файлов и каталогов. Пустые строки, равно как и строки с первым символом '#' игнорируются.

tools/files2delete_bin
#
# tools/files2delete_bin
#
bin/chio
bin/domainname
bin/ed
bin/pax
sbin/bsdlabel
sbin/camcontrol
sbin/ccdconfig
sbin/comcontrol
sbin/ddb
sbin/dhclient
sbin/dhclient-script
sbin/dump
sbin/dumpon
sbin/etherswitchcfg
sbin/fdisk
sbin/fsirand
sbin/gbde
sbin/ggatec
sbin/ggated
sbin/ggatel
sbin/gvinum
sbin/hastctl
sbin/hastd
sbin/ifconfig
sbin/iscontrol
sbin/kldconfig
sbin/kldunload
sbin/mksnap_ffs
sbin/mount_fusefs
sbin/mount_nfs
sbin/nfsiod
sbin/nos-tun
sbin/nvmecontrol
sbin/ping
sbin/resolvconf
sbin/restore
sbin/route
sbin/savecore
sbin/sconfig
sbin/setkey
sbin/spppcontrol
sbin/swapctl
usr/bin/asa
usr/bin/banner
usr/bin/brandelf
usr/bin/chat
usr/bin/chfn
usr/bin/chkey
usr/bin/colldef
usr/bin/cpasswd
usr/bin/cpuset
usr/bin/crontab
usr/bin/csup
usr/bin/ctlstat
usr/bin/dialog
usr/bin/dtc
usr/bin/ee
usr/bin/elf2aout
usr/bin/elfdump
usr/bin/fetch
usr/bin/file
usr/bin/finger
usr/bin/ftp
usr/bin/gcore
usr/bin/gencat
usr/bin/ipcrm
usr/bin/ipcs
usr/bin/iscsictl
usr/bin/keylogin
usr/bin/keylogout
usr/bin/ktrace
usr/bin/ktrdump
usr/bin/lam
usr/bin/lastcomm
usr/bin/leave
usr/bin/lockf
usr/bin/look
usr/bin/lorder
usr/bin/m4
usr/bin/mandoc
usr/bin/mesg
usr/bin/mkdep
usr/bin/mkfifo
usr/bin/mklocale
usr/bin/mt
usr/bin/netstat
usr/bin/newgrp
usr/bin/nfsstat
usr/bin/pagesize
usr/bin/passwd
usr/bin/pr
usr/bin/protect
usr/bin/rctl
usr/bin/revoke
usr/bin/rpcinfo
usr/bin/rs
usr/bin/rup
usr/bin/rusers
usr/bin/rwall
usr/bin/send-pr
usr/bin/showmount
usr/bin/smbutil
usr/bin/sockstat
usr/bin/stdbuf
usr/bin/su
usr/bin/tabs
usr/bin/talk
usr/bin/tcopy
usr/bin/tee
usr/bin/tftp
usr/bin/tip
usr/bin/tsort
usr/bin/units
usr/bin/unvis
usr/bin/vis
usr/bin/vmstat
usr/bin/wall
usr/bin/what
usr/bin/whois
usr/bin/write
usr/sbin/adduser
usr/sbin/arp
usr/sbin/asf
usr/sbin/automountd
usr/sbin/binmiscctl
usr/sbin/boot0cfg
usr/sbin/bootparamd
usr/sbin/bootpef
usr/sbin/bootptest
usr/sbin/bsdconfig
usr/sbin/bsdinstall
usr/sbin/callbootd
usr/sbin/cdcontrol
usr/sbin/chroot
usr/sbin/ckdist
usr/sbin/clear_locks
usr/sbin/cpucontrol
usr/sbin/crashinfo
usr/sbin/cron
usr/sbin/ctladm
usr/sbin/ctld
usr/sbin/dconschat
usr/sbin/digictl
usr/sbin/dumpcis
usr/sbin/etcupdate
usr/sbin/fifolog_create
usr/sbin/fifolog_reader
usr/sbin/fifolog_writer
usr/sbin/fwcontrol
usr/sbin/getfmac
usr/sbin/getpmac
usr/sbin/i2c
usr/sbin/ifmcstat
usr/sbin/inetd
usr/sbin/iscsid
usr/sbin/keyserv
usr/sbin/kgmon
usr/sbin/kgzip
usr/sbin/lptcontrol
usr/sbin/memcontrol
usr/sbin/mergemaster
usr/sbin/mfiutil
usr/sbin/mixer
usr/sbin/mlxcontrol
usr/sbin/mount_smbfs
usr/sbin/mountd
usr/sbin/moused
usr/sbin/mptable
usr/sbin/mptutil
usr/sbin/mtest
usr/sbin/newsyslog
usr/sbin/nfscbd
usr/sbin/nfsd
usr/sbin/nfsdumpstate
usr/sbin/nfsrevoke
usr/sbin/nfsuserd
usr/sbin/nmtree
usr/sbin/nologin
usr/sbin/pciconf
usr/sbin/periodic
usr/sbin/powerd
usr/sbin/procctl
usr/sbin/quot
usr/sbin/rarpd
usr/sbin/rmt
usr/sbin/rmuser
usr/sbin/rpc.lockd
usr/sbin/rpc.statd
usr/sbin/rpc.umntall
usr/sbin/rpcbind
usr/sbin/rtprio
usr/sbin/services_mkdb
usr/sbin/setfib
usr/sbin/setfmac
usr/sbin/setpmac
usr/sbin/sicontrol
usr/sbin/smbmsg
usr/sbin/snapinfo
usr/sbin/spkrtest
usr/sbin/spray
usr/sbin/syslogd
usr/sbin/sysrc
usr/sbin/tcpdchk
usr/sbin/tcpdmatch
usr/sbin/tcpdrop
usr/sbin/tcpdump
usr/sbin/timed
usr/sbin/timedc
usr/sbin/traceroute
usr/sbin/trpt
usr/sbin/ugidfw
usr/sbin/vipw
usr/sbin/wake
usr/sbin/watch
usr/sbin/watchdog
usr/sbin/zdump
usr/sbin/zic
usr/sbin/zzz
# vi
usr/bin/vi


Чистим bin, sbin, usr/bin, usr/sbin. При удалении файлов автоматически ищутся и удаляются жёсткие ссылки на них.

tools/files2delete_etc
#
# tools/files2delete_etc
#
etc/X11
etc/auto_master
etc/autofs
etc/bluetooth
etc/crontab
etc/ddb.conf
etc/defaults/bluetooth.device.conf
etc/defaults/periodic.conf
etc/devd
etc/devd.conf
etc/dhclient.conf
etc/disktab
etc/dumpdates
etc/ftpusers
etc/gss
etc/hosts
etc/hosts.allow
etc/hosts.equiv
etc/inetd.conf
etc/libalias.conf
etc/mac.conf
etc/mail
etc/mtree
etc/netconfig
etc/netstart
etc/networks
etc/newsyslog.conf
etc/newsyslog.conf.d
etc/nsmb.conf
etc/ntp
etc/pam.d
#etc/pam.d/passwd
etc/pccard_ether
etc/periodic
etc/phones
etc/pkg
etc/ppp
etc/rc.bsdextended
etc/rc.d
etc/rc.firewall
etc/rc.initdiskless
etc/rc.resume
etc/rc.sendmail
etc/rc.suspend
etc/remote
etc/rpc
etc/security
etc/skel
etc/ssh
etc/ssl
etc/syslog.conf
etc/termcap.small
etc/zfs


Чистим etc. Обратите внимание — удаляется etc/rc.d целиком, а нужные rc-скрипты запуска потом восстановятся из чуть ранее созданного архива.

tools/files2delete_lib
#
# tools/files2delete_lib
#
lib/libalias.so.7
lib/libalias_cuseeme.so
lib/libalias_dummy.so
lib/libalias_ftp.so
lib/libalias_irc.so
lib/libalias_nbt.so
lib/libalias_pptp.so
lib/libalias_skinny.so
lib/libalias_smedia.so
lib/libbegemot.so.4
lib/libcam.so.6
lib/libgcc_s.so.1
lib/libipsec.so.4
lib/libpcap.so.8
lib/libreadline.so.8
lib/libthr.so.3
lib/libulog.so.0
libexec/resolvconf
# OpenSSL IBM 4758 CCA hardware engine support
usr/lib/engines/lib4758cca.so
# OpenSSL AEP hardware engine support
usr/lib/engines/libaep.so
# OpenSSL Atalla hardware engine support
usr/lib/engines/libatalla.so
# OpenSSL CHIL hardware engine support
usr/lib/engines/libchil.so
# OpenSSL CryptoSwift hardware engine support
usr/lib/engines/libcswift.so
# OpenSSL Nuron hardware engine support
usr/lib/engines/libnuron.so
# OpenSSL SureWare hardware engine support
usr/lib/engines/libsureware.so
# OpenSSL UBSEC hardware engine support
usr/lib/engines/libubsec.so
usr/lib/private
usr/lib/libBlocksRuntime.so.0
usr/lib/libalias.so
usr/lib/libauditd.so.5
usr/lib/libbegemot.so
usr/lib/libbsm.so.3
usr/lib/libcam.so
usr/lib/libcurses.so
usr/lib/libcursesw.so
usr/lib/libdwarf.so.3
usr/lib/libexecinfo.so.1
usr/lib/libfetch.so.6
usr/lib/libform.so.5
usr/lib/libformw.so.5
usr/lib/libgcc_s.so
usr/lib/libgomp.so.1
usr/lib/libhistory.so.8
usr/lib/libipsec.so
usr/lib/libmagic.so.4
usr/lib/libmemstat.so.3
usr/lib/libmenu.so.5
usr/lib/libmenuw.so.5
usr/lib/libpanel.so.5
usr/lib/libpanelw.so.5
usr/lib/libpcap.so
usr/lib/libproc.so.2
usr/lib/libpthread.so
usr/lib/libradius.so.4
usr/lib/libreadline.so
usr/lib/librt.so.1
usr/lib/librtld_db.so.2
usr/lib/libsmb.so.4
usr/lib/libstdbuf.so.1
usr/lib/libstdthreads.so.0
usr/lib/libtacplus.so.5
usr/lib/libtermcap.so
usr/lib/libtermcapw.so
usr/lib/libtermlib.so
usr/lib/libtermlibw.so
usr/lib/libthr.so
usr/lib/libthread_db.so.3
usr/lib/libtinfo.so
usr/lib/libtinfow.so
usr/lib/libugidfw.so.4
usr/lib/libutempter.so
usr/lib/libvgl.so.6
usr/lib/libwrap.so.6
usr/lib32
usr/libdata
usr/libexec


Бибилиотеки. Самый ответственный файл. PAM-модули из usr/lib вынесены в отдельный файл

tools/files2delete_local
#
# tools/files2delete_local
#
usr/local/libdata
usr/local/man
usr/local/share
usr/local/etc/joe/charmaps
usr/local/etc/joe/syntax


Если ставили пакеты, то можно почистить usr/local

tools/files2delete_pam
#
# tools/files2delete_pam
#
usr/lib/pam_chroot.so
usr/lib/pam_deny.so
usr/lib/pam_echo.so
usr/lib/pam_exec.so
usr/lib/pam_ftpusers.so
usr/lib/pam_group.so
usr/lib/pam_guest.so
#usr/lib/pam_lastlog.so
#usr/lib/pam_login_access.so
#usr/lib/pam_nologin.so
#usr/lib/pam_opie.so
#usr/lib/pam_opieaccess.so
usr/lib/pam_passwdqc.so
usr/lib/pam_permit.so
usr/lib/pam_radius.so
usr/lib/pam_rhosts.so
usr/lib/pam_rootok.so
#usr/lib/pam_securetty.so
#usr/lib/pam_self.so
usr/lib/pam_tacplus.so
#usr/lib/pam_unix.so


PAM-модули.

tools/files2delete_var
#
# tools/files2delete_var
#
var/account
var/at
var/audit
var/authpf
var/crash
var/cron
var/db/hyperv
var/db/ipf
var/db/pkg
var/db/ports
var/db/portsnap
var/games
var/heimdal
var/mail
var/msgs
var/run/ppp
var/run/wpa_supplicant
var/rwho
var/spool
var/unbound
var/yp


Можно почитать man hier и почистить иерархию var от ненужных пустых каталогов. Для красоты.

tools/files2delete_other
#
# tools/files2delete_other
#
usr/games
usr/include
usr/share/bsdconfig
usr/share/dtrace
usr/share/examples
usr/share/info
# keys?
usr/share/keys
usr/share/locale
usr/share/man
usr/share/mdocml
usr/share/misc
usr/share/nls
usr/share/openssl
usr/share/skel
usr/share/tabset
usr/share/vi
usr/share/vt/keymaps
usr/share/zoneinfo


Чистим всё остальное, в основном usr/share

Стоит отметить:
  • пути к файлам необходимо записывать аккуратно, без ведущего слеша, чтобы не выхолостить базовую систему
  • никакой проверки на пробелы, кавычки и тому подобное в именах файлов не делается
  • смысл удаления не столько в экономии места на флешке, да и при создании второго mfs образа мы щедрой рукой накинем десяток-другой мегабайт под место для обновлений, сколько в том, чтобы просто удалить явно ненужное, оставив необходимое (да-да, ядро, шелл и openssl), ну и то, что может пригодится — а это самое творческое. Впрочем, при желании можно сделать две флешки — одну действительно минимальную, а вторую — с инструментарием

Приступаем.

# Удаляем файл архива, если он остался от предыдущих итераций
$ rm -f keepfiles.tar

# Архивируем файлы, перечисленные в tools/files2keep
$ tar --create --file keepfiles.tar --directory ${WORKDIR}/vanilla --dereference --files-from tools/files2keep

# Удаляем файлы из списка и их жёсткие ссылки.
# Если оформлять в виде скрипта, то нагляднее было бы сделать циклом for, да с дополнительными проверками, но циклы
# неудобно копипастить в командную строку, поэтому в данном примере обойдёмся такими вот однострочными конструкциями:
$ cat tools/files2delete_bin   | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla -samefile ${WORKDIR}/vanilla/% -exec rm -rf {} \;

# Далее удаляем без -samefile
$ cat tools/files2delete_etc   | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla/% -exec rm -rf {} \;
$ cat tools/files2delete_local | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla/% -exec rm -rf {} \;
$ cat tools/files2delete_other | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla/% -exec rm -rf {} \;
$ cat tools/files2delete_pam   | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla/% -exec rm -rf {} \;
$ cat tools/files2delete_lib   | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla/% -exec rm -rf {} \;
$ cat tools/files2delete_var   | egrep -v '(^#|^\s*$)' | xargs -I % -t -L 1 find ${WORKDIR}/vanilla/% -exec rm -rf {} \;

# Восстанавливаем заботливо сохранённое
$ tar --extract --file keepfiles.tar --verbose --directory ${WORKDIR}/vanilla
$ rm -f keepfiles.tar

Небольшой экскурс в историю — как получились эти файлы.

Были изучены rc-скрипты запуска в etc/rc.d, отобраны необходимые и прописаны в список для сохранения. Затем сформирован небольшой список к удалению, включающий в себя строчку 'etc/rc.d' и, в общем-то, содержимое нынешнего files2delete_other. Ключевым моментом было удалить все лишние скрипты запуска. Затем создали флешку, загрузились с неё и запустили поиск файлов:

$ find / -type f -amin -15 > /ca.certs/files-keep
$ find / -type f -not -amin -15 > /ca.certs/files-delete

Так были получены списки с «холодными» файлами и «горячими», использовавшимися в процессе загрузки, чей atime изменился за последние пятнадцать минут. Если бы мы не почистили заранее rc-скрипты, то в список «горячих» файлов попали бы многие явно лишние — типа ifconfig и тому подобные.

Ещё раз поиск файлов был произведён после обновления системы с помощью freebsd-update — это позволило выявить необходимые для её работы программы, порой неочевидные, типа join, tr, touch.

Из перечня «холодных» файлов сформировали списки:
  • files2delete_bin — отсюда исключались программы, которые могут пригодиться — для манипуляций с текстом, дисковые утилиты, архиваторы и прочие. Предварительно был получен список файлов с жёсткими ссылками («cd ${WORKDIR}/vanilla && find bin/ -type f -links +1 -exec echo „hardlinks of “ {} \; -exec find bin/ -samefile {} \;», или более удобно с помощью sysutils/samefile) — он использовался для избежания как включения одной ипостаси программы в список на удаление, а второй — на сохранение, так и многократного включения жёстких ссылок на один и тот же файл
  • files2delete_etc — тут исключались скрипты, используемые при завершении работы и файлы типа /etc/passwd, etc/master.passwd
  • files2delete_local — файл формировался с учетом установленных программ из портов — clex и jupp
  • files2delete_other — всё ценное сохранили с помощью files2keep, остальное было признано ненужным
  • files2delete_var — man hier и вперёд — rm -rf, rm -rf
  • files2delete_lib — после формирования списка files2delete_bin и применения его в ${WORKDIR}/vanilla, на изрядно похудевшие каталоги с программами был напущен найденный на просторах интернета скрипт по поиску «неиспользуемых» библиотек (ldd + grep), выдавший список, который после некоторой доработки составил этот файл. PAM модули были перенесены в другой файл. Так же не стали включать библиотеки из /lib/geom/. Причём, после первоначальной чистки библиотек, тот скрипт был запущен ещё раз, что помогло выявить ещё несколько библиотек, видимо находившихся в зависимостях у только что удалённых (libulog, libmagic, libwrap)
  • files2delete_pam — скриптом PAM модули показываются как неиспользуемые, хотя в действительности они нужны. Поэтому пришлось чистить файлы конфигурации в etc/pam.d/, оставлять модули, упомянутые в сохранённых файлах (login, passwd, system) и смириться с очевидными ограничениями — так, удалив etc/pam.d/su — мы не сделаем su. А с другой стороны — зачем нам нужен su, если по Alt+F1 у нас будет консоль 'ca', по Alt+F2 у нас 'root'

По итогам чистки системы была сделана флешка, в которой последовательно запускали все файлы из bin, sbin, usr/bin, usr/sbin на предмет выявления грубых ошибок. Обнаружилось, что poweroff пытается вызвать wall для оповещения пользователей; clear хочет tput (команда нужная, tput пришлось вернуть в систему); а костыль для whereis мы предусмотрели раньше. Конечно, выдача «usage:» не требовала от программ использования каких-то неочевидных библиотек, но зато можно было в отчётности отметить что «всё запускается». Ну а дальше, практика — критерий истины.

Создаём образ системы


Производим заключительную чистку ${WORKDIR}/vanilla

# Удаляем пустые каталоги в /usr.
$ find ${WORKDIR}/vanilla/usr -type d -empty -delete

# Удаляем мёртвые символьные ссылки
$ chroot ${WORKDIR}/vanilla find -L / -type l -exec rm -f {} \;

# Удаляем скачанные пакеты
$ rm -rf ${WORKDIR}/vanilla/tmp/pkg.files

# Удаляем БД pkg
$ rm -rf ${WORKDIR}/pkg.db

# Удаляем БД по установленным пакетам
$ rm -f ${WORKDIR}/vanilla/var/db/pkg/local.sqlite

# Создаём termcap db
$ chroot ${WORKDIR}/vanilla cap_mkdb /usr/share/misc/termcap

# Программа 'nologin' у нас была удалена при чистке - уж больно большая
# она, аж 372 кб, поэтому создаём одномённый симлинк на 'false'
# При очередном freebsd-update файл восстановится. Но это грозит лишь второму образу системы
$ ln -f ${WORKDIR}/vanilla/usr/bin/false ${WORKDIR}/vanilla/usr/sbin/nologin

# Создаём точки монтирования для ЦС
$ mkdir -p -m 0700 ${WORKDIR}/vanilla/{ca,ca.keys,ca.certs}
# и для флешек
$ mkdir -p -m 0700 ${WORKDIR}/vanilla/home/ca/mnt
# Устанавливаем владельца
$ chroot ${WORKDIR}/vanilla chown ca:ca /ca /ca.keys /ca.certs /home/ca/mnt

# Создаём mfs образы. Один (эталонный) мы создадим с минимумом свободного места, а для второго добавим
# запас, на случай обновлений freebsd-update. Параметры к makefs будем использовать следующие:
# -b free-blocks - Ensure that a minimum of 'free-blocks' free blocks (512 bytes) exist in the image
# -f free-files - Ensure that a minimum of 'free-files' free files (inodes) exist in the image
# -o fs-options - Set file system specific options
# -t fs-type - Create an 'fs-type' file system image

# Первый образ. Добавляем 1 Мб свободного места, чтобы никакие утилиты не чувствовали себя ущемлёнными
$ makefs -b 1m  -f 100 -o label=sysimg1 -t ffs ${WORKDIR}/custom/mfs1 ${WORKDIR}/vanilla

# Он меняться не будет, сожмём его.
$ gzip -9 -f ${WORKDIR}/custom/mfs1

# Второй образ. Предусмотрим 10 Мб свободного места для обновления системы.
$ makefs -b 10m -f 100 -o label=sysimg2 -t ffs ${WORKDIR}/custom/mfs2 ${WORKDIR}/vanilla

Создаём CD


Практическая польза от записи на CD-ROM сомнительна — всё же флешки практичнее, но этот образ очень удобно использовать для тестирования предыдущих этапов конструирования нашей системы, особенно загрузки. Сделали iso образ, подсунули его тому же qemu, попытались загрузиться — посмотрели, что да как.
$ makefs -t cd9660 -o bootimage=i386\;${WORKDIR}/cdboot,label=caBSD,no-emul-boot,rockridge caBSD-10.1-i386.iso ${WORKDIR}/custom

# Готово
$ ls -l caBSD-10.1-i386.iso

Создаём флешку


Вот мы и подошли к заключительному этапу — созданию img образа для записи на флешку.

У нас будет следующая структура:
  1. Раздел для обмена с внешними системами — для записи .csr и выгрузки .crt, тип ms-basic-data, размер 1 Мб. Делаем его первым, чтобы без дополнительных телодвижений был виден в Windows (7, на XP и ниже не проверялся)
  2. Загрузочный GPT раздел, freebsd-boot, 64 Кб
  3. Основной раздел с /boot, ядрами и mfs-образами, freebsd-ufs, размером с содержимое ${WORKDIR}/custom
  4. Раздел со скриптами и файлами конфигурации нашего ЦС, freebsd-ufs, 1 Мб
  5. Шифрованный раздел с секретными ключами ЦС, freebsd-ufs, 1 Мб. Сильно маленьким лучше не делать, т.к. geli может съесть прилично места


# Удаляем результаты предыдущих опытов
$ rm -f caBSD-10.1-i386.img caBSD-10.1-i386.txt

# Устанавливаем в переменные окружения сохранённые ранее цифровые uid/gid пользователя 'ca'
# На предыдущем шаге мы свернули корневой раздел нашей новой системы в файл, делать chroot уже некуда,
# поэтому устанавливать владельцем файлов пользователя 'ca' будем по его цифровому id
$ set CA_UID=`cat ${WORKDIR}/ca.uid`
$ set CA_GID=`cat ${WORKDIR}/ca.gid`

# Получаем размер ${WORKDIR}/custom (в Мб).
$ set SYSSIZE=`du -sm ${WORKDIR}/custom | cut -f 1`

# Размер основного раздела. Опытным путём возникла цифра в три мегабайта,
# которые мы добавляем на служебные области UFS и прочие доли ангелов.
$ set SYSSIZE=`expr ${SYSSIZE} + 3`

# Добавляем резерв под обновление ядра. Несжатое GENERIC ядро весит 17 Мб. Для обновления freebsd-update
# нужно это место учесть. А если включите "BackupKernel yes" в freebsd-update.conf, то и под предыдущее
# ядро. Либо можно при обновлении монтировать /boot/kernel в tmpfs, там разархивировать, обновлять,
# архивировать и класть на место. Но пока добавим 30 Мб на всё. Для тестирования
$ set SYSSIZE=`expr ${SYSSIZE} + 30`

# Размер файла с образом для флешки (system + exchange + ca + keys)
$ set IMGSIZE=`expr ${SYSSIZE} + 1 + 1 + 1`

# Создаём пустой пока img образ
$ dd if=/dev/zero of=caBSD-10.1-i386.img count=${IMGSIZE} bs=1m

# Монтируем его как md устройство
$ set MDDEVICE=`mdconfig -a -t vnode -f caBSD-10.1-i386.img`

Количество создаваемых md устройств в базовой системе не бесконечно, поэтому в случае повторных попыток создания img образа не забывайте удалять устройства, оставшиеся от предыдущих попыток — «ls /dev/md*» и следом повторять «mdconfig -d -u N», подставляя вместо 'N' номера ненужных md устройств. И обращать внимание, на что указывает в данный момент ${MDDEVICE}

Создаём разделы



# Создаём разделы, согласно man gpart
$ gpart create -s GPT ${MDDEVICE}

# Embed GPT bootstrap code into a protective MBR:
$ gpart bootcode -b ${WORKDIR}/pmbr ${MDDEVICE}

# Create a dedicated freebsd-boot partition that can boot FreeBSD from a freebsd-ufs partition,
# and install bootstrap code into it. We uses 88 blocks (44 kB) so the next partition will be
# aligned on a 64 kB boundary. The boot partition itself is aligned on a 4 kB boundary
# Не совсем очевидна необходимость выравнивания по границе 4k - это же не SSD и не жёсткий диск.
# Но пусть будет.
$ gpart add -b 40 -s 1m          -t ms-basic-data -l exchange ${MDDEVICE}
$ gpart add -a 4k -s 64k         -t freebsd-boot  -l boot     ${MDDEVICE}
$ gpart add -a 4k -s ${SYSSIZE}m -t freebsd-ufs   -l system   ${MDDEVICE}
$ gpart add -a 4k -s 1m          -t freebsd-ufs   -l ca       ${MDDEVICE}
$ gpart add -a 4k                -t freebsd-ufs   -l keys     ${MDDEVICE}
# Размер последнего раздела не указываем, фактически он будет чуть меньше 1 Мб

# Показываем, что получилось
$ gpart show ${MDDEVICE}

# Сохраняем структуру в файл
$ gpart backup ${MDDEVICE} > caBSD-10.1-i386.txt

$ unset SYSSIZE IMGSIZE

Форматируем разделы


# Форматируем первый раздел
# Если указать "-F 32", то будет FAT32, если не указывать - автоопределение.
# Для нашего размера в 1 Мб - FAT12
$ newfs_msdos -L exchange /dev/gpt/exchange

# Второй раздел. Указываем номер freebsd-boot раздела -> "-i 2"
$ gpart bootcode -p ${WORKDIR}/gptboot -i 2 ${MDDEVICE}

# Третий раздел
# -O - filesystem type;
# -n - disable snapshot. Background fsk must be OFF in rc.conf
# -o - optimization;
# -m - free-space. The percentage of space reserved from normal users;
# Опцию -U не ставим - софтапдейты не нужны, -t тоже не ставим - это не SSD
$ newfs -O2 -n -o space -m 0 -L system /dev/gpt/system

# Четвёртый раздел
$ newfs -O2 -n -o space -m 0 -L ca /dev/gpt/ca

# Пятый раздел. Создаём шифрованный раздел для закрытых ключей данного ЦС
# В данном примере будем обходиться лишь одной парольной фразой. В реальной жизни
# можно использовать ключевые файлы (user key + company key)

# Инициализируем раздел /dev/gpt/keys, дважды вводим пароль
# -a Enable data integrity verification (authentication) using the given algorithm
# -B File name to use for metadata backup
# -s Change decrypted provider's sector size.
# Здесь мы HMAC включать не будем, с ним хлопот больше. Опять же, если будут повреждены файлы
# с ключами - мы об этом узнаем и так, при попытке их использования. К тому же HMAC уменьшает
# полезную ёмкость раздела. В зависимости от размеров секторов может даже весьма значительно
$ geli init -B caBSD-10.1-i386.gelibak -s 4096 /dev/gpt/keys

# Сохраняем метаданные в текстовый файл, потом распечатаем. На бумаге оно понадёжнее будет.
# Коли приспичит, семьсот символов напечатать не сложно.
$ cat caBSD-10.1-i386.gelibak | b64encode caBSD-10.1-i386.gelibak >> caBSD-10.1-i386.txt

# Отметим возможность указания параметра '-r' - Attach read-only provider. Тут будет
# храниться только закрытый ключи ЦС и после его создания раздел можно будет подключать
# в режиме для чтения - параметр geli_default_flags="-r" в rc.conf. И 'ro' в fstab
$ geli attach /dev/gpt/keys

# Появившийся шифрованный раздел /dev/gpt/keys.eli мы можем заполнить случайными данными
# (dd if=/dev/random of=/dev/gpt/keys.eli bs=4096), а можем и не заполнять, и так ясно что
# там хранится. Размер блока (4k) должен быть кратным тому, который мы указали в geli init

# Форматируем (именно keys.eli)
$ newfs -O2 -n -o space -m 0 -L gelikeys /dev/gpt/keys.eli

# Позже, загрузившись с нашей флешки и сделав df -h мы увидим:

Filesystem           Size    Used   Avail Capacity  Mounted on
/dev/md0              14M     13M    690K    95%    /
devfs                1.0K    1.0K      0B   100%    /dev
tmpfs                 50M    4.0K     50M     0%    /tmp
/dev/gpt/ca          828K    240K    588K    29%    /ca
/dev/gpt/keys.eli    720K    4.0K    716K     1%    /ca.keys
/dev/gpt/exchange    999K    6.5K    992K     1%    /ca.certs

Наполняем разделы


# [keys] Начнём с него, чтоб развязаться с geli. Раздел пока пустой,
# специально обученные люди ключи потом создадут сами
$ mount /dev/gpt/keys.eli ${WORKDIR}/mnt

# Для удобства отладки ставим метку "Здесь был Вася"
$ touch ${WORKDIR}/mnt/.created-keys.eli

# Меняем владельца раздела на 'ca'
$ chown -R ${CA_UID}:${CA_GID} ${WORKDIR}/mnt

# Отключаем
$ umount ${WORKDIR}/mnt
$ geli detach /dev/gpt/keys.eli

# [exchange]. Тоже пустой раздел
# -m maximum file permissions for files
# -M maximum file permissions for directories
$ mount_msdosfs -u ${CA_UID} -g ${CA_GID} -m 0640 -M 0750 -l /dev/gpt/exchange ${WORKDIR}/mnt

# Это не UFS раздел, владельца и права каждый раз задаём при монтировании.
# Что тут, что в fstab. Поэтому просто ставим метку и размонтируем
$ touch ${WORKDIR}/mnt/.created-exchange
$ umount ${WORKDIR}/mnt

# [ca] Устанавливаем файлы скриптов и настроек нашего ЦС из архива.
# Предположим, он находится в tools/ca.tar.gz.
$ mount /dev/gpt/ca ${WORKDIR}/mnt
$ tar --extract --gunzip --no-same-permissions --file tools/ca.tar.gz --directory ${WORKDIR}/mnt

# Всем файлам права rw-r-----
$ chmod -R u=rw,g=r,o= ${WORKDIR}/mnt/*

# Каталогу conf права rwxr-x---
$ chmod u=rwx,g=rx,o= ${WORKDIR}/mnt/conf

# Скриптам права rwxr-x---
$ chmod u=rwx,g=rx,o= ${WORKDIR}/mnt/*.sh

$ touch ${WORKDIR}/mnt/.created-ca
$ chown -R ${CA_UID}:${CA_GID} ${WORKDIR}/mnt
$ umount ${WORKDIR}/mnt

# [system] Готовимся заполнять образ системы
$ mount /dev/gpt/system ${WORKDIR}/mnt

# Копируем всё
$ cp -av ${WORKDIR}/custom/. ${WORKDIR}/mnt/
$ umount ${WORKDIR}/mnt

# Убираем за собой
$ mdconfig -d -u ${MDDEVICE}
$ unset MDDEVICE CA_UID CA_GID

# Готово
$ ls -l caBSD-10.1-i386.img

Обновление системы


Теперь настроим оффлайновое обновление нашей FreeBSD на флешке. Проще всего при каждом важном обновлении заново создавать флешку по данному руководству, но это слишком легко, не наш путь.

Прежде всего нам понадобится оставленный в живых после создания флешки каталог ${WORKDIR}. Можно, конечно, без него — каждый раз ставить в виртуальной машине чистую систему с DVD — но тогда будут скачиваться все обновления, для всех программ, даже для тех, которые мы в варианте для флешки либо не ставили, либо удаляли. Поэтому будем считать, что каталог ${WORKDIR} у нас есть.

Поиск и скачивание обновлений будет проходить двумя итерациями — отдельно для ядра, отдельно для остальной системы.

Имеет место определённая неоднозначность — freebsd-update получает информацию о текущем ядре из переменной kern.bootfile, то есть базовой системы. Но в нашем случае они идентичны — ядро GENERIC мы как раз брали оттуда.

$ cd ${BASE}

# Разархивируем GENERIC ядро, если сжимали, иначе freebsd-update просто его не найдёт
# gunzip ${WORKDIR}/custom/boot/kernel/kernel.gz

# В custom у нас только каталог /boot, поэтому получим обновления только для GENERIC ядра и модулей
# -b basedir - Operate on a system mounted at basedir
# -d workdir - Store working files in workdir
# -f conffile - Read configuration options from conffile
$ freebsd-update fetch -b ${WORKDIR}/custom -d ${WORKDIR}/mnt -f ${WORKDIR}/vanilla/etc/freebsd-update.conf

# Скачиваем обновления для остальной системы
$ freebsd-update fetch -b ${WORKDIR}/vanilla -d ${WORKDIR}/mnt -f ${WORKDIR}/vanilla/etc/freebsd-update.conf

К сожалению (?), в ветке FreeBSD 10.1 на момент написания статьи не было обновлений модулей ядра, поэтому проверить как они обновляются не получилось. Соответственно остался открытым вопрос — по каким путям ищутся модули. Возможно, понадобятся ещё какие-то телодвижения для корректного обновления, учитывая, что у нас модули в двух местах.

Итак, обновления мы получили, смотрим в ${WORKDIR}/mnt. Там у нас каталог files с обновлениями, несколько служебных файлов, и две длинных символических ссылки с хвостиком -install. Имена этих ссылок представляют собой SHA256 хеши от имён каталогов "${WORKDIR}/custom" и "${WORKDIR}/vanilla". При установке обновлений программа заново сформирует хеши по именам каталогов, указанных в параметре '-b' и будет искать индексные файлы в /mnt по данным ссылкам. Но устанавливать обновления мы будем на флешке, где разделы с ядром и системой будут монтироваться совсем в другие места, и хеши этих точек монтирования будут немного отличаться. Поэтому сделаем вот так:

$ cd ${WORKDIR}/mnt

# На флешке раздел с ядром будем монтировать в /media/sys
# Создадим ссылку на каталог с индексами для ядра
$ ln -s `echo ${WORKDIR}/custom | sha256 -q`-install ${WORKDIR}/mnt/`echo /media/sys | sha256 -q`-install

# А раздел с системой - в /media/mfs
$ ln -s `echo ${WORKDIR}/vanilla | sha256 -q`-install ${WORKDIR}/mnt/`echo /media/mfs | sha256 -q`-install

# Архивируем
$ tar -cvf ${BASE}/update.tar .

$ cd ${BASE}

Архив переносим на транспортную флешку. Будем считать, что она отформатирована под FAT.

Загружаемся с нашей флешки с caBSD, выбрав третий пункт в загрузочном меню — GENERIC ядро и mfs2 образ. Во втором окне консоли входим как root.

# Нам понадобятся каталоги для точек монтирования, /mnt у нас занят - он прописан
# в /etc/freebsd-update.conf как источник обновлений, поэтому воспользуемся /media
$ mkdir /media/{sys,mfs,flash}

# Монтируем системный раздел
$ mount /dev/gpt/system /media/sys

# в /media/sys нам открылся каталог /boot с ядрами и двумя образами системы.
# Теперь монтируем второй образ в /dev/md1 (md0 занято нашим /)
$ mdconfig -a -t vnode -f /media/sys/mfs2 -u 1
$ mount /dev/md1 /media/mfs

# Монтируем транспортную флешку с обновлениями в /media/flash
$ mount_msdosfs -l /dev/da1s1 /media/flash

# Так как размер обновлений может быть большим, то извлекать их будем в tmpfs
# Оцениваем размер обновлений
$ du -h /media/flash/update.tar
# Монтируем с указанием соответствующего размера
$ mount -t tmpfs -o size=100m tmpfs /mnt

# Извлекаем обновления в /mnt
$ tar -xvf /media/flash/update.tar -C /mnt

# Разархивируем ядро (если сжимали изначально)
# gunzip /media/sys/boot/kernel/kernel.gz

# freebsd-update берёт путь к ядру из переменной ядра, поэтому её корректируем
$ sysctl kern.bootfile="/media/sys/boot/kernel/kernel"

# Обновляем ядро
$ freebsd-update install -b /media/sys -d /mnt

# Сжимаем ядро. До следующего раза.
# Хотя можно и не сжимать - место в образе всё равно выделено
# gzip -9 /media/sys/boot/kernel/kernel

# Обновляем мир
$ freebsd-update install -b /media/mfs -d /mnt

# Убираем за собой
$ umount /media/flash
$ umount /mnt
$ umount /media/mfs
$ mdconfig -d -u 1
$ umount /media/sys
$ rmdir /media/{sys,mfs,flash}

$ reboot

# Загружаемся, выбрав третий пункт меню

# Проверяем
$ freebsd-version

10.1-RELEASE-p12

$ openssl version

OpenSSL 1.0.1l-freebsd 15 Jan 2015


Всё.

Касательно самого ЦС — дублировать руководства, приводя примеры openssl req ..., openssl ca ..., и так далее не вижу смысла. Но могу порекомендовать неплохой ресурс pki-tutorial, по ссылке ниже.

Ссылки по теме:

Russian Unicode, wiki.freebsd.org/Newcons и unix1.jinr.ru/~lavr/vt/vtcons.html
Шрифты из склепа — habrahabr.ru/post/137544
OpenSSL PKI Tutorial — pki-tutorial.readthedocs.org
mfsBSD, mfsbsd.vx.sk
EasyBSD, www.fbsd-dev.org
FreeWDE, rop.gonggri.jp/?p=269

UPD:

Архив с файлами конфигурации и скриптом — cabsd.abrca.net/cabsd-1.0.zip

Комментарии (22)


  1. Ivan_83
    09.07.2015 14:10
    +2

    В ядре можно было больше отрезать: компаты и файловые системы выкинуть. У меня х64 занимает 5,3 мб.

    В scr.conf как то много слишком всего отключено, тот же PAM, INET, INET6 — имхо могут создать проблемы. Есть нюансы и при отключении сборки OpenSSH с ситемой — нужно очень аккуратно обновлятся удалённо.

    В sysctl.conf маловато тюнингов :)

    Загружать tmpfs из лоадера смысла нет, оно же впомпилено в ядро. Кроме того на этапе загрузки не используется, а при монтировании из fstab модуль подгрузится сам (если его в ядре нет).


    1. abrca Автор
      09.07.2015 14:36
      +2

      В ядре COMPAT_ отключать не стал, так как где-то было написано, что эти опции много кода не привносят, и используются некоторыми программами. Без указания какими :)
      Файловые системы — в минимальном ядре и так оставлено лишь по-минимуму — FFS, FAT, TMPFS и CD9660. Ну, можно было бы попробовать последнюю отключить, но как бы это сработало при загрузке с CD — не пробовал.

      По src.conf — PAM отключилось безболезненно, насчёт INET я тоже колебался, в ранних версиях FreeBSD помнилось строгое предупреждение что отключение этой опции всё порушит. Но нет, все программы, вошедшие в комплект как минимум запускались номально, а openssl корректно работал, все функции ЦС выполнялись.

      По sysctl — а что там тюнинговать? Чего-то сильно интересного не нашёл ))

      tmpfs вкомпилено в минимальное ядро. А в GENERIC — нет. Поэтому модуль и грузится для него. И как раз на этапе загрузки он и нужен — когда ядро раскручивает mfs образ системы в память.


      1. Ivan_83
        13.07.2015 02:16
        +1

        У меня на десктопе только: options COMPAT_FREEBSD32 #oW Compatible with i386 binaries
        из за вайна. Линуксовые бинарники я не запускаю, как и какие то старые от фри.

        Тут небольшой пардон, я обычно рассчитываю что система по сети будет работать штатно, как и с сетью.
        У вас, как я понял, сеть вообще не требуется, даже для каких обновлений.
        Если забить на сеть то пофик, резать не жалко.
        Без PAM у OpenSSH на вход уже пароли не подойдут.
        А без INET я не пробовал, но в целом, насколько помню, раньше система без него вообще не собиралась, и был целый небольшой проект, целью которого была сборка системы без INET (IPv4 совсем).

        Опять же, у меня тюнинги sysctl в основном для сетевой и дисковой активности, а у вас ни того ни другого.
        kern.sync_on_panic=1 # Do a sync before rebooting from a panic
        #kern.securelevel=2 # Current secure level

        # SECURITY
        security.bsd.map_at_zero=0 # Permit processes to map an object at virtual address 0.
        security.bsd.suser_enabled=1 # processes with uid 0 have privilege
        security.bsd.unprivileged_mlock=0 # Allow non-root users to call mlock(2)
        security.bsd.see_other_uids=1 # prevent users from seeing processes that are being run under another UID.
        security.bsd.see_other_gids=1 # disable is break some scripts, like rc.d scripts.
        security.bsd.conservative_signals=0 # disable some signals for setuid/setgid processes
        security.bsd.unprivileged_proc_debug=0 # disable debug for unprivileged users
        security.bsd.unprivileged_idprio=0 # Allow non-root users to set an idle priority
        security.bsd.unprivileged_read_msgbuf=1 # Unprivileged processes may read the kernel message buffer
        security.bsd.hardlink_check_uid=1 # Unprivileged processes cannot create hard links to files owned by other users
        security.bsd.hardlink_check_gid=1 # Unprivileged processes cannot create hard links to files owned by other groups
        security.bsd.unprivileged_get_quota=0 # Unprivileged processes may retrieve quotas for other uids and gids
        security.bsd.stack_guard_page=1 # Insert stack guard page ahead of the growable segments.

        kern.randompid=1 # Random PID modulus
        kern.logsigexit=1 # Log processes quitting on abnormal signals to syslog(3)
        vfs.usermount=0 # disable mount for unprivileged users

        kern.elf32.nxstack=1 # enable non-executable stack
        kern.elf64.nxstack=1 # enable non-executable stack

        # BASE KERNEL
        kern.random.sys.harvest.ethernet=0 # Harvest NIC entropy
        kern.random.sys.harvest.point_to_point=0 # Harvest serial net entropy
        kern.random.sys.harvest.interrupt=0 # Harvest IRQ entropy
        kern.random.sys.harvest.swi=0 # Harvest SWI entropy

        # RESOURCE TUNINGS

        # Every socket is a file, so increase them
        #kern.maxproc=2048 # Maximum number of processes
        kern.maxfiles=262144 # Maximum files
        kern.maxfilesperproc=262144 # Maximum files allowed open per process
        kern.maxvnodes=262144 # Maximum number of vnodes

        # SHARED MEMORY
        kern.ipc.shmmax=4294967296 # Maximum shared memory segment size
        kern.ipc.shm_use_phys=1 # Enable/Disable locking of shared memory pages in core, making them unswappable# Useful for databases
        kern.ipc.shm_allow_removed=0 # Enable/Disable attachment to attached segments marked for removal
        kern.ipc.semmsl=1024 # Max semaphores per id

        # FILE SYSTEM
        vfs.vmiodirenable=1 # Use the VM system for directory writes
        vfs.write_behind=1 # Cluster write-behind; 0: disable, 1: enable, 2: backed off
        vfs.read_max=128 # Cluster read-ahead max block count
        vfs.ufs.dirhash_maxmem=67108864 # Should be increased when you have A LOT of files on server (Increase until vfs.ufs.dirhash_mem becames lower)
        #vfs.hirunningspace=8388608 #! FreeBSD 10 — brokes IO. Maximum amount of space to use for in-progress I/O

        # GEOM
        # stripe
        kern.geom.stripe.debug=0 # Debug level
        kern.geom.stripe.fast=1 # Fast, but memory-consuming, mode
        #kern.geom.stripe.maxmem=134217728 #L 128mb # Maximum memory that can be allocated in «fast» mode (in bytes)
        # mirror
        kern.geom.mirror.debug=0 # Debug level
        kern.geom.mirror.timeout=30 # Time to wait on all mirror components
        kern.geom.mirror.idletime=8 # Mark components as clean when idling
        kern.geom.mirror.disconnect_on_failure=1 # Disconnect component on I/O failure.
        #kern.geom.mirror.sync_requests=4 #L Parallel synchronization I/O requests.
        # eli
        kern.geom.eli.debug=0 # Debug level
        kern.geom.eli.tries=3 # Number of tries for entering the passphrase
        kern.geom.eli.visible_passphrase=0 # Visibility of passphrase prompt (0 = invisible, 1 = visible, 2 = asterisk)
        kern.geom.eli.overwrites=8 # Number of times on-disk keys should be overwritten when destroying them
        kern.geom.eli.threads=0 # Number of threads doing crypto work
        kern.geom.eli.batch=1 # Use crypto operations batching
        #kern.geom.eli.boot_passcache=1 #L Passphrases are cached during boot process for possible reuse
        #kern.geom.eli.key_cache_limit=65536 #L Maximum number of encryption keys to cache

        # POWER SAVING: wiki.freebsd.org/TuningPowerConsumption
        hw.pci.do_power_nodriver=3 # off power on devices without driver
        hw.pci.do_power_resume=3 # Transition from D3 -> D0 on resume

        # HARDWARE TUNINGS
        hw.syscons.bell=0 #
        hw.intr_storm_threshold=16000 # Number of consecutive interrupts before storm protection is enabled

        Это у меня, вам бы наоборот включить сбор энтропии и тп под свою специфику.

        Ещё у вас оно на некоторых UEFI системах не запустится, сам наступил на такое: выставление легаси загрузчика заставляет биос думать что таблица разделов может быть только MBR, и если там GPT то никакой загрузки не получится.

        Нужно добавлять уефи раздел с загрузчиком.
        Если кратко то примерно так:
        gpart add -t efi -b 552 adaX
        dd if=/boot/boot1.efifat of=/dev/adaXpY bs=4k
        gpart unset -a active adaX
        (у меня с 552 до 2048 свободное место и оно туда попадает, а вам нужно будет размер указывать)

        И монтировать предпочитаю по UUID вместо текстовых меток, ибо случалось иметь одинаковые метки. Теперь вообще метки не ставлю.

        Ещё, я бы смотрел на LibreSSL вместо OpenSSL, сам переползаю везеде по маленьку. Минусом не стабильное апи и необходимость пересобирать зависимые порты, плюсом секурность и то что с либрессл у меня к нгинх виндовый вебдав клиент может подключится, в отличии от опенссл, который не так давно сломали.

        48 метров это круто. Я бы просто взял флешку на 8 гб и особо ничего не резал. Может даже приделал иксы и прочие непотребства.
        У меня даже когда то была флешка с миром для ARM, а само ядро шилось во флешку девайса. Плюсом было то что можно компелять прямо на девайсе, это часто было проще, чем заниматся кросс компиляцией портов. Но собиралось не всё, памяти было всего 32мб и даже своп не помогал. nginx собирался, самба нет.


        1. abrca Автор
          14.07.2015 10:03

          За тюнинги спасибо, помедитирую.

          А какие VM могут эмулировать загрузку с UEFI BIOS'ом, вы не в курсе? qemu вроде можно подсунуть такой rom, а кто ещё… А то всё доступное железо с обычным BIOS

          LibreSSL имелось в виду (собственно, в OpenBSD оно и шло в комплекте), но всякие TLS улучшения не особо актуальны, а в части управления сертификатами каких-то прорывных преимуществ мне не озвучивали. Да и требования уйти с openssl не было. Работает же :)

          48 Мб — это с дополнительным ядром и раздутым образом с пустым местом, а так — 7 с копейками. Да и то, можно было бы и дальше резать, но смысл… Совсем уж кастрированную систему делать не хотелось.

          А вот иксы и прочее это свят-свят. Хорошо, что xca не глянулся, а то бы пришлось. Впрочем, тогда было бы проще. Взять какой-нибудь live-USB с функцией сохранения состояния системы — и всё, готово.


  1. toxicdream
    09.07.2015 15:09

    Ссылочку бы на готовый образ для совсем ленивых и «обнаглевших» :)


    1. abrca Автор
      09.07.2015 15:39
      +1

      Гм. Вряд ли кто-то рискнёт разворачивать PKI на столь недоверенном образе «из интернета»… Но вот все используемые рабочие файлы + скрипт, который отработает все этапы и сделает образы системы я запросто могу выложить. Попозже допишу в конце поста где лежит.


      1. toxicdream
        09.07.2015 17:38
        +1

        Да мне просто потыкать — удовлетворить праздное любопытство…
        Мне ЦС пока не нужен.


        1. abrca Автор
          10.07.2015 08:04

          1. toxicdream
            10.07.2015 09:02

            Спасибо.
            Только обменник требует регистрацию или доступ к соц.аккаунтам.
            Сейчас что-нибудь «придумаю».


          1. toxicdream
            10.07.2015 13:34

            Смонтировал iso в VMplayer 6.0.1 (10). Что-то ошибки при монтировании вылезли.
            При выборе первого пункта:
            habrastorage.org/files/6be/647/88d/6be64788d51d4c42be040f357611d3d6.PNG
            При выборе второго и третьего:
            habrastorage.org/files/4f8/2d8/289/4f82d82893424eeda29270484ee861eb.PNG

            Будет время поищу QEMU. Хотелось бы услышать варианты — чем могут быть вызваны ошибки.


            1. abrca Автор
              10.07.2015 13:43

              по рис.1 — это нормально. Вы, видимо, монтировали .iso — а там нет разделов с ЦС, ключами и т.п. — вот оно и обламывается при попытке их монтирования. Можно игнорировать

              по рис.2 — просто нажмите Enter, там сообщение от geli перебивает командную строку.

              Консоль root — по Alt+F2


              1. toxicdream
                10.07.2015 13:57

                Понял. Спасибо еще раз за труды и инструкции.


  1. juffinhalli
    09.07.2015 15:18

    А что мешало использовать виртуальную машину взамен флешки? Это сэкономило бы массу времени и сил.


    1. abrca Автор
      09.07.2015 15:28

      Требование переносимости и возможности достаточно безопасной работы даже на чужом компьютере. Конечно, можно было бы использовать что-то вроде Portable VirtualBox, но это означало бы запуск из-под «недоверенного» windows, если на чужом компьютере (да и на своём...) + администраторские права.

      И ресурсоёмкость. В качестве машины под этот походный ЦС предполагалось использовать и в том числе и нетбуки типа asus eee pc 700 901, на которых разворачивать виртуальное окружение вряд ли бы получилось. Равно как и выделять отдельные компьютеры только под ЦС.


  1. BasilioCat
    09.07.2015 18:12
    +1

    А чем вам mfsBSD-то не угодил? Его «исходный код», а на деле Makefile, по объему меньше этой статьи. Пересборка ядра не обязательна для использования vt (см мануал lavr'а), про world тоже не ясно зачем. Экономия пары мегабайт места на флэшках с современными объемами слегка архаична. И как будет работать после пересборки мира и ядра бинарное обновление через freebsd-update?


    1. abrca Автор
      10.07.2015 08:33

      mfsBSD весьма хорош, и он подарил несколько идей, реализованных здесь. Но такое впечатление, что в развитии он остановился где-то на 8й версии FreeBSD. Это не плохо, но тем не менее.

      Пересборка ядра и мира здесь используется как для получения системы с последними багфиксами (чего не получить, устанавливая систему из DVD1:/usr/freebsd-dist/base.txz, как это делает mfsBSD — или же всё равно накатывать freebsd-update), так и для минимизации получившегося образа, а то prunelist разросся бы до неприличных размеров :)

      А бинарное обновление через freebsd-update работает отлично. Мир (в т.ч. openssl) обновляет корректно, GENERIC ядро тоже. Кастомное ядро естественно нет, но это не так уж часто бывает нужно. А апгрейдить флешку с 10й на 11ю версию FreeBSD планируется всё же путём пересборки системы.


      1. BasilioCat
        10.07.2015 13:25

        По-моему пересборка системы из исходников — прошлый век даже для FreeBSD. Распакуйте base.txz, накатите UNAME_r=10.1-RELEASE freebsd-update -s <base.txz-dir> и получите обновленный дистрибутив. Сборка мира на вашей машине не гарантирует полностью идентичных файлов — думаю что freebsd-update IDS вам об этом и сообщит.

        И я все же не понимаю, зачем вам кастомный конфиг ядра? FreeBSD вроде не 4.х, все делается через loader.conf


        1. abrca Автор
          10.07.2015 14:05

          Да, это вариант. Но тогда будет установлена полная система, со всеми компиляторами, утилитами и прочим. Придётся весьма усиленно чистить посредством files2delete_*

          А кастомный конфиг… Дело не в модулях, тут их используется раз-два и обчёлся, а просто как один из этапов минимизации размера системы


  1. istui
    09.07.2015 23:27

    Спасибо за статью, большой труд :)

    Вы упоминали флешки QUMO — как они по вашему опыту, не было проблем с надежностью?


    1. abrca Автор
      10.07.2015 08:35

      Эта флешка использовалась как опытная для проверки загрузки и функционирования флеш-образа системы, так что записывалась и стиралась часто на протяжении нескольких месяцев, но претензий к работе нет. Хотя, конечно, для установки рабочей системы купили новую :)


  1. danfe
    10.07.2015 12:26
    +1

    В результате в финал вышли два дистрибутива — Alpine Linux и OpenBSD. Всё бы хорошо, и не было бы смысла писать эту статью, как ВНЕЗАПНО уточнились требования к операционной системе — потребовалась полная поддержка русского текста в Unicode в системной консоли. [...] Однако это требование позволило иначе взглянуть на доступные дистрибутивы, и в фаворитах неожиданно оказалась FreeBSD.
    Скажите, почему фряхи изначально не было в претендентах? Мне кажется, что она вполне удовлетворяет исходным требованиям.

    Про Alpine Linux я ничего не знаю, а вот увидеть в списке скорее нишевую OpenBSD и не увидеть более general-purpose FreeBSD было несколько странно.


    1. abrca Автор
      10.07.2015 12:57

      Первоначальными критериями при подборе дистрибутивов были:
      1. возможность минимальной инсталляции (именно инсталляции, а не live-CD)
      2. не то, что бы ориентированность на безопасность, но как минимум не игнорирование её.

      Поэтому изначально и вышли в лидеры те два дистрибутива, так как по п. 1 тот же Alpine Linux предполагает компактную установку, а по OpenBSD довольно много руководств (гуглится по «openbsd soekris»). И по п.2 оба они представляются как security oriented.

      Массовость дистрибутива особой роли не играла, учитывая планируемую сферу применения — на тот момент я тоже про Alpine Linux ничего не знал, а FreeBSD вначале не рассматривалась, так по по предыдущему опыту использования, родным инсталлятором сделать компактную установку (сравнимую по размеру с alpine/openbsd) было нельзя, а picobsd и nanobsd уже тогда не впечатляли.