├── Makefile ├── README.md ├── debian ├── changelog ├── control ├── copyright ├── debootstick.docs ├── debootstick.manpages ├── lintian-overrides ├── rules └── source │ └── format ├── debootstick ├── debootstick.8 ├── disk-layouts └── target │ ├── pc │ └── disk-layout │ └── rpi │ └── disk-layout └── scripts ├── create-image ├── chrooted-customization-draft.sh ├── chrooted-customization-final.sh ├── cmdline ├── common │ ├── apt │ ├── basic │ ├── bootargs │ ├── failsafe │ ├── files │ ├── finalize │ ├── formatting │ ├── layout │ ├── lvm │ ├── mount │ └── target-helpers ├── os-detect └── target │ ├── pc │ ├── cpu │ ├── detect.sh │ ├── entrypoints │ ├── grub │ ├── packages │ └── rootfs │ ├── rpi │ ├── bootloader │ ├── detect.sh │ ├── emulation │ ├── entrypoints │ ├── packages │ └── rootfs │ └── skeleton │ ├── bootloader │ ├── detect.sh │ ├── entrypoints │ ├── packages │ ├── rootfs │ └── steps └── live └── init ├── first-init.sh ├── getty-hook.sh ├── migrate-to-disk.sh ├── occupy-space.sh └── tools.sh /Makefile: -------------------------------------------------------------------------------- 1 | # avoid dpkg-dev dependency; fish out the version with sed 2 | VERSION := $(shell sed 's/.*(\(.*\)).*/\1/; q' debian/changelog) 3 | 4 | all: 5 | 6 | clean: 7 | 8 | ifdef DH_BUILD_TYPE 9 | # debhelper will manage installation of docs itself 10 | install: install-tool 11 | 12 | uninstall: uninstall-tool 13 | 14 | else 15 | install: install-tool install-docs 16 | 17 | uninstall: uninstall-tool uninstall-docs 18 | 19 | endif 20 | 21 | DSDIR=$(DESTDIR)/usr/share/debootstick 22 | install-tool: 23 | set -e 24 | mkdir -p $(DSDIR)/scripts $(DSDIR)/disk-layouts 25 | mkdir -p $(DESTDIR)/usr/sbin 26 | 27 | cp -ar scripts/* $(DSDIR)/scripts/ 28 | cp -ar disk-layouts/* $(DSDIR)/disk-layouts/ 29 | 30 | sed 's/@VERSION@/$(VERSION)/g' debootstick >$(DESTDIR)/usr/sbin/debootstick 31 | [ "$(DH_BUILD_TYPE)" = "1" ] || chown root:root $(DESTDIR)/usr/sbin/debootstick 32 | chmod 0755 $(DESTDIR)/usr/sbin/debootstick 33 | 34 | install-docs: 35 | mkdir -p /usr/share/doc/debootstick 36 | gzip < debootstick.8 > $(DESTDIR)/usr/share/man/man8/debootstick.8.gz 37 | gzip < debian/changelog > $(DESTDIR)/usr/share/doc/debootstick/changelog.gz 38 | cp README.md debian/copyright $(DESTDIR)/usr/share/doc/debootstick/ 39 | 40 | uninstall-tool: 41 | rm -Rf $(DSDIR) 42 | rm $(DESTDIR)/usr/sbin/debootstick 43 | 44 | uninstall-docs: 45 | rm $(DESTDIR)/usr/share/man/man8/debootstick.8.gz 46 | rm -Rf $(DESTDIR)/usr/share/doc/debootstick/ 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | debootstick 2 | =========== 3 | _Turn a chroot environment into a bootable image._ 4 | 5 | Trivial example: 6 | ---------------- 7 | ``` 8 | $ debootstrap --variant=minbase focal focal_tree http://archive.ubuntu.com/ubuntu/ 9 | $ debootstick --config-root-password-none focal_tree img.dd 10 | $ dd if=img.dd of=/dev/ bs=10M 11 | ``` 12 | Your USB device now embeds a live Ubuntu system and can be booted 13 | on any UEFI or BIOS computer. 14 | 15 | From docker image to raspberry pi SD: 16 | ------------------------------------- 17 | A more interesting example: 18 | ``` 19 | $ docker run -it --name mycontainer --entrypoint /bin/bash eduble/rpi-mini 20 | > [... customize ...] 21 | > exit 22 | $ mkdir mycontainer_fs; cd mycontainer_fs 23 | $ docker export mycontainer | tar xf - ; docker rm mycontainer 24 | $ cd .. 25 | $ debootstick --config-root-password-none mycontainer_fs rpi.dd 26 | $ dd if=rpi.dd of=/dev/mmcblk0 bs=10M 27 | ``` 28 | Your **Raspberry Pi** now boots your customized OS! 29 | 30 | Embedded OS features 31 | -------------------- 32 | The embedded system is: 33 | 34 | - ready to be used (no installation step) 35 | - viable in the long-term, fully upgradable (including the kernel and the bootloader) 36 | - compatible with BIOS and UEFI systems (PC) or Raspberry Pi boards 37 | 38 | More information on the wiki 39 | ---------------------------- 40 | On the wiki at https://github.com/drakkar-lig/debootstick/wiki, you will find: 41 | * A more complete workflow for designing and testing an image 42 | * How to install __debootstick__ 43 | * How to combine __debootstrap__ or __docker__ with __debootstick__ 44 | * How to test images with __kvm__ 45 | * Design notes, FAQ 46 | 47 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | debootstick (2.8) unstable; urgency=medium 2 | 3 | * Bookworm support (thanks to ciscon@github) 4 | * The package is now architecture-independent 5 | * Support running from source dir (thanks to ciscon@github) 6 | * Raspberry Pi guest: improve binfmt cpu emulation handling 7 | * More robust guest resolv.conf 8 | * Fix possible missing space error when updating large initramfs 9 | * apt install: fix return code if ever it fails 10 | * Disk layout sizes: increase margins for security 11 | * Fix dependency issue (LP: #1981651) 12 | * Migration: Fix issues with 'disk' or 'part' in LVM labels 13 | (thanks to V.Danjean) 14 | 15 | -- Etienne Dublé Thu, 20 Oct 2022 14:32:04 +0000 16 | 17 | debootstick (2.7) focal impish jammy; urgency=medium 18 | 19 | * New option --run-on-first-boot (thanks to S H Mohanjith) 20 | 21 | -- Etienne Dublé Mon, 24 Jan 2022 09:58:00 +0000 22 | 23 | debootstick (2.6) focal impish jammy; urgency=medium 24 | 25 | * Secure-Boot support (idea of khimaros) 26 | * Support for 4K native disks, and new --sector-size option 27 | (thanks to S.Fiala) 28 | * Fix support for migrating to NVMe devices (thanks to F.Lavratti) 29 | * Fix migrating to a disk with a different logical sector size 30 | * Installer-mode: generic disk detection with lsblk 31 | * Fix UEFI boot on Ubuntu minbase 32 | * Improved /etc/resolv.conf handling again 33 | * Minor improvements and fixes 34 | 35 | -- Etienne Dublé Wed, 19 Jan 2022 16:03:25 +0000 36 | 37 | debootstick (2.5) unstable; urgency=medium 38 | 39 | * Validate support for Raspberry Pi 4 40 | * Handle user-defined disk layouts (idea of D.Muhamedagic) 41 | * Less aggressive guest cleanup function (thanks to Ken Gilmer) 42 | * Handle resize of FAT partitions 43 | * Improvement to state recovery in case of failure 44 | * GPT: Relocate secondary header when resizing last partition 45 | * Improve precision of size calculations 46 | * Restrict image permissions to 644 (thanks to Dejan Muhamedagic) 47 | * Handle latest guest OS versions 48 | * Makefile improvements (by Ken Gilmer and Etienne Dublé) 49 | * Improved /etc/resolv.conf handling 50 | * Grub: Preferably disable OS prober while building image 51 | * Obsolete code removal 52 | * Documentation updates 53 | 54 | -- Etienne Dublé Mon, 2 Nov 2020 11:03:25 +0000 55 | 56 | debootstick (2.4) unstable; urgency=medium 57 | 58 | * Just rebuild for upload to unstable. 59 | 60 | -- Etienne Dublé Fri, 31 May 2019 18:01:03 +0000 61 | 62 | debootstick (2.3) experimental; urgency=medium 63 | 64 | * Fix new LVM versions hanging in migration procedure (closes: Bug#928080). 65 | * Fix migration script sometimes failing (closes: Bug#929355). 66 | * Fix handling of chroots based on Ubuntu >= 18.04 (closes: Bug#929356). 67 | 68 | -- Etienne Dublé Wed, 22 May 2019 13:56:42 +0000 69 | 70 | debootstick (2.2) unstable; urgency=medium 71 | 72 | * Fix new LVM versions hanging in chroot (closes: Bug#923852). 73 | 74 | -- Etienne Dublé Fri, 8 Mar 2019 13:48:03 +0000 75 | 76 | debootstick (2.1) unstable; urgency=medium 77 | 78 | * Extensive tests. 79 | * Fix arch detection on some 32bits PC chroots. 80 | * Fix UEFI boot (obsolete rootfs label). 81 | * Fix filesystem expansion issues. 82 | * Update long description and README.md. 83 | 84 | -- Etienne Dublé Fri, 5 Oct 2018 13:50:56 +0000 85 | 86 | debootstick (2.0) unstable; urgency=medium 87 | 88 | * Raspberry Pi SD card generation support (with a raspbian chroot). 89 | * Enhanced kernel bootargs management (wrt. = format). 90 | (Thanks to ThibsG for reporting the related issue.) 91 | * Refactor to separate target-specific (RPi or PC) and common code. 92 | * Also added 'skeleton' target for reference. 93 | * Support both GPT and DOS partitioning. 94 | * Allow a given target not to use LVM (i.e. have rootfs on partition). 95 | * More robust lvm devices removal while building image. 96 | * Fix issue when extending filesystem over a large disk (with Jessie). 97 | * Improve code robustness. 98 | 99 | -- Etienne Dublé Fri, 9 Mar 2018 17:59:13 +0000 100 | 101 | debootstick (1.3) unstable; urgency=medium 102 | 103 | * Support Debian Buster (as of january 31, 2018) chroot environments. 104 | * Enhance --config-kernel-bootargs: add or remove bootargs 105 | (Thanks to Andreas Unterkircher for the initial idea.) 106 | * Do not fail if there is no free space on 1st boot 107 | (Thanks to Andreas Unterkircher.) 108 | * Add dependency on e2fsprogs (closes: Bug#887209). 109 | * Fix options lost when calling sudo! 110 | * Fix error detection in grub install function. 111 | (Thanks to Andreas Unterkircher for reporting the issue.) 112 | * Fix lintian warnings. 113 | * Reduce verbosity. 114 | 115 | -- Etienne Dublé Wed, 31 Jan 2018 16:35:13 +0000 116 | 117 | debootstick (1.2) unstable; urgency=medium 118 | 119 | * Support Debian Stretch (as of october 31, 2016) chroot environments. 120 | 121 | -- Etienne Dublé Mon, 31 Oct 2016 11:25:33 +0000 122 | 123 | debootstick (1.1) unstable; urgency=medium 124 | 125 | * Support Debian Stretch (as of september 1, 2016) chroot environments. 126 | * Support Debian Stretch as host. 127 | * Support Ubuntu 16.04 chroot environments. 128 | * Update support for Ubuntu 14.04, Debian Wheezy and Jessie. 129 | * Drop support for Ubuntu 12.04. 130 | * Minor improvements and bugfixes. 131 | 132 | -- Etienne Dublé Thu, 1 Sep 2016 13:13:52 +0000 133 | 134 | debootstick (1.0) unstable; urgency=medium 135 | 136 | * New option to generate installer images. 137 | * i386 support (host and chroot environments). 138 | * Fix about removal of host cache files (closes: Bug#794938). 139 | Thanks to Julien Pinon for pointing this out. 140 | * Minor improvements and bugfixes. 141 | 142 | -- Etienne Dublé Mon, 24 Aug 2015 11:09:07 +0000 143 | 144 | debootstick (0.9c) unstable; urgency=low 145 | 146 | * Initial Debian release. (Closes: Bug#779654) 147 | 148 | -- Etienne Dublé Fri, 20 Mar 2015 10:26:33 +0000 149 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: debootstick 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Etienne Dublé 5 | Build-Depends: debhelper-compat (= 13) 6 | Rules-Requires-Root: no 7 | Standards-Version: 4.6.1.0 8 | Homepage: https://drakkar-lig.github.io/debootstick 9 | Vcs-Git: https://github.com/drakkar-lig/debootstick.git 10 | Vcs-Browser: https://github.com/drakkar-lig/debootstick 11 | 12 | Package: debootstick 13 | Architecture: all 14 | Depends: ${misc:Depends}, lvm2, uuid-runtime, gdisk, 15 | dosfstools, e2fsprogs, qemu-user-static 16 | Suggests: debootstrap, qemu-kvm 17 | Description: Turn a chroot environment into a bootable image 18 | debootstick is used to generate a bootable image from a Debian 19 | or Ubuntu chroot environment (such as one generated with 20 | debootstrap, docker export, etc.). 21 | This image should then be copied to a USB stick or disk and 22 | used to boot any amd64 machine (BIOS- or UEFI-based). 23 | debootstick can also generate an SD card image for a 24 | raspberry pi board. 25 | The embedded system is ready to be started live (no 26 | installation procedure needed), and is fully upgradeable 27 | (kernel and bootloader included). 28 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Contact: Etienne Dublé 3 | 4 | Files: * 5 | Copyright: 2014-2015 Centre National de la Recherche Scientifique (CNRS) 6 | License: BSD-3-clause 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 3. Neither the name of CNRS nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | . 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /debian/debootstick.docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/debootstick.manpages: -------------------------------------------------------------------------------- 1 | debootstick.8 2 | -------------------------------------------------------------------------------- /debian/lintian-overrides: -------------------------------------------------------------------------------- 1 | # lintian false-positive: 2 | # in scripts/create-image/functions, we define a shell alias for 3 | # the keyword 'with'. This allows the following constructs: 4 | # "with ; do ; done;" 5 | # (similar to python's with statements) 6 | # debootstick uses such a construct to rollback the 7 | # host system to a sane state if an unexpected issue occurs. 8 | debootstick: shell-script-fails-syntax-check [usr/sbin/debootstick] 9 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | # let the Makefile know that we are building a debian 8 | # package. 9 | export DH_BUILD_TYPE=1 10 | 11 | %: 12 | dh $@ 13 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debootstick: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # constants 4 | VERSION='@VERSION@' # updated at install time 5 | DEBUG=0 6 | DD="dd status=none" 7 | DBSTCK_DIR="/usr/share/debootstick" 8 | if [ ! -d "$DBSTCK_DIR" ];then 9 | CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/" >/dev/null 2>&1 && pwd)" 10 | DBSTCK_DIR="$CURRENT_DIR" 11 | fi 12 | 13 | # get cmdline parsing and os-detection functions 14 | . $DBSTCK_DIR/scripts/create-image/cmdline 15 | . $DBSTCK_DIR/scripts/create-image/os-detect 16 | 17 | # check options 18 | root_password_request="NO_REQUEST" 19 | root_password_on_first_boot=0 20 | config_grub_on_serial_line=0 21 | system_type="live" 22 | layout_file="default" 23 | sector_size=512 24 | kernel_bootargs="" 25 | config_hostname="" 26 | chroot_in="" 27 | image_out="" 28 | 29 | parse_args() 30 | { 31 | while [ $# != 0 ] 32 | do 33 | case "$1" in 34 | -h|--help) 35 | usage_and_exit 0 36 | ;; 37 | --help-os-support) 38 | describe_os_support 39 | exit 0 40 | ;; 41 | -v|--version) 42 | echo "debootstick $VERSION" 43 | exit 0 44 | ;; 45 | --kernel-package) 46 | kernel_package="$2" 47 | shift 2 48 | ;; 49 | --config-kernel-bootargs) 50 | kernel_bootargs="$2" 51 | shift 2 52 | ;; 53 | --config-root-password-ask) 54 | root_password_request="ASK" 55 | shift 56 | ;; 57 | --config-root-password-none) 58 | root_password_request="NO_PASSWORD" 59 | shift 60 | ;; 61 | --config-root-password-first-boot) 62 | root_password_on_first_boot=1 63 | shift 64 | ;; 65 | --config-hostname) 66 | config_hostname="$2" 67 | shift 2 68 | ;; 69 | --config-grub-on-serial-line) 70 | config_grub_on_serial_line=1 71 | shift 72 | ;; 73 | --system-type) 74 | system_type="$2" 75 | shift 2 76 | ;; 77 | --disk-layout) 78 | layout_file="$2" 79 | shift 2 80 | ;; 81 | --sector-size) 82 | sector_size="$2" 83 | shift 2 84 | ;; 85 | --run-on-first-boot) 86 | first_boot_script="$2" 87 | shift 2 88 | ;; 89 | -*) 90 | echo "Unknown option '$1'. Exiting." >&2 91 | exit 1 92 | ;; 93 | *) 94 | break 95 | ;; 96 | esac 97 | done 98 | 99 | # we need 2 more args 100 | if [ -z "$2" ] 101 | then 102 | usage_and_exit 1 103 | fi 104 | 105 | chroot_in="$1" 106 | image_out="$2" 107 | } 108 | 109 | parse_args "$@" 110 | 111 | # let's verify system_type variable 112 | case "$system_type" in 113 | 'live'|'installer') 114 | ;; # ok 115 | *) 116 | echo "--system-type option value must be either 'live' or 'installer'." >&2 117 | exit 1 118 | esac 119 | 120 | # let's verify layout_file variable 121 | if [ "$layout_file" != "default" ] 122 | then 123 | if [ ! -r "$layout_file" ] 124 | then 125 | echo "disk layout file '$layout_file' not found or not readable." >&2 126 | exit 1 127 | fi 128 | fi 129 | 130 | # ensure we are root 131 | if [ $(id -u) -ne 0 ]; then 132 | echo "debootstick should be run as root. Trying sudo..." 133 | exec sudo "$0" "$@" 134 | fi 135 | 136 | # check that $image_out is a writable file path 137 | if [ ! -w $(dirname "$image_out") ] 138 | then 139 | usage_and_exit 140 | fi 141 | 142 | # $chroot_in should be a directory 143 | if [ ! -d "$chroot_in" ] 144 | then 145 | usage_and_exit 146 | fi 147 | 148 | # this directory should contain a system 149 | # file hierarchy (1st level of checks) 150 | check_fs_hierarchy "$chroot_in" 1 || exit 1 151 | 152 | # let's verify first_boot_script variable 153 | if [ -n "$first_boot_script" ] 154 | then 155 | if [ ! -x "${chroot_in}/${first_boot_script}" ] 156 | then 157 | echo "First boot script file '$first_boot_script' not found in chroot or missing execute permission." >&2 158 | exit 1 159 | fi 160 | fi 161 | 162 | # detect target type 163 | target_type="$(detect_target_type "$chroot_in")" 164 | [ -z "$target_type" ] && exit 1 165 | 166 | # get common and target-specific functions 167 | functions="$( 168 | cat "$DBSTCK_DIR"/scripts/create-image/common/* 169 | cat $(find "$DBSTCK_DIR"/scripts/create-image/target/$target_type/ \ 170 | -type f ! -name detect.sh) 171 | )" 172 | 173 | # have them available here and in chrooted scripts 174 | eval "$functions" 175 | export chrooted_functions="$functions" 176 | probe_target_optional_functions 177 | 178 | if $target_get_bootloader_install_command_exists 179 | then 180 | bootloader_install_command=$(target_get_bootloader_install_command) 181 | fi 182 | 183 | if [ "$system_type" = "installer" ] 184 | then 185 | if [ -z "$bootloader_install_command" ] || ! $(target_use_lvm) 186 | then 187 | # cannot use installer mode if target does not use LVM or does not 188 | # specify a bootloader installation procedure. 189 | echo "Sorry, this target does not support installer system type." >&2 190 | exit 1 191 | fi 192 | fi 193 | 194 | # if we are here, command line is ok :) 195 | if [ "$root_password_request" = "ASK" ] 196 | then 197 | while true 198 | do 199 | read -s -p "Enter embedded-os root password: " passwd1 200 | echo 201 | read -s -p "Enter embedded-os root password again: " passwd2 202 | echo 203 | if [ "$passwd1" = "$passwd2" ] 204 | then 205 | echo 'OK' 206 | root_password_request="root:$passwd1" 207 | break 208 | else 209 | echo 'Sorry, passwords do not match, please retry.' 210 | fi 211 | done 212 | fi 213 | 214 | ORIG_TREE="$(cd "$chroot_in"; pwd)" 215 | STICK_OS_ID=$(uuidgen | tr -d '-' | head -c 8) 216 | DBSTCK_TMPDIR=$(mktemp -du --tmpdir tmp.dbstck.XXXXX.d) 217 | final_image_path="$image_out" 218 | final_image_abspath="$(abspath "$image_out")" 219 | if [ "$DEBUG" = "1" ] 220 | then 221 | CHROOTED_DEBUG="--debug" 222 | fi 223 | 224 | final_cleanup() 225 | { 226 | return_code=$1 227 | if [ "$1" -gt 0 ] # if error 228 | then 229 | rm -f $final_image_abspath 230 | fi 231 | } 232 | 233 | echo "I: detected target system: $(target_get_name)" 234 | 235 | start_failsafe_mode --toplevel final_cleanup 236 | 237 | failsafe mkdir -p $DBSTCK_TMPDIR 238 | 239 | # step: parse and verify the layout file 240 | default_layout_file="$DBSTCK_DIR/disk-layouts/target/$target_type/disk-layout" 241 | if [ "$layout_file" = "default" ] 242 | then 243 | layout_file="$default_layout_file" 244 | echo "I: using default disk layout: $default_layout_file" 245 | else 246 | layout_file=$(abspath "$layout_file") 247 | echo -n "I: verifying disk layout file... " 248 | fi 249 | layout_dir="$DBSTCK_TMPDIR/.layout" 250 | parse_layout "$layout_dir" < "$layout_file" 251 | check_layout "$layout_dir" 252 | if [ "$layout_file" != "$default_layout_file" ] 253 | then 254 | def_layout_dir="$DBSTCK_TMPDIR/.def-layout" 255 | parse_layout "$def_layout_dir" < "$default_layout_file" 256 | check_layout_updates "$def_layout_dir" "$layout_dir" 257 | echo done 258 | fi 259 | 260 | need_fatresize=$(check_need_fatresize "$layout_dir") 261 | 262 | cd $DBSTCK_TMPDIR 263 | # execute target-specific preliminary steps, if any 264 | optional_target_preliminary_steps 265 | 266 | # step: create draft image structure 267 | echo -n "I: draft image - partitioning and formatting... " 268 | create_formatted_image \ 269 | draft \ 270 | $sector_size \ 271 | "$ORIG_TREE" \ 272 | $STICK_OS_ID 273 | draft_rootfs_mountpoint="$DBSTCK_TMPDIR/draft/fs" 274 | echo done 275 | 276 | # step: copy original tree to work image and modify it 277 | echo -n "I: draft image - copying filesystem tree... " 278 | cd $draft_rootfs_mountpoint 279 | cp -au "$ORIG_TREE"/* . 280 | echo done 281 | 282 | # execute target-specific preparation steps, if any 283 | optional_target_prepare_rootfs draft outside 284 | 285 | # 2nd level of checks of input file hierarchy 286 | check_fs_hierarchy "$PWD" 2 || exit 1 287 | 288 | echo -n "I: draft image - generating fstab... " 289 | generate_fstab "$layout_dir" 290 | echo done 291 | 292 | # copy host resolv.conf to help generating a proper one in guest 293 | cp /etc/resolv.conf etc/resolv.conf.host 294 | 295 | mkdir -p opt/debootstick 296 | cp -a $DBSTCK_DIR/scripts/live opt/debootstick/live 297 | cp -a $DBSTCK_DIR/scripts/create-image/chrooted-customization-draft.sh . 298 | draft_device="$(readlink $DBSTCK_TMPDIR/draft/device)" 299 | with mount -o bind /run $PWD/run; do 300 | # let's start the customization 301 | chroot . ./chrooted-customization-draft.sh $CHROOTED_DEBUG \ 302 | "$draft_device" "$root_password_request" \ 303 | stick_os_id=$STICK_OS_ID \ 304 | config_grub_on_serial_line=$config_grub_on_serial_line \ 305 | kernel_package="\"$kernel_package\"" \ 306 | kernel_bootargs="\"$kernel_bootargs\"" \ 307 | config_hostname="\"$config_hostname\"" \ 308 | need_fatresize=$need_fatresize 309 | done 310 | rm ./chrooted-customization-draft.sh 311 | 312 | # execute target-specific cleanup steps, if any 313 | optional_target_cleanup_rootfs draft outside 314 | 315 | # step: finalyse filesystem setup 316 | finalize_fs $draft_rootfs_mountpoint 317 | 318 | # step: prepare a final image with minimal size 319 | echo -n "I: final image - partitioning and formatting... " 320 | create_formatted_image \ 321 | final \ 322 | $sector_size \ 323 | $draft_rootfs_mountpoint \ 324 | ${STICK_OS_ID} \ 325 | $final_image_abspath 326 | final_rootfs_mountpoint="$DBSTCK_TMPDIR/final/fs" 327 | echo done 328 | echo -n "I: final image - copying content from draft image... " 329 | cp -au $draft_rootfs_mountpoint/* $final_rootfs_mountpoint/ 330 | echo done 331 | release_image draft # not needed anymore 332 | 333 | # complete the dbstck.conf file 334 | cat >> $final_rootfs_mountpoint/dbstck.conf << EOF 335 | STICK_OS_ID=$STICK_OS_ID 336 | USE_LVM=$(target_use_lvm) 337 | SYSTEM_TYPE=$system_type 338 | ASK_ROOT_PASSWORD_ON_FIRST_BOOT=$root_password_on_first_boot 339 | BOOTLOADER_INSTALL=$bootloader_install_command 340 | PARTITIONS="$(dump_partition_volumes_info "$layout_dir")" 341 | LVM_VOLUMES="$(dump_lvm_volumes_info "$layout_dir")" 342 | IMAGE_SIZE_MB="$(cat $layout_dir/needed_size_mb)" 343 | EOF 344 | 345 | if [ -f "$layout_dir"/final_vg_name ] 346 | then 347 | cat >> $final_rootfs_mountpoint/dbstck.conf << EOF 348 | FINAL_VG_NAME="$(cat "$layout_dir"/final_vg_name)" 349 | VG_RENAME="$(target_get_vg_rename_command)" 350 | EOF 351 | fi 352 | 353 | if [ -n "$first_boot_script" ] 354 | then 355 | cat >> $final_rootfs_mountpoint/dbstck.conf << EOF 356 | FIRST_BOOT_SCRIPT="$first_boot_script" 357 | EOF 358 | fi 359 | 360 | # step: customize final OS 361 | cd $final_rootfs_mountpoint 362 | final_device="$(readlink $DBSTCK_TMPDIR/final/device)" 363 | 364 | echo -n "I: final image - generating fstab... " 365 | generate_fstab "$layout_dir" 366 | echo done 367 | 368 | # execute target-specific preparation steps, if any 369 | optional_target_prepare_rootfs final outside 370 | 371 | # since the size of the filesystem mounted there is minimized, 372 | # creating new files may cause problems. 373 | # so we will use the directory /tmp that we mount in memory. 374 | with mount -t tmpfs none $final_rootfs_mountpoint/tmp; do 375 | with mount -o bind /run $PWD/run; do 376 | cp -a $DBSTCK_DIR/scripts/create-image/chrooted-customization-final.sh tmp 377 | chroot . tmp/chrooted-customization-final.sh "$final_device" 378 | done 379 | done 380 | 381 | # execute target-specific cleanup steps, if any 382 | optional_target_cleanup_rootfs final outside 383 | 384 | cd .. 385 | 386 | # execute target-specific final steps, if any 387 | optional_target_final_customization_steps ${STICK_OS_ID} 388 | 389 | # step: clean up 390 | echo -n "I: cleaning up... " 391 | undo_all 392 | echo done 393 | 394 | chmod u+rw $final_image_abspath 395 | stick_size=$(real_size_human_readable $final_image_abspath) 396 | echo "I: $final_image_path ready (size: ${stick_size}). " 397 | -------------------------------------------------------------------------------- /debootstick.8: -------------------------------------------------------------------------------- 1 | .\" (C) Copyright 2015 Etienne Dublé , 2 | .\" 3 | .TH DEBOOTSTICK 8 "November 2, 2020" 4 | .\" Please adjust this date whenever revising the manpage. 5 | 6 | .SH NAME 7 | debootstick \- Generate a bootable image from a Debian-based chroot environment 8 | 9 | .SH SYNOPSIS 10 | .B debootstick 11 | .RI [ options ] 12 | .I SOURCE DEST 13 | 14 | .SH DESCRIPTION 15 | 16 | .B debootstick 17 | generates a bootable image (at \fIDEST\fP) from a Debian-based chroot environment (at \fISOURCE\fP). 18 | .br 19 | The output image generated at \fIDEST\fP should then be copied 20 | to a USB stick, disk or SD card. 21 | 22 | .PP 23 | \fBdebootstick\fP can currently generate bootable images for: 24 | .br 25 | - Standard PC systems (32 or 64bits) 26 | .br 27 | - Raspberry Pi boards 28 | .br 29 | This target system is automatically selected given the \fISOURCE\fP chroot environment 30 | (Debian/Ubuntu or Raspbian-based). 31 | .PP 32 | Most popular options for generating the \fISOURCE\fP directory are: 33 | .br 34 | - exporting the content of a \fBdocker\fP container 35 | .br 36 | - using dedicated tools such as \fBdebootstrap\fP(8) or \fBqemu-debootstrap\fP(1) 37 | .br 38 | See section \fBCHROOT ENVIRONMENTS\fP below. 39 | 40 | .PP 41 | The embedded system is: 42 | .br 43 | - ready to be used (no installation step) 44 | .br 45 | - viable in the long-term, fully upgradable (kernel, bootloader included) 46 | .br 47 | - compatible with BIOS and UEFI systems (PC), or Raspberry Pi Boards. 48 | 49 | .B debootstick 50 | can also generate installer media (for PCs). See option \fB\-\-system\-type\fP below. 51 | 52 | .SH OPTIONS 53 | .B debootstick 54 | follows the usual GNU command line syntax, with long 55 | options starting with two dashes (`\-'). 56 | A summary of options is included below. 57 | .TP 58 | .B \-h, \-\-help 59 | Show summary of options. 60 | .TP 61 | .B \-v, \-\-version 62 | Show version of program. 63 | .TP 64 | .B \-\-help\-os\-support 65 | Describe which chroot environments are supported. 66 | .TP 67 | .B \-\-system\-type [live|installer] 68 | Specify which kind of system is targeted. The default is \fBlive\fP. 69 | When booting a system where \fBinstaller\fP was selected, 70 | the system will try to migrate to a larger device on first startup. 71 | If \fBlive\fP was selected, or if no such option was specified, 72 | no migration will occur. 73 | See section \fBINSTALLER MEDIA\fP below. 74 | .TP 75 | .B \-\-kernel\-package PACKAGE_NAME 76 | Specify the kernel that should be installed. Without this option, \fBdebootstick\fP 77 | will install a default one (depending on the embedded distribution). 78 | .TP 79 | .B \-\-config\-hostname HOSTNAME 80 | Specify the hostname the embedded system will have. 81 | .TP 82 | .B \-\-config\-kernel\-bootargs BOOTARGS 83 | Specify boot arguments to be added/removed from the kernel cmdline. 84 | Use a plus sign to get a bootarg added and a minus sign to have it removed from the 85 | existing bootloader configuration. 86 | For example, \fB\-\-config\-kernel\-bootargs \(dq+console=ttyS0 -rootdelay\(dq\fP 87 | will add \fBconsole=ttyS0\fP to the kernel cmdline, and remove any parameter 88 | matching \fBrootdelay=\fP or just \fBrootdelay\fP. 89 | When no plus or minus sign is specified, the bootarg is added (like plus). 90 | An alternative to using this option is to have the bootloader installed and 91 | customized before you call \fBdebootstick\fP. 92 | .TP 93 | .B \-\-config\-root\-password\-ask 94 | Prompt for the root password of the embedded system and set it accordingly. 95 | .TP 96 | .B \-\-config\-root\-password\-none 97 | Remove the root password of the embedded system (root login will not prompt any password). 98 | .TP 99 | .B \-\-config\-root\-password\-first\-boot 100 | Ask for the root password when the system will be booted for the first time. 101 | .TP 102 | .B \-\-config\-grub\-on\-serial\-line 103 | Update grub configuration to show boot menu on serial line. (This is obviously PC-specific.) 104 | .TP 105 | .B \-\-disk\-layout DISK_LAYOUT_FILE 106 | Specify an alternate disk layout configuration file. See section \fBDISK LAYOUTS\fP below. 107 | .TP 108 | .B \-\-run\-on\-first\-boot FIRST_BOOT_SCRIPT_FILE 109 | Let \fBdebootstick\fP run the specified custom script (or other kind of executable) on first boot, 110 | after OS resizing or migration is completed. 111 | The path specified must be relative to the root of the chroot environment. 112 | .TP 113 | .B \-\-sector\-size SECTOR_SIZE 114 | Specify an alternate sector size (default is 512). 115 | For instance, to build an image that will be flashed on a 4Kn disk, use \fB\-\-sector\-size 4096\fP. 116 | .br 117 | See section \fBDISK SECTOR SIZE\fP below. 118 | 119 | .SH EXAMPLES 120 | 121 | The most common workflow is the following. 122 | 123 | .PP 124 | .B 1- 125 | Generate a chroot environment: 126 | .br 127 | \fBdebootstrap\fP \-\-variant=minbase buster /tmp/buster_tree 128 | 129 | .PP 130 | .B 2- 131 | (Optionally) customize it: 132 | .br 133 | \fBchroot\fP /tmp/buster_tree; [...]; exit 134 | 135 | .PP 136 | .B 3- 137 | Generate the bootable image: 138 | .br 139 | \fBdebootstick\fP \-\-config\-root\-password\-ask /tmp/buster_tree /tmp/img.dd 140 | .br 141 | Enter root password: 142 | .br 143 | Enter root password again: 144 | .br 145 | OK 146 | .br 147 | [...] 148 | .br 149 | 150 | .PP 151 | .B 4- 152 | Test it with kvm. 153 | .br 154 | \fBcp\fP /tmp/img.dd /tmp/img.dd\-test # let's work on a copy, our test is destructive 155 | .br 156 | \fBtruncate\fP \-s 2G /tmp/img.dd\-test # simulate a copy on a 2G-large USB stick 157 | .br 158 | \fBkvm\fP \-m 2048 \-hda /tmp/img.dd\-test # the test itself (BIOS mode) 159 | 160 | .PP 161 | .B 5- 162 | Copy the boot image to a USB stick or disk. 163 | .br 164 | \fBdd\fP bs=10M if=\fB/tmp/img.dd\fP of=/dev/your\-device 165 | 166 | .PP 167 | The USB device may now be booted on any BIOS or UEFI system. 168 | 169 | .SH CHROOT ENVIRONMENTS 170 | 171 | An example of chroot environment generation for a PC system is given in the 172 | previous section. 173 | 174 | .PP 175 | In order to generate a chroot environment for a Raspberry Pi, you can use 176 | \fBqemu-debootstrap\fP(1): 177 | .br 178 | \fBqemu\-debootstrap\fP \-\-no\-check\-gpg \-\-arch=armhf \-\-variant=minbase 179 | buster rpi\-fs http://mirrordirector.raspbian.org/raspbian 180 | 181 | .PP 182 | Exporting the OS files from a virtual machine or a docker container is another option 183 | to generate a chroot environment. 184 | The added benefit of this approach is that a virtualized environment is 185 | very convenient for the OS customization phase, before calling \fBdebootstick\fP. 186 | 187 | .SH TARGET SYSTEM ARCHITECTURES 188 | \fBdebootstick\fP expects a chroot environment built for amd64 or i386 systems, 189 | or for Raspberry Pi boards. 190 | Of course, the resulting image will reflect this initial architecture, and thus 191 | it should be booted on a compatible system. 192 | 193 | .SH INSTALLER MEDIA 194 | 195 | When first booting a system built with the \fB\-\-system\-type installer\fP 196 | option, it will look for a larger disk and move to that disk. 197 | This operation does not require a reboot. Once done, the system will just continue its 198 | bootup procedure (and the initial device can be removed). 199 | .PP 200 | Notes: 201 | .br 202 | - \fBCAUTION:\fP Any data on the target disk will be lost! 203 | .br 204 | - The system is \fBmoved\fP, not copied. Thus the initial device cannot be used 205 | anymore after the migration, unless you copy an image on it again, of course. 206 | .br 207 | - This option is \fBnot\fP available for Raspberry Pi boards. 208 | It would make little sense anyway, since the SD card is usually the only 209 | bootable media available on this kind of board. 210 | 211 | .SH UEFI BOOTING 212 | 213 | It is also possible to test the UEFI boot with \fBkvm\fP, if you have the 214 | \fBovmf\fP package installed, by adding \fB\-bios /path/to/OVMF.fd\fP to 215 | the \fBkvm\fP command line. 216 | 217 | .SH DISK LAYOUTS 218 | 219 | It is possible to modify the disk layout of the system \fBdebootstick\fP generates. 220 | .PP 221 | If option \fB\-\-disk\-layout\fP is not specified, a default layout file is used, 222 | and the path of this file is printed. 223 | .br 224 | The preferred way to write a new layout file is to copy this default file, modify it, 225 | and then add option \fB\-\-disk\-layout \fP. 226 | .br 227 | An example of a modification could be to set \fB/var\fP on a different partition or 228 | dedicated LVM volume. 229 | .PP 230 | Notes: 231 | .br 232 | - Not all modifications are allowed. \fBdebootstick\fP will print an error message if needed. 233 | .br 234 | - Currently \fBdebootstick\fP only handles fat and ext4 filesystems. 235 | .PP 236 | About the size of a partition or lvm volume: 237 | .br 238 | - \fBauto\fP means \fBdebootstick\fP will reserve enough space for this volume, with a little 239 | margin. For instance, on a /boot partition with fat filesystem, it will estimate the size 240 | needed for the files stored there and size the partition accordingly. 241 | .br 242 | - \fB[G|M]\fP (e.g. 1G or 50M) means debootstick should allocate exactly the specified 243 | size to this partition/volume. Use this preferably on LVM volumes or on the last disk 244 | partition: since previous disk partitions cannot be resized, \fBdebootstick\fP has to 245 | reserve the space for them on the disk image it generates, which can make it large. 246 | .br 247 | - \fB%\fP (e.g. 10%) means debootstick should allocate the given percentage of the 248 | disk to this partition/volume. 249 | .br 250 | - \fBmax\fP means debootstick should allocate any remaining free space to this partition/volume. 251 | .PP 252 | Keep in mind that debootstick is supposed to generate a minimal image, and, at this time, 253 | it has no knowledge about the size of the device where the image will be copied. 254 | Using \fBmax\fP and \fB%\fP on an lvm volume and on last partition allows one to ensure an 255 | appropriate disk layout, when the OS will expand itself over the device (or migrate), 256 | on first boot. 257 | .PP 258 | If LVM is used, it is possible to set a custom volume group name by using keyword \fBlvm_vg_name\fP. 259 | For instance, one could specify \fBlvm_vg_name "MYVG"\fP (quotes are optional). 260 | If not specified, or when special value \fBauto\fP is given instead of the group name, 261 | \fBdebootstick\fP generates a random name \fBDBSTCK_\fP. 262 | .br 263 | Note that on first boot, even if a volume group name was specified, the system will first use the 264 | random name \fBDBSTCK_\fP, and then rename it at the end of the bootup procedure. 265 | This allows the system to boot properly even if the target name conflicts with a volume group 266 | already present on a secondary disk. 267 | 268 | .SH DISK SECTOR SIZE 269 | 270 | If the image should be flashed on a disk with non-default logical sector size (default is 271 | 512 bytes), one may use option \fB\-\-sector\-size \fP to change it. 272 | 273 | The value of option \fB\-\-sector\-size\fP should match the logical sector size of \fBthe disk the 274 | image will be flashed on\fP. Usually, this disk is a removable device with a logical sector size of 275 | 512 bytes. Thus, in a vast majority of cases debootstick should generate a compatible image with its 276 | default option value. 277 | 278 | When using the installer mode, the fact the target disk (i.e. the disk the OS will finally migrate 279 | to) has a different sector size does \fBnot\fP mean the image sector size should be changed. 280 | 281 | .SH DESIGN NOTES 282 | 283 | Many Live distributions propose a highly compressed system based on a squashfs image. 284 | They handle writes using an overlay based on a filesystem union. 285 | While this allows the system to remain compact in the first times, this also has 286 | disavantages: 287 | .br 288 | - Some important files remain read-only and cannot be upgraded (that is the case of 289 | the linux kernel and the bootloader) which quickly leads to security issues or upgrade 290 | problems. 291 | .br 292 | - Storing modified files in an overlay and never releasing the room needed for 293 | the original versions in the squashfs image is counter-productive in the long term. 294 | .br 295 | One of the objectives \fBdebootstick\fP achieves is to provide a viable long-term 296 | live system, therefore this kind of setup has been discarded. 297 | 298 | .SH AUTHORS 299 | Etienne Duble (etienne.duble@imag.fr) and contributors. 300 | 301 | .SH SEE ALSO 302 | .BR debootstrap (8), 303 | .BR qemu-debootstrap (1), 304 | .BR kvm (1). 305 | -------------------------------------------------------------------------------- /disk-layouts/target/pc/disk-layout: -------------------------------------------------------------------------------- 1 | # gpt or dos partition table 2 | gpt 3 | 4 | # ordered list of partitions (with mountpoint, type, size) 5 | partition /boot/efi efi auto 6 | partition none bios auto 7 | partition none lvm max 8 | 9 | # lvm volumes (with mountpoint, type, size) 10 | lvm_volume / ext4 max 11 | 12 | # lvm volume group name 13 | lvm_vg_name auto 14 | -------------------------------------------------------------------------------- /disk-layouts/target/rpi/disk-layout: -------------------------------------------------------------------------------- 1 | # gpt or dos partition table 2 | dos 3 | 4 | # ordered list of partitions (with mountpoint, type, size) 5 | partition /boot fat 50M 6 | partition / ext4 max 7 | -------------------------------------------------------------------------------- /scripts/create-image/chrooted-customization-draft.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PACKAGES="lvm2 gdisk e2fsprogs dosfstools kmod udev" 3 | eval "$chrooted_functions" 4 | probe_target_optional_functions 5 | start_failsafe_mode 6 | # in the chroot commands should use /tmp for temporary files 7 | export TMPDIR=/tmp 8 | 9 | if [ "$1" = "--debug" ] 10 | then 11 | debug=1 12 | shift 13 | else 14 | debug=0 15 | fi 16 | 17 | loop_device=$1 18 | root_password_request=$2 19 | shift 2 20 | # eval = parameters 21 | while [ ! -z "$1" ] 22 | do 23 | eval "$1" 24 | shift 25 | done 26 | 27 | failsafe mount -t proc none /proc >/dev/null 28 | failsafe_mount_sys_and_dev >/dev/null 29 | export DEBIAN_FRONTEND=noninteractive LANG=C 30 | 31 | optional_target_prepare_rootfs draft inside 32 | 33 | # We will need internet connectivity for package management. 34 | # Ensure we have a valid DNS setup. 35 | resolv_conf_orig_status=$(ensure_valid_resolv_conf) 36 | if [ "$resolv_conf_orig_status" != "ok" ] 37 | then 38 | echo "I: draft image - generated /etc/resolv.conf (it was missing or incomplete)" 39 | fi 40 | 41 | if $target_custom_packages_exists 42 | then 43 | PACKAGES="$PACKAGES $(target_custom_packages)" 44 | fi 45 | 46 | # install missing packages 47 | echo -n "I: draft image - updating package manager database... " 48 | apt-get update -qq 49 | echo done 50 | 51 | # fdisk & sfdisk are part of util-linux, but on newer OS they are 52 | # in a dedicated package. Ensure it is installed. 53 | if package_exists fdisk 54 | then 55 | PACKAGES="$PACKAGES fdisk" 56 | fi 57 | 58 | # if needed (a FAT partition or volume will have to grow) install 59 | # fatresize (package is in section 'universe' on Ubuntu). 60 | if [ $need_fatresize -eq 1 ] 61 | then 62 | PACKAGES="$PACKAGES fatresize" 63 | if ! package_exists fatresize 64 | then 65 | echo "I: draft image - package fatresize not found." 66 | echo -n "I: draft image - trying to add section universe in apt sources (for fatresize)... " 67 | apt_sources_add_section universe 68 | echo done 69 | echo -n "I: draft image - updating package manager database again... " 70 | apt-get update -qq 71 | echo done 72 | fi 73 | fi 74 | 75 | if [ -z "$kernel_package" ] 76 | then 77 | # kernel package not specified, install a default one 78 | kernel_search_regexp="^$(target_kernel_default_package)$" 79 | error_if_missing="$( 80 | echo "E: no linux kernel package found." 81 | echo "E: Run 'debootstick --help-os-support' for more info." 82 | )" 83 | else 84 | kernel_search_regexp="^${kernel_package}$" 85 | error_if_missing="E: no such package '$kernel_package'" 86 | fi 87 | 88 | kernel_package_found=$( 89 | list_available_packages "$kernel_search_regexp") 90 | if [ -z "$kernel_package_found" ] 91 | then 92 | echo "$error_if_missing" 93 | exit 1 94 | fi 95 | 96 | to_be_installed="" 97 | for package in $kernel_package_found $PACKAGES 98 | do 99 | if [ $(package_is_installed $package) -eq 0 ] 100 | then 101 | to_be_installed="$to_be_installed $package" 102 | fi 103 | done 104 | 105 | [ -f /sbin/init ] || to_be_installed="$to_be_installed init" 106 | 107 | if [ "$to_be_installed" != "" ] 108 | then 109 | echo -n "I: draft image - installing packages:${to_be_installed}... " 110 | install_packages $to_be_installed 111 | echo done 112 | fi 113 | 114 | # keep it small 115 | apt-get -qq clean 116 | rm -rf /var/lib/apt/lists/* 117 | 118 | # tune LVM config 119 | tune_lvm 120 | 121 | # for text console in kvm 122 | if [ "$debug" = "1" ] 123 | then 124 | # if OS init is upstart, create a service 125 | # in order to start a shell when the system is ready 126 | if [ -f '/sbin/upstart' ] 127 | then 128 | cat > ./etc/init/ttyS0.conf << EOF 129 | start on stopped rc or RUNLEVEL=[12345] 130 | stop on runlevel [!12345] 131 | 132 | respawn 133 | exec /sbin/getty -L 115200 ttyS0 xterm 134 | EOF 135 | fi 136 | fi 137 | 138 | # set the root password if requested 139 | case "$root_password_request" in 140 | "NO_REQUEST") 141 | true # nothing to do 142 | ;; 143 | "NO_PASSWORD") 144 | passwd -dq root # remove root password 145 | ;; 146 | *) # change root password 147 | echo "$root_password_request" | chpasswd 148 | ;; 149 | esac 150 | 151 | echo -n "I: draft image - setting up bootloader... " 152 | target_configure_bootloader 153 | 154 | # installing grub on this temporary work-image 155 | # may not seem useful (it will be repeated on the final 156 | # stick anyway), but actually it is: 157 | # the files created there should be accounted when 158 | # estimating the final stick minimal size). 159 | target_install_bootloader 160 | 161 | echo done 162 | 163 | # check if target-specific code specified 164 | # 'applied_kernel_cmdline' variable 165 | if [ ! -z ${applied_kernel_cmdline+x} ] 166 | then 167 | bootargs="$applied_kernel_cmdline" 168 | if [ -z "$bootargs" ] 169 | then 170 | bootargs="" 171 | fi 172 | echo "I: draft image - kernel bootargs: $bootargs" 173 | fi 174 | 175 | if [ "$config_hostname" != "" ] 176 | then 177 | echo -n "I: draft image - setting hostname... " 178 | echo "$config_hostname" > /etc/hostname 179 | echo done 180 | fi 181 | 182 | echo -n "I: draft image - performing sanity checks... " 183 | should_update_hosts_file=$(missing_or_empty /etc/hosts) 184 | should_update_locale_file=$(missing_or_empty /etc/default/locale) 185 | echo done 186 | 187 | if [ $should_update_hosts_file -eq 1 ] 188 | then 189 | echo -n "I: draft image - generating /etc/hosts (it was empty or missing)... " 190 | generate_hosts_file 191 | echo done 192 | fi 193 | 194 | if [ $should_update_locale_file -eq 1 ] 195 | then 196 | echo -n "I: draft image - adding missing locale definition... " 197 | mkdir -p /etc/default 198 | echo "LC_ALL=C" > /etc/default/locale 199 | echo done 200 | fi 201 | 202 | possibly_restore_resolv_conf 203 | 204 | optional_target_cleanup_rootfs draft inside 205 | # umount all 206 | undo_all 207 | 208 | -------------------------------------------------------------------------------- /scripts/create-image/chrooted-customization-final.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | loop_device=$1 3 | 4 | eval "$chrooted_functions" 5 | probe_target_optional_functions 6 | start_failsafe_mode 7 | 8 | # in the chroot commands should use /tmp for temporary files 9 | export TMPDIR=/tmp 10 | export DEBIAN_FRONTEND=noninteractive LANG=C 11 | 12 | # classical mounts 13 | failsafe mount -t proc none /proc >/dev/null 14 | failsafe_mount_sys_and_dev >/dev/null 15 | 16 | optional_target_prepare_rootfs final inside 17 | 18 | echo -n "I: final image - setting up the bootloader... " 19 | target_install_bootloader 20 | echo done 21 | 22 | optional_target_cleanup_rootfs final inside 23 | 24 | # umount things 25 | undo_all 26 | 27 | -------------------------------------------------------------------------------- /scripts/create-image/cmdline: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | usage_and_exit() 4 | { 5 | echo "Usage: debootstick [options] " >&2 6 | exit $1 7 | } 8 | 9 | describe_os_support() 10 | { 11 | cat << EOF 12 | This version of debootstick was tested successfully with the following kinds of 13 | chroot-environments: 14 | * OS for Raspberry Pi: 15 | - Raspberry Pi OS (formerly "rasbian") bullseye 16 | * OS for 32 or 64 bit PCs: 17 | - Debian 12 (bookworm) as of october 20, 2022 18 | - Debian 11 (bullseye) 19 | - Ubuntu 22.10 (kinetic) 20 | - Ubuntu 22.04 LTS (jammy) 21 | EOF 22 | } 23 | -------------------------------------------------------------------------------- /scripts/create-image/common/apt: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # Package management 4 | # ------------------ 5 | package_is_installed() 6 | { 7 | dpkg-query -W --showformat='${Status}\n' \ 8 | $1 2>/dev/null | happy_grep -c "^i" 9 | } 10 | 11 | list_available_packages() 12 | { 13 | apt-cache search "$1" | awk '{print $1}' 14 | } 15 | 16 | package_exists() 17 | { 18 | [ "$(list_available_packages "^$1$")" = "$1" ] 19 | } 20 | 21 | apt_sources_add_section() 22 | { 23 | section="$1" 24 | if grep "^#.*\\)/\1/g" /etc/apt/sources.list 28 | else 29 | # no lines for this section, edit lines with section main 30 | sed -i -e "s/\(^[[:space:]]*deb[[:space:]].*\\)/\1 ${section}/g" /etc/apt/sources.list 31 | fi 32 | } 33 | 34 | # with some OS versions, package installation 35 | # causes many things to be printed to stderr. Some of those things 36 | # are just informational, others are very minor warnings. 37 | # we will silence them with grep. 38 | INSTALL_GREP_PATTERN="$(cat << EOF | tr -d '\n' 39 | (delaying package configuration)|(Done.)|(^Moving old)|(^Running)| 40 | (^update-initramfs: deferring)|(^Examining)|(^run-parts: executing)| 41 | (^update-initramfs: Generating)|(^initrd.img)|(points to)| 42 | (doing nothing)|(^vmlinu)|(connect to Upstart)|(policy-rc.d denied)| 43 | (Creating config)|(^$)|(start and stop actions)|(^Created symlink)| 44 | (Initializing machine ID)|(:$)|(etc.modprobe.d)| 45 | (initramfs support missing)|(policy-rc.d returned 101)| 46 | (update-alternatives: warning: skip creation)| 47 | (Secure Boot validation state)|(if you wish to change)| 48 | (default time zone)|(time is now)|(Time is now)| 49 | (^Creating group)|(^Creating user)|(using gzip) 50 | EOF 51 | )" 52 | 53 | install_packages() 54 | { 55 | packages=$* 56 | 57 | # disable service startup at package installation 58 | if [ -e /usr/sbin/policy-rc.d ] 59 | then 60 | mv /usr/sbin/policy-rc.d /usr/sbin/policy-rc.d.saved 61 | fi 62 | echo exit 101 > /usr/sbin/policy-rc.d 63 | chmod +x /usr/sbin/policy-rc.d 64 | 65 | err_output="$( 66 | apt-get -qq --no-install-recommends -o=Dpkg::Use-Pty=0 \ 67 | install $packages 2>&1 >/dev/null 68 | )" || return_code=$? 69 | 70 | echo "$err_output" | happy_grep -vE "$INSTALL_GREP_PATTERN" 1>&2 71 | 72 | # restore policy-rc.d conf 73 | if [ -e /usr/sbin/policy-rc.d.saved ] 74 | then 75 | mv /usr/sbin/policy-rc.d.saved /usr/sbin/policy-rc.d 76 | else 77 | rm /usr/sbin/policy-rc.d 78 | fi 79 | 80 | # the return value we want is the one we caught 81 | # earlier (or none if all went well): 82 | return $return_code 83 | } 84 | -------------------------------------------------------------------------------- /scripts/create-image/common/basic: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # basic routines 4 | # -------------- 5 | missing_or_empty() 6 | { 7 | f="$1" 8 | if [ ! -e "$f" ] 9 | then 10 | echo 1 11 | return 12 | else 13 | if [ "$(cat "$f" | wc -l)" -eq 0 ] 14 | then 15 | echo 1 16 | return 17 | fi 18 | fi 19 | echo 0 20 | } 21 | 22 | num_dir_entries() { 23 | d="$1" 24 | if [ ! -d "$d" ] 25 | then 26 | echo 0 27 | else 28 | ls -1 "$d" | wc -l 29 | fi 30 | } 31 | 32 | abspath() 33 | { 34 | echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" 35 | } 36 | 37 | print_last_word() 38 | { 39 | awk '{print $NF}' 40 | } 41 | 42 | # divide %1/%2 rounding up 43 | ceil() 44 | { 45 | divide=$1 46 | by=$2 47 | echo $(((divide+by-1)/by)) 48 | } 49 | 50 | estimated_size_mb() 51 | { 52 | du -sm "$1" | awk '{print $1}' 53 | } 54 | 55 | real_size_human_readable() 56 | { 57 | du -sh --apparent-size "$1" | awk '{print $1}' 58 | } 59 | 60 | device_size_mb() 61 | { 62 | size_b=$(blockdev --getsize64 $1) 63 | echo $((size_b/1024/1024)) 64 | } 65 | 66 | size_as_mb() 67 | { 68 | echo $(($(echo $1 | sed -e "s/M//" -e "s/G/*1024/"))) 69 | } 70 | 71 | check_integer() { 72 | case "$1" in 73 | ''|*[!0-9]*) 74 | return 1 ;; # not a number 75 | *) 76 | return 0 ;; # number 77 | esac 78 | } 79 | 80 | sum_lines() { 81 | exp="$(paste -sd+ -)" 82 | [ "$exp" = "" ] && echo 0 || echo "$(($exp))" 83 | } 84 | 85 | # grep returns non-zero if no line is found. 86 | # in some cases, having no line is expected 87 | # (e.g. in the case of error lines) 88 | # thus the or-true construct. 89 | happy_grep() 90 | { 91 | grep "$@" || true 92 | } 93 | 94 | # get total size needed taking into account 95 | # an overhead. 96 | # $1: the initial size (not taking the overhead into account) 97 | # $2: percent of overhead 98 | # return value: the size needed (such that applying the 99 | # overhead would get $1 again) 100 | apply_overhead_percent() 101 | { 102 | echo $((($1)*100/(100-($2)))) 103 | } 104 | 105 | quiet() 106 | { 107 | $* >/dev/null 108 | } 109 | 110 | wait_for_device() 111 | { 112 | while [ ! -e "$1" ] 113 | do 114 | sleep 0.1 115 | done 116 | } 117 | 118 | get_vg_name() 119 | { 120 | case "$1" in 121 | "draft") 122 | echo "DRAFT_$2" 123 | ;; 124 | "final") 125 | echo "DBSTCK_$2" 126 | ;; 127 | esac 128 | } 129 | 130 | get_rootfs_uuid() 131 | { 132 | image_name="$1" # final or draft 133 | cat $DBSTCK_TMPDIR/$image_name/rootfs_vol/uuid 134 | } 135 | -------------------------------------------------------------------------------- /scripts/create-image/common/bootargs: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # kernel command line customization 4 | # --------------------------------- 5 | 6 | # The behavior of the following function is better 7 | # understood with the following example: 8 | # 9 | # $ to_bootarglist quiet rootdelay=3 -quiet rootdelay=2 +break=init 10 | # + quiet 11 | # + rootdelay 3 12 | # - quiet 13 | # + rootdelay 2 14 | # + break init 15 | # $ 16 | 17 | to_bootarglist() 18 | { 19 | echo $@ | tr ' ' '\n' | tr '=' ' ' | \ 20 | sed -e 's/^\([^+-]\)/+\1/g' | sed -e 's/^\(.\)/\1 /g' 21 | } 22 | 23 | # The following function allows to generate an appropriate kernel cmdline 24 | # given a set of bootarg definitions. 25 | # For a given bootarg name, last definition provided takes precedence. 26 | # Example: 27 | # 28 | # $ aggregate_kernel_cmdline quiet rootdelay=3 -quiet rootdelay=2 +break=init 29 | # break=init rootdelay=2 30 | # $ 31 | 32 | aggregate_kernel_cmdline() 33 | { 34 | bootarglist="$(to_bootarglist $@)" 35 | bootargnames="$(echo "$bootarglist" | awk '{print $2}' | sort -u)" 36 | cmdline="" 37 | for bootarg in $bootargnames 38 | do 39 | # last definition of given bootarg takes precedence 40 | set -- $(echo "$bootarglist" | grep -w "$bootarg" | tail -n 1) 41 | if [ "$1" = "+" ] # otherwise ignore this bootarg 42 | then 43 | # ok register this bootarg 44 | if [ -z "$3" ] 45 | then 46 | cmdline="$cmdline $bootarg" # bootarg with no value 47 | else 48 | cmdline="$cmdline $bootarg=$3" # bootarg with value 49 | fi 50 | fi 51 | done 52 | echo $cmdline 53 | } 54 | -------------------------------------------------------------------------------- /scripts/create-image/common/failsafe: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # failsafe mode handling 4 | # ---------------------- 5 | # we want to leave the system in a clean state, 6 | # whatever happens. 7 | # for example, if a "disk full" error happens 8 | # in the middle of the chrooted-customization 9 | # step, we should be able to umount what have 10 | # been mounted in the chroot, exit the chroot, 11 | # umount things and remove peripherals created 12 | # by debootstick outside the chroot, before 13 | # exiting. 14 | # we handle this by trapping the EXIT 15 | # of the scripts. Also, each command creating 16 | # a persistent artefact (mounts, devices, etc.) 17 | # is recorded, in order to be able to 'undo the 18 | # command' (i.e. remove the artefacts) if needed. 19 | 20 | undo_all() 21 | { 22 | # run saved failsafe commands prefixed with 'undo_' 23 | eval "$(tac $FAILSAFE_COMMANDS | \ 24 | awk '{print "undo_" $0}')" 25 | 26 | # flush file 'FAILSAFE_COMMANDS' 27 | echo -n > $FAILSAFE_COMMANDS 28 | } 29 | 30 | on_sigint() 31 | { 32 | trap - INT EXIT USR1 33 | on_exit --from-signal $* 34 | exit 1 35 | } 36 | 37 | on_sigusr1() 38 | { 39 | trap - INT EXIT USR1 40 | # exiting because of wrong user input 41 | on_exit $* 42 | exit 1 43 | } 44 | 45 | on_exit() 46 | { # save exit code 47 | res=$? 48 | 49 | # get args 50 | toplevel=0 51 | fromsignal=0 52 | if [ "$1" = "--from-signal" ] 53 | then 54 | fromsignal=1 55 | shift 56 | fi 57 | if [ "$1" = "--toplevel" ] 58 | then 59 | toplevel=1 60 | shift 61 | fi 62 | cleanup_function=$1 63 | 64 | # if unexpected, inform user 65 | if [ $toplevel -eq 1 ] 66 | then 67 | if [ $fromsignal -eq 1 ] 68 | then 69 | echo 70 | echo "Interrupted." 71 | fi 72 | if [ $res -gt 0 ] 73 | then 74 | echo 75 | echo "E: an error occured." >&2 76 | echo "E: did you try 'debootstick --help-os-support'?" >&2 77 | fi 78 | fi 79 | 80 | if [ -s $FAILSAFE_COMMANDS ] # if not empty 81 | then 82 | # inform user 83 | if [ $toplevel -eq 1 ] 84 | then 85 | echo -n "I: restoring a clean state... " 86 | fi 87 | 88 | # undo operations (remove artefacts) 89 | undo_all 90 | rm $FAILSAFE_COMMANDS 91 | 92 | # inform user 93 | if [ $toplevel -eq 1 ] 94 | then 95 | echo "done" 96 | fi 97 | fi 98 | 99 | # call an additional cleanup function 100 | # if provided. 101 | if [ ! -z "$cleanup_function" ] 102 | then 103 | $cleanup_function $res 104 | fi 105 | 106 | return $res 107 | } 108 | 109 | start_failsafe_mode() 110 | { 111 | # stop if an error occurs 112 | set -e 113 | # clean remaining artefacts before exitting 114 | trap "on_exit $*" EXIT 115 | trap "on_sigint $*" INT 116 | trap "on_sigusr1 $*" USR1 117 | 118 | # allow with constructs (see f_with function) 119 | alias with="while f_with" 120 | 121 | # create a temporary file to save commands 122 | FAILSAFE_COMMANDS=$(mktemp) 123 | 124 | # bash does not expand aliases by default, 125 | # when running a script. 126 | # busybox sh does, and has no such configuration 127 | # option (thus the error ignoring construct) 128 | shopt -s expand_aliases 2>/dev/null || true 129 | } 130 | 131 | exit_wrong_user_input() 132 | { 133 | kill -USR1 $$ 134 | } 135 | 136 | undo_mount_with_prefix() 137 | { 138 | # I know 2 usual things that could cause umount 139 | # to fail with an error reporting that 'device is busy'. 140 | # Either one process has its current directory on this 141 | # mount, or there is cached data that was not yet 142 | # written to disk. We handle these below. 143 | for last; do true; done # retrieve last arg 144 | cd / # just in case we would be on the mountpoint 145 | # some say that a sync request is treated asynchronously. 146 | # but if a second one comes in, then the first one is 147 | # forced. Thus the 2 requests in row: 148 | sync; sync 149 | $1 umount "$last" 150 | # try to return to previous dir if possible 151 | cd - >/dev/null 2>&1 || true 152 | } 153 | 154 | undo_mount() 155 | { 156 | undo_mount_with_prefix "" $* 157 | } 158 | 159 | undo_busybox_mount() 160 | { 161 | undo_mount_with_prefix "$busybox_path" $* 162 | } 163 | 164 | undo_mkdir() 165 | { 166 | for last; do true; done # retrieve last arg 167 | rm -rf "$last" 168 | } 169 | 170 | undo_losetup() 171 | { # we assume the failsafe command was 172 | # $ failsafe losetup --sector-size 173 | losetup -d "$3" 174 | } 175 | 176 | undo_partx() 177 | { # we assume the failsafe command was 178 | # $ failsafe partx -a 179 | disk_device="$2" 180 | 181 | # we have to detach lvm devices associated 182 | # to the , they keep the related 183 | # partition in a busy state otherwise. 184 | # Retrieving these devices is not so easy... 185 | partitions=$(get_loop_device_partitions $disk_device) 186 | vg_names=$(pvs -o vg_name --noheadings $partitions 2>/dev/null || true) 187 | if [ ! -z "$vg_names" ] 188 | then 189 | lv_devices=$(lvs -o vg_name,lv_name --noheadings $vg_names | \ 190 | awk '{print "/dev/" $1 "/" $2}') 191 | for lv_device in $lv_devices 192 | do 193 | lvchange -an $lv_device 194 | if [ -e $lv_device ] 195 | then 196 | dmsetup remove $lv_device 197 | fi 198 | done 199 | fi 200 | 201 | # we can now request the kernel to remove 202 | # partitions 203 | partx -d "$disk_device" 204 | 205 | # update lvm knowledge about physical volumes 206 | pvscan --cache >/dev/null 207 | } 208 | 209 | undo_chroot() 210 | { 211 | exit 212 | } 213 | 214 | failsafe() 215 | { 216 | $* && echo "$*" >> $FAILSAFE_COMMANDS 217 | } 218 | 219 | # workaround the fact losetup sometimes fail with 220 | # 'resource temporarily unavailable' error. 221 | # (sometimes seen when using "--sector-size 4096" option) 222 | failsafe_losetup() 223 | { 224 | while [ 1 ] 225 | do 226 | losetup "$@" 2>/dev/null && break || true 227 | sleep 0.1 # retry after a short delay 228 | done 229 | echo losetup "$@" >> $FAILSAFE_COMMANDS 230 | } 231 | 232 | undo() 233 | { 234 | # undo-ing one failsafe operation only 235 | 236 | # we have to remove this operation from 237 | # file 'FAILSAFE_COMMANDS'. 238 | # first, we escape it in order to use 239 | # it in a sed statement below. 240 | escaped_cmd="$( 241 | echo "$*" | \ 242 | sed -e 's/[\/&]/\\&/g')" 243 | # and now we remove it 244 | sed -i -e "/^$escaped_cmd\$/d" $FAILSAFE_COMMANDS 245 | 246 | # of course we really undo it 247 | eval "undo_$*" 248 | } 249 | 250 | # the function f_with() allows constructs such as: 251 | # 252 | # with mount [...]; do 253 | # [...] 254 | # done 255 | # 256 | # The unmount-ing will be done at the end of the 257 | # block regardless of what happens inside (issue raised 258 | # or not). 259 | # 260 | # 'with' is actually an alias involving this function 261 | # and a while loop: 262 | # with -> while f_with (see 'start_failsafe_mode') 263 | # 264 | # we ensure that the while loop stops at the 2nd 265 | # iteration. 266 | f_with() 267 | { 268 | # save the command 269 | cmd=$* 270 | # we need an id to recognise this construct 271 | with_id=$(echo $cmd | md5sum | awk '{print $1}') 272 | # let's load the stack of ids we have 273 | set -- $with_ids_stack 274 | 275 | # if this is a new id... 276 | if [ "$1" != "$with_id" ] 277 | then 278 | # this is a new 'with' construct 279 | # perform the command requested 280 | failsafe $cmd 281 | # update the stack 282 | with_ids_stack="$with_id $with_ids_stack" 283 | return 0 # continue the while loop 284 | else 285 | # second (and last) time through this 'with' construct 286 | # pop this id from the stack 287 | shift; with_ids_stack=$* 288 | # revert the command 289 | undo $cmd 290 | return 1 # stop the while loop 291 | fi 292 | } 293 | -------------------------------------------------------------------------------- /scripts/create-image/common/files: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | generate_fstab() 4 | { 5 | layout_dir="$1" 6 | 7 | echo "# " > etc/fstab 8 | 9 | generate_mount_info "$layout_dir" | while read vol_dir mounttype uuid vol_mountpoint 10 | do 11 | echo "UUID=$uuid $vol_mountpoint $mounttype errors=remount-ro 0 1" >> etc/fstab 12 | done 13 | } 14 | 15 | generate_hosts_file() 16 | { 17 | cat > /etc/hosts << EOF 18 | 127.0.0.1 localhost 19 | ::1 ip6-localhost ip6-loopback 20 | fe00::0 ip6-localnet 21 | ff00::0 ip6-mcastprefix 22 | ff02::1 ip6-allnodes 23 | ff02::2 ip6-allrouters 24 | EOF 25 | } 26 | 27 | ensure_valid_resolv_conf() 28 | { 29 | status="ok" 30 | 31 | # check if resolv.conf is missing 32 | # (we use `stat` and not [ -e [...] ] to avoid trying to 33 | # dereference a broken symlink). 34 | if ! stat "/etc/resolv.conf" >/dev/null 2>&1 35 | then 36 | status="missing" 37 | # check if file is invalid (no 'nameserver' directive) 38 | elif [ -f "/etc/resolv.conf" ] 39 | then 40 | if ! grep -q "^nameserver" /etc/resolv.conf 41 | then 42 | # invalid 43 | rm -f /etc/resolv.conf 44 | status="missing" 45 | fi 46 | fi 47 | 48 | # in more complex scenarios (e.g. it is a symlink currently broken 49 | # to /run/systemd/resolve/stub-resolv.conf), we consider the setup 50 | # is OK and/or the user knows what he is doing. 51 | 52 | # in any case, we must ensure we have a valid setup during package 53 | # downloads. so we temporarily move the file and replace it with 54 | # an appropriate conf. 55 | if [ "$status" = "ok" ] 56 | then 57 | mv /etc/resolv.conf /etc/resolv.conf.chroot-orig 58 | fi 59 | 60 | generate_resolv_conf_file 61 | 62 | echo "$status" # return status to caller 63 | } 64 | 65 | possibly_restore_resolv_conf() 66 | { 67 | if stat "/etc/resolv.conf.chroot-orig" >/dev/null 2>&1 68 | then 69 | rm -f /etc/resolv.conf 70 | mv /etc/resolv.conf.chroot-orig /etc/resolv.conf 71 | fi 72 | } 73 | 74 | generate_resolv_conf_file() 75 | { 76 | # note: this generated file may be overriden later in the procedure 77 | # if debootstick has to install one missing package including a DNS 78 | # resolver (e.g. systemd). In any case this file is needed right 79 | # know for the package downloading to succeed. 80 | # We copy the name servers which were used on the host and also add 81 | # a few public ones. 82 | # The one on the host may be '127.0.0.1' (e.g. the host may be running 83 | # a local service, e.g., for caching) which will work when the image 84 | # is being built but not when the target image is booted. 85 | # On the other hand the public nameservers may be unreachable because 86 | # of firewalling. 87 | # That's why we indicate all of them for more robustness. 88 | cat > /etc/resolv.conf << EOF 89 | # the following nameservers are those which were defined 90 | # on the machine where debootstick was run 91 | # ------------------------------------------------------ 92 | EOF 93 | grep nameserver /etc/resolv.conf.host >> /etc/resolv.conf 94 | rm /etc/resolv.conf.host 95 | 96 | cat >> /etc/resolv.conf << EOF 97 | 98 | # the following nameservers are public ones 99 | # ------------------------------------------------------ 100 | # cloudflare 101 | nameserver 1.1.1.1 102 | # google 103 | nameserver 8.8.8.8 104 | # opendns 105 | nameserver 208.67.220.220 106 | EOF 107 | } 108 | -------------------------------------------------------------------------------- /scripts/create-image/common/finalize: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | finalize_fs() 4 | { 5 | fs_tree="$(cd "$1"; pwd)" 6 | cd "$fs_tree" 7 | 8 | # clean up 9 | rm -rf proc/* sys/* dev/* tmp/* \ 10 | $(find run -type f) $(ls -1d var/cache/* | grep -v 'debconf$') var/lock 11 | 12 | # install debootstick init hook on getty command 13 | getty_command="$(realpath --relative-to . "$(readlink -f sbin/getty)")" 14 | mv "$getty_command" "${getty_command}.orig" 15 | ln -s /opt/debootstick/live/init/getty-hook.sh "$getty_command" 16 | echo "GETTY_COMMAND=$getty_command" >> dbstck.conf 17 | } 18 | -------------------------------------------------------------------------------- /scripts/create-image/common/formatting: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # partitioning and formatting 4 | # --------------------------- 5 | 6 | # Selection of ext4 features: 7 | # We must select only features available on the *oldest* system 8 | # version we want to support. We can get these features by 9 | # creating a sample filesystem on such a system: 10 | 11 | # $ cd /tmp 12 | # $ dd of=test.ext4 bs=1G count=0 seek=100 13 | # $ mkfs.ext4 -F -q -L ROOT -T big -m 2 test.ext4 14 | # $ dumpe2fs test.ext4 | grep features 15 | 16 | # Note about the '-T big' option: 17 | # We try to create a USB stick as small as possible. 18 | # However, the embedded system may later by copied on a 19 | # potentially large disk. 20 | # As a result, we should select appropriate ext4 features even if 21 | # the filesystem might be considered 'small' at first. 22 | # This may seem cosmetic but it's not: if initialized with 23 | # '-T small' (or with no -T option and run on a small disk), 24 | # when we move to a large disk, resize2fs apparently 25 | # enables the 'meta_bg' option (supposedly trying to adapt as much 26 | # as possible this 'small' filesystem to a much larger device). 27 | # Since this option is not handled by grub, it prevents the 28 | # system from booting properly. 29 | 30 | EXT4_FEATURES=$(cat << EOF 31 | has_journal ext_attr resize_inode dir_index filetype extent 32 | flex_bg sparse_super large_file huge_file uninit_bg dir_nlink 33 | extra_isize 34 | EOF 35 | ) 36 | 37 | format_fat() 38 | { 39 | label="$1" 40 | device="$2" 41 | label_option="" 42 | if [ ! -z "$label" ] 43 | then 44 | label_option="-n $label" 45 | fi 46 | quiet mkfs.vfat $label_option "$device" 47 | } 48 | 49 | format_ext4() 50 | { 51 | label="$1" 52 | device="$2" 53 | label_option="" 54 | if [ ! -z "$label" ] 55 | then 56 | label_option="-L $label" 57 | fi 58 | features="$(echo $EXT4_FEATURES | tr ' ' ',')" 59 | mkfs.ext4 -F -q $label_option -b 4096 -O "none,$features" -m 2 "$device" 60 | } 61 | 62 | get_loop_device_partitions() 63 | { 64 | loop_device="$1" 65 | partx -o NR -g "$loop_device" | while read num 66 | do 67 | echo "${loop_device}p$num" 68 | done 69 | } 70 | 71 | partition_image() 72 | { 73 | image_path="$1" 74 | layout_dir="$2" 75 | 76 | { 77 | part_table_type=$(cat "$layout_dir"/part_table_type) 78 | echo "label: $part_table_type" 79 | echo 80 | for vol in $(ls -1d "$layout_dir"/partitions/* | sort -n) 81 | do 82 | vol_id="$(basename $vol)" 83 | applied_size_mb=$(cat $vol/applied_size_mb) 84 | fdisk_type=$(cat $vol/fdisk_type) 85 | partdef="type=$fdisk_type" 86 | if [ $applied_size_mb -gt 0 ] 87 | then 88 | partdef="size=${applied_size_mb}MiB,$partdef" 89 | fi 90 | echo $partdef 91 | done 92 | } | sfdisk --no-tell-kernel "$image_path" >/dev/null 93 | } 94 | 95 | save_vol_uuid() 96 | { 97 | vol_dir="$1" 98 | blkid -s UUID -o value $vol_dir/device > $vol_dir/uuid 99 | } 100 | 101 | format_volume() 102 | { 103 | lvm_vg="$1" 104 | vol_dir="$2" 105 | vol_subtype=$(cat "$vol_dir/subtype") 106 | 107 | label="" 108 | if [ -f "$vol_dir/label" ] 109 | then 110 | label="$(cat "$vol_dir/label")" 111 | fi 112 | 113 | case $vol_subtype in 114 | efi|fat) 115 | format_fat "$label" $vol_dir/device 116 | echo vfat > $vol_dir/mounttype 117 | save_vol_uuid $vol_dir 118 | ;; 119 | ext4) 120 | format_ext4 "$label" $vol_dir/device 121 | echo ext4 > $vol_dir/mounttype 122 | save_vol_uuid $vol_dir 123 | ;; 124 | lvm) 125 | # this is our LVM PV partition 126 | quiet pvcreate $(readlink -f "$vol_dir/device") 127 | 128 | # we have to set the physical extent size to 1M otherwise 129 | # default value may be above (4M) and cause issues with 130 | # our volume size calculations 131 | quiet vgcreate -s 1M $lvm_vg $(readlink -f "$vol_dir/device") 132 | ;; 133 | esac 134 | } 135 | 136 | create_lvm_volume() 137 | { 138 | lvm_vg="$1" 139 | vol_dir="$2" 140 | 141 | label=$(cat "$vol_dir/label") 142 | applied_size_mb=$(cat "$vol_dir/applied_size_mb") 143 | 144 | if [ "$applied_size_mb" -eq "0" ] 145 | then 146 | quiet lvcreate -n "$label" -l 100%FREE "$lvm_vg" 147 | else 148 | quiet lvcreate -n "$label" -L ${applied_size_mb}M "$lvm_vg" 149 | fi 150 | ln -sf "/dev/$lvm_vg/$label" "$vol_dir/device" 151 | } 152 | 153 | format_volumes() 154 | { 155 | work_dir="$1" 156 | layout_dir="$2" 157 | lvm_vg="$3" 158 | device="$(readlink $work_dir/device)" 159 | 160 | # retrieve the partition devices 161 | partition_devices="$(get_loop_device_partitions $device)" 162 | 163 | # format partitions 164 | i=1 165 | for part_device in $(echo "$partition_devices") 166 | do 167 | wait_for_device $part_device 168 | part_vol="$layout_dir/partitions/$i" 169 | ln -sf "$part_device" "$part_vol/device" 170 | format_volume $lvm_vg $part_vol 171 | i=$((i+1)) 172 | done 173 | 174 | # create and format lvm volumes 175 | for lvm_vol_id in $(ls -1 "$layout_dir"/lvm_volumes/ | sort -n) 176 | do 177 | lvm_vol="$layout_dir"/lvm_volumes/$lvm_vol_id 178 | create_lvm_volume $lvm_vg $lvm_vol 179 | format_volume $lvm_vg $lvm_vol 180 | done 181 | } 182 | 183 | generate_mount_info() 184 | { 185 | layout_dir="$1" 186 | for vol_dir in $(ls -d "$layout_dir"/*/*) 187 | do 188 | echo "$(cat $vol_dir/mountpoint) $vol_dir" 189 | done | sort -k 1,1 | while read vol_mountpoint vol_dir 190 | do 191 | if [ "$vol_mountpoint" = 'none' ] 192 | then 193 | continue 194 | fi 195 | echo "$vol_dir" "$(cat $vol_dir/mounttype)" "$(cat $vol_dir/uuid)" "$vol_mountpoint" 196 | done 197 | } 198 | 199 | mount_volumes() 200 | { 201 | work_dir="$1" 202 | layout_dir="$2" 203 | fs_dir="$1/fs" 204 | 205 | generate_mount_info "$layout_dir" | while read vol_dir mounttype uuid vol_mountpoint 206 | do 207 | if [ "$vol_mountpoint" = "/" ] 208 | then 209 | fs_mountpoint="$fs_dir" 210 | else 211 | fs_mountpoint="$fs_dir$vol_mountpoint" 212 | fi 213 | mkdir -p "$fs_mountpoint" 214 | failsafe mount $vol_dir/device "$fs_mountpoint" 215 | echo "undo mount $vol_dir/device \"$fs_mountpoint\"" >> $work_dir/release_info 216 | # if this is the rootfs volume, save a pointer to it 217 | if [ "$vol_mountpoint" = "/" ] 218 | then 219 | ln -s $vol_dir $work_dir/rootfs_vol 220 | fi 221 | done 222 | } 223 | 224 | create_formatted_image() 225 | { 226 | image_name=$1 # 'draft' or 'final' 227 | sector_size=$2 228 | target_fs="$3" 229 | stick_os_id=$4 230 | image_file="$5" 231 | work_dir="$DBSTCK_TMPDIR/$image_name" 232 | layout_dir="$DBSTCK_TMPDIR/.layout" 233 | if [ -z "$image_file" ] 234 | then 235 | image_file="$work_dir/file" 236 | fi 237 | 238 | mkdir -p "$work_dir" 239 | 240 | # compute vg name for future use 241 | lvm_vg=$(get_vg_name $image_name $stick_os_id) 242 | echo $lvm_vg > "$work_dir/vg_name" 243 | 244 | # compute size of partitions and lvm volumes 245 | compute_applied_sizes $image_name $layout_dir "$target_fs" 246 | 247 | # create image file 248 | rm -f "$image_file" 249 | stick_size_mb=$(cat $layout_dir/needed_size_mb) 250 | $DD bs=$((1024*1024)) seek=$stick_size_mb count=0 of="$image_file" 251 | 252 | # create loop device 253 | image_device=$(losetup -f) 254 | failsafe_losetup --sector-size $sector_size $image_device "$image_file" 255 | echo undo losetup --sector-size $sector_size $image_device "$image_file" \ 256 | >> $work_dir/release_info 257 | 258 | # create partitions 259 | partition_image "$image_device" "$layout_dir" 260 | 261 | ln -sf "$image_device" "$work_dir/device" 262 | # let the kernel know about partitions of this device 263 | failsafe partx -a $image_device 264 | echo "undo partx -a $image_device" >> $work_dir/release_info 265 | 266 | # format partitions and lvm volumes, then mount them 267 | format_volumes "$work_dir" "$layout_dir" "$lvm_vg" 268 | mount_volumes "$work_dir" "$layout_dir" 269 | } 270 | 271 | release_image() 272 | { 273 | image_name="$1" # 'draft' or 'final' 274 | work_dir="$DBSTCK_TMPDIR/$image_name" 275 | 276 | eval "$(tac $work_dir/release_info)" 277 | rm $work_dir/file 278 | } 279 | 280 | -------------------------------------------------------------------------------- /scripts/create-image/common/layout: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | EMPTY_VOLUME_DATA_SIZE_MB=10 4 | MIN_GROWABLE_FAT_SIZE_MB=256 5 | LVM_PART_OVERHEAD_MB=4 # PV metadata size 6 | DISK_OVERHEAD_MB=1 # offset of 1st partition 7 | BIOSBOOT_PARTITION_SIZE_MB=1 8 | DRAFT_VOLUME_SIZE_ADDUP_MB=$((4*1024)) 9 | 10 | apply_overhead() { 11 | size_mb=$1 12 | vol_subtype=$2 13 | case "$vol_subtype" in 14 | ext4) 15 | # we use a block size of 4K (cf. file 'formatting'), so journal is at least 4M (cf. man mkfs.ext4) 16 | # this makes an important overhead on small filesystems. 17 | apply_overhead_percent $((size_mb+8)) 15 18 | ;; 19 | fat|efi) 20 | apply_overhead_percent $((size_mb+4)) 15 21 | ;; 22 | esac 23 | } 24 | 25 | get_fdisk_type() 26 | { 27 | part_table_type="$1" 28 | vol_subtype="$2" 29 | case "$part_table_type-$vol_subtype" in 30 | "dos-ext4") 31 | echo 83 32 | ;; 33 | "dos-fat") 34 | echo c 35 | ;; 36 | "dos-lvm") 37 | echo 8e 38 | ;; 39 | "gpt-ext4") 40 | echo 0FC63DAF-8483-4772-8E79-3D69D8477DE4 41 | ;; 42 | "gpt-fat") 43 | echo EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 44 | ;; 45 | "gpt-lvm") 46 | echo E6D6D379-F507-44C2-A23C-238F2A3DF928 47 | ;; 48 | "gpt-bios") 49 | echo 21686148-6449-6E6F-744E-656564454649 50 | ;; 51 | "gpt-efi") 52 | echo C12A7328-F81F-11D2-BA4B-00A0C93EC93B 53 | ;; 54 | *) 55 | layout_error "partition type '$vol_subtype' is not allowed with a '$part_table_type' partition table" 56 | ;; 57 | esac 58 | } 59 | 60 | layout_error() { 61 | echo >&2 62 | echo "E: invalid disk layout -- $1" >&2 63 | exit_wrong_user_input 64 | } 65 | 66 | save_volume_info() { 67 | vol_type="$1" 68 | vol_dir="$2" 69 | shift 2 70 | # check number of args 71 | [ "$4" != "" ] || layout_error "invalid syntax '$*'" 72 | shift 73 | mountpoint="$1" 74 | subtype="$2" 75 | size="$3" 76 | # check mountpoint 77 | [ "$mountpoint" = "none" -o "${mountpoint:0:1}" = "/" ] || \ 78 | layout_error "invalid mount point: '$mountpoint' (should start with '/')" 79 | # check subtype 80 | case $subtype in 81 | fat|ext4) # ok 82 | ;; 83 | efi|bios|lvm) 84 | [ "$vol_type" = "part" ] || \ 85 | layout_error "'$subtype' type is allowed for partitions, not LVM volumes" 86 | [ "$subtype" = "efi" -o "$mountpoint" = "none" ] || \ 87 | layout_error "'$subtype' partitions cannot be mounted, use 'none' as a mountpoint" 88 | ;; 89 | *) 90 | [ "$vol_type" = "lvm" ] && \ 91 | layout_error "unknown lvm volume type '$subtype' -- allowed types: fat, ext4" 92 | [ "$vol_type" = "part" ] && \ 93 | layout_error "unknown partition type '$subtype' -- allowed types: fat, ext4, efi, bios, lvm" 94 | ;; 95 | esac 96 | # check size 97 | case $size in 98 | max) # ok 99 | ;; 100 | auto) 101 | [ "$subtype" = "bios" -o "$mountpoint" != "none" ] || \ 102 | layout_error "cannot set size of '$subtype' partition to 'auto' unless you specify the mountpoint" 103 | ;; 104 | *[0-9][%GM]) 105 | check_integer ${size%?} # last char removed, must be an integer 106 | ;; 107 | *) 108 | layout_error "invalid size '$size'" 109 | ;; 110 | esac 111 | # ok save info 112 | mkdir -p $vol_dir 113 | # compute a volume label when relevant 114 | case $subtype in 115 | fat|ext4) 116 | if [ "$mountpoint" = "/" ] 117 | then 118 | echo "ROOT_FS" > $vol_dir/label 119 | elif [ "$mountpoint" != "none" ] 120 | then 121 | basename "$mountpoint" | tr a-z A-Z > $vol_dir/label 122 | elif [ "$vol_type" = "lvm" ] 123 | then 124 | # having a label is mandatory for LVM volumes 125 | echo "lvol$(basename $vol_dir)" > $vol_dir/label 126 | fi 127 | ;; 128 | efi) 129 | echo EFI > $vol_dir/label 130 | ;; 131 | esac 132 | echo "$vol_type" > $vol_dir/type 133 | echo "$mountpoint" > $vol_dir/mountpoint 134 | echo "$subtype" > $vol_dir/subtype 135 | echo "$size" > $vol_dir/size 136 | } 137 | 138 | get_vol_attr_files() { 139 | attr="$1" 140 | ls -1 | sort -n | sed -e "s/$/\/$attr/" 141 | } 142 | 143 | count_attr_matches() { 144 | cd "$1" 145 | files="$(get_vol_attr_files $2)" 146 | if [ "$files" = "" ] 147 | then 148 | echo 0 149 | else 150 | grep $3 $files | wc -l 151 | fi 152 | cd - >/dev/null 153 | } 154 | 155 | check_layout() { 156 | in_layout_dir=$1 157 | 158 | # check that at least one partition was defined 159 | [ -d "$in_layout_dir/partitions" ] || \ 160 | layout_error "no partitions defined" 161 | 162 | # check that, except for the last one, size of partitions is not "max" or "%" 163 | cd "$in_layout_dir/partitions" 164 | size_files="$(get_vol_attr_files size | head -n -1)" 165 | [ $(grep max $size_files | wc -l) -eq 0 ] || \ 166 | layout_error "'max' keyword is only allowed for the last partition" 167 | [ $(grep "%$" $size_files | wc -l) -eq 0 ] || \ 168 | layout_error "only the last partition can have its size declared as a percentage" 169 | cd - >/dev/null 170 | 171 | # check that no more than 1 lvm volume has size="max" 172 | if [ -d "$in_layout_dir/lvm_volumes" ] 173 | then 174 | [ $(count_attr_matches "$in_layout_dir/lvm_volumes" size max) -lt 2 ] || \ 175 | layout_error "'max' keyword is only allowed for at most 1 lvm volume" 176 | fi 177 | 178 | # check that at most one lvm, efi, bios partition is declared 179 | for special_subtype in lvm efi bios 180 | do 181 | [ $(count_attr_matches "$in_layout_dir/partitions" subtype $special_subtype) -lt 2 ] || \ 182 | layout_error "cannot have several partitions with type '$special_subtype'" 183 | done 184 | 185 | # check if an lvm partition exists 186 | lvm_partition_exists=$(count_attr_matches "$in_layout_dir/partitions" subtype lvm) 187 | 188 | # if lvm volumes are declared, verify that an lvm partition was declared 189 | if [ $(ls -1 "$in_layout_dir/lvm_volumes" | wc -l) -gt 0 ] 190 | then 191 | [ $lvm_partition_exists -eq 1 ] || \ 192 | layout_error "cannot declare lvm volumes unless you declare a partition with type lvm" 193 | fi 194 | 195 | # check that mount points are not repeated 196 | mountpoints="$(cat "$in_layout_dir"/*/*/mountpoint | grep -vx none)" 197 | [ $(echo "$mountpoints" | wc -w) -eq $(echo "$mountpoints" | uniq | wc -w) ] || \ 198 | layout_error "repeated mount point" 199 | 200 | # check that vg renaming can be handled 201 | if [ -f "$in_layout_dir"/final_vg_name ] 202 | then 203 | [ $lvm_partition_exists -eq 1 ] || \ 204 | layout_error "cannot record LVM volume group name since LVM is not used in this disk layout" 205 | if ! $target_get_vg_rename_command_exists 206 | then 207 | layout_error "this target does not support lvm vg renaming" 208 | fi 209 | fi 210 | } 211 | 212 | volume_is_partition() { 213 | parent_dir="$(basename "$(dirname "$1")")" 214 | [ "$parent_dir" = "partitions" ] && return 0 # yes 215 | return 1 # no 216 | } 217 | 218 | check_layout_updates() { 219 | in_def_layout_dir=$1 220 | in_new_layout_dir=$2 221 | 222 | # check that important data was not removed or changed from the default layout 223 | for defvol in $in_def_layout_dir/*/* 224 | do 225 | defvol_subtype=$(cat "$defvol/subtype") 226 | defvol_mountpoint=$(cat "$defvol/mountpoint") 227 | 228 | newvol_found=0 229 | for newvol in $in_new_layout_dir/*/* 230 | do 231 | newvol_subtype=$(cat "$newvol/subtype") 232 | newvol_mountpoint=$(cat "$newvol/mountpoint") 233 | if [ "$defvol_mountpoint" = "none" -a "$newvol_subtype" = "$defvol_subtype" ] 234 | then 235 | newvol_found=1 236 | break 237 | fi 238 | if [ "$defvol_mountpoint" != "none" -a "$newvol_mountpoint" = "$defvol_mountpoint" ] 239 | then 240 | [ "$newvol_subtype" = "$defvol_subtype" ] || \ 241 | layout_error "'$defvol_mountpoint' volume type cannot be changed from default disk layout ($defvol_subtype)" 242 | newvol_found=1 243 | break 244 | fi 245 | done 246 | 247 | if [ $newvol_found -eq 0 ] 248 | then 249 | [ "$defvol_mountpoint" = "none" ] && info_vol1="$defvol_subtype" || info_vol1="'$defvol_mountpoint'" 250 | volume_is_partition "$defvol" && info_vol2="partition" || info_vol2="lvm volume" 251 | layout_error "$info_vol1 $info_vol2 was removed from default disk layout" 252 | fi 253 | done 254 | 255 | [ $(cat "$in_def_layout_dir/part_table_type") = $(cat "$in_new_layout_dir/part_table_type") ] || \ 256 | layout_error "changing partition table type (gpt <-> dos) from default disk layout is not allowed" 257 | } 258 | 259 | save_lvm_vg_name() { 260 | in_layout_dir="$1" 261 | vg_name="$2" 262 | if [ ! -z "$3" -o -z "$vg_name" ] 263 | then 264 | layout_error "lvm vg name declaration should be 'lvm_vg_name ' or 'lvm_vg_name auto'" 265 | fi 266 | if [ "$vg_name" = "auto" ] 267 | then 268 | return # nothing to do, the auto-generated vg name will be preserved 269 | fi 270 | 271 | # if string delimiters were used, remove them 272 | vg_name="$(echo "$vg_name" | tr -d '"'"'")" 273 | if [ ! -z "$(echo "$vg_name" | tr -d '[a-gA-Z0-9_]')" ] 274 | then 275 | layout_error "lvm vg name accepts only the set of chars [a-zA-Z0-9_]" 276 | fi 277 | echo "$vg_name" > $in_layout_dir/final_vg_name 278 | } 279 | 280 | parse_layout() { 281 | in_layout_dir="$1" 282 | in_layout_file="$2" 283 | 284 | mkdir -p "$in_layout_dir/partitions" "$in_layout_dir/lvm_volumes" 285 | 286 | sed -e 's/#.*$//' $in_layout_file | while read inst args 287 | do 288 | case "$inst" in 289 | "") 290 | ;; 291 | "partition") 292 | part_num=$(($(num_dir_entries "$in_layout_dir/partitions")+1)) 293 | part_dir="$in_layout_dir/partitions/$part_num" 294 | save_volume_info part "$part_dir" "$inst" $args 295 | ;; 296 | "lvm_volume") 297 | vol_num=$(($(num_dir_entries "$in_layout_dir/lvm_volumes")+1)) 298 | vol_dir="$in_layout_dir/lvm_volumes/$vol_num" 299 | save_volume_info lvm "$vol_dir" "$inst" $args 300 | ;; 301 | "gpt"|"dos") 302 | [ ! -f "$in_layout_dir/part_table_type" ] || \ 303 | layout_error "several declarations of the partition table type (gpt|dos)" 304 | echo "$inst" > $in_layout_dir/part_table_type 305 | ;; 306 | "lvm_vg_name") 307 | save_lvm_vg_name "$in_layout_dir" $args 308 | ;; 309 | *) 310 | layout_error "invalid syntax '$inst'" 311 | esac 312 | done 313 | 314 | # check that partition table type was defined 315 | [ -f "$in_layout_dir/part_table_type" ] || \ 316 | layout_error "partition table type is not defined (gpt or dos)" 317 | 318 | # compute fdisk type of partitions 319 | part_table_type=$(cat "$in_layout_dir"/part_table_type) 320 | for vol in $(ls -1d "$in_layout_dir"/partitions/*) 321 | do 322 | vol_subtype=$(cat $vol/subtype) 323 | get_fdisk_type $part_table_type $vol_subtype > $vol/fdisk_type 324 | done 325 | } 326 | 327 | estimate_minimal_vol_size_mb() 328 | { 329 | in_vol="$1" 330 | in_target_fs="$2" 331 | 332 | vol_mountpoint=$(cat "$in_vol/mountpoint") 333 | vol_subtype=$(cat "$in_vol/subtype") 334 | target_fs_path="$in_target_fs/$vol_mountpoint" 335 | 336 | if [ -d "$target_fs_path" ] 337 | then 338 | data_size_mb=$(estimated_size_mb "$target_fs_path") 339 | else 340 | data_size_mb=$EMPTY_VOLUME_DATA_SIZE_MB 341 | fi 342 | 343 | min_size_mb=$(apply_overhead $data_size_mb $vol_subtype) 344 | 345 | # if volume is FAT and it should grow during init procedure 346 | # then ensure size is not too small. 347 | # otherwise it will be FAT-12 and fatresize will not be able 348 | # to handle it. 349 | if ! is_fixed_size_partition $in_vol 350 | then 351 | if [ "$vol_subtype" = "fat" -a \ 352 | "$min_size_mb" -lt "$MIN_GROWABLE_FAT_SIZE_MB" ] 353 | then 354 | min_size_mb=$MIN_GROWABLE_FAT_SIZE_MB 355 | fi 356 | fi 357 | echo "$min_size_mb" 358 | } 359 | 360 | should_extend_up_to_the_end() 361 | { 362 | vol=$1 363 | num_last_vol=$(ls -1 "$vol"/.. | sort -n | tail -n 1) 364 | [ $(basename $vol) = $num_last_vol ] 365 | } 366 | 367 | is_fixed_size_partition() 368 | { 369 | vol="$1" 370 | vol_type=$(cat "$vol/type") 371 | if [ "$vol_type" = "lvm" ] 372 | then 373 | return 1 # false 374 | fi 375 | vol_size=$(cat "$vol/size") 376 | case "$vol_size" in 377 | *[GM]) 378 | return 0 # true 379 | ;; 380 | *) 381 | return 1 # false 382 | ;; 383 | esac 384 | } 385 | 386 | store_computed_size() 387 | { 388 | mode="$1" 389 | vol="$2" 390 | 391 | read needed_size_mb 392 | 393 | # in the case of the 'draft' image, we should apply a big margin, 394 | # except for the partition with subtype "lvm". 395 | # (the size of this specific partition is obtained by summing the size 396 | # of lvm volumes, and all of them already include a margin.) 397 | if [ "$mode" = "draft" -a $(cat "$vol"/subtype) != "lvm" ] 398 | then 399 | needed_size_mb=$((needed_size_mb + DRAFT_VOLUME_SIZE_ADDUP_MB)) 400 | fi 401 | 402 | echo $needed_size_mb > $vol/needed_size_mb 403 | } 404 | 405 | compute_applied_sizes() 406 | { 407 | in_mode="$1" 408 | in_layout_dir="$2" 409 | in_target_fs="$3" 410 | 411 | # estimate data size of each volume with a mountpoint 412 | # 413 | # we have to take care not adding up size of sub-mounts 414 | # such as in the case of '/' and '/boot' for instance. 415 | # 416 | # precedure: 417 | # 1 - 1st loop: estimate whole tree size at each mountpoint 418 | # (ignoring existence of possible sub-mounts) 419 | # 2 - intermediary line (with 'sort | tac' processing): 420 | # sort to get deepest sub-mounts first 421 | # 3 - 2nd loop: remove from first datasize estimation 422 | # the datasize of sub-mounts. To ease this, we use 423 | # a temporary file hierarchy "$data_size_tree": 424 | # for each mountpoint, we record in parent dirs, up to "/", 425 | # the datasize we already counted at this step. 426 | data_size_tree=$(mktemp -d) 427 | for vol in $(ls -1d "$in_layout_dir"/*/* | sort -n) 428 | do 429 | vol_mountpoint=$(cat "$vol/mountpoint") 430 | if [ "$vol_mountpoint" != "none" ] 431 | then 432 | target_fs_path="$in_target_fs/$vol_mountpoint" 433 | if [ -d "$target_fs_path" ] 434 | then 435 | data_size_mb=$(estimated_size_mb "$target_fs_path") 436 | else 437 | data_size_mb=0 438 | fi 439 | echo "$vol_mountpoint" "$data_size_mb" "$vol" 440 | fi 441 | done | sort -k 1,1 | tac | while read vol_mountpoint data_size_mb vol 442 | do 443 | mp=$vol_mountpoint 444 | mkdir -p "$data_size_tree/$mp" 445 | if [ -f "$data_size_tree/$mp/.submounts_data_size_mb" ] 446 | then 447 | submounts_data_size_mb=$(cat $data_size_tree/$mp/.submounts_data_size_mb) 448 | data_size_mb=$((data_size_mb-submounts_data_size_mb)) 449 | fi 450 | echo $data_size_mb > $vol/data_size_mb 451 | while [ "$mp" != "/" ] 452 | do 453 | mp=$(dirname $mp) 454 | if [ -f "$data_size_tree/$mp/.submounts_data_size_mb" ] 455 | then 456 | submounts_data_size_mb=$(cat $data_size_tree/$mp/.submounts_data_size_mb) 457 | else 458 | submounts_data_size_mb=0 459 | fi 460 | echo $((data_size_mb+submounts_data_size_mb)) > \ 461 | "$data_size_tree/$mp/.submounts_data_size_mb" 462 | done 463 | done 464 | rm -rf $data_size_tree 465 | 466 | # compute the size we will apply to each volume. 467 | # 468 | # we sort volumes to list lvm volumes before partitions. 469 | # this allows to know the size of all lvm volumes when 470 | # we have to compute the size of the lvm-type partition. 471 | for vol in $(ls -1d "$in_layout_dir"/*/* | sort) 472 | do 473 | vol_size=$(cat "$vol/size") 474 | vol_type=$(cat "$vol/type") 475 | vol_subtype=$(cat "$vol/subtype") 476 | if [ "$vol_subtype" = "bios" ] 477 | then 478 | # bios boot partition is special, we know which size is recommended 479 | echo $BIOSBOOT_PARTITION_SIZE_MB 480 | elif [ "$vol_subtype" = "lvm" ] 481 | then 482 | # lvm-type partition => sum the size of all lvm volumes, add lvm metadata size 483 | { cat "$in_layout_dir"/lvm_volumes/*/needed_size_mb 484 | echo $LVM_PART_OVERHEAD_MB 485 | } | sum_lines 486 | else 487 | # variable size 488 | estimate_minimal_vol_size_mb $vol $in_target_fs 489 | fi | store_computed_size $in_mode $vol 490 | done 491 | 492 | # re-sort lvm volumes, smallest needed_size first 493 | # (otherwise approximation may cause the last volume to be short on disk space) 494 | mkdir "$in_layout_dir"/sorted_lvm_volumes/ 495 | for vol_id in $(ls -1 "$in_layout_dir"/lvm_volumes/) 496 | do 497 | vol="$in_layout_dir"/lvm_volumes/$vol_id 498 | echo "$(cat $vol/needed_size_mb) $vol" 499 | done | sort -k 1,1 | { 500 | i=1 501 | while read needed_size_mb vol 502 | do 503 | cp -r $vol "$in_layout_dir"/sorted_lvm_volumes/$i 504 | i=$((i+1)) 505 | done 506 | } 507 | rm -rf "$in_layout_dir"/lvm_volumes 508 | mv "$in_layout_dir"/sorted_lvm_volumes "$in_layout_dir"/lvm_volumes 509 | 510 | # compute applied_size_mb 511 | for vol in $(ls -1d "$in_layout_dir"/*/* | sort) 512 | do 513 | if should_extend_up_to_the_end $vol 514 | then 515 | echo 0 > $vol/applied_size_mb # 0 means 'fill available space' 516 | elif is_fixed_size_partition $vol 517 | then 518 | size_as_mb $(cat $vol/size) > $vol/applied_size_mb 519 | # if requested size if too low, set it to minimum needed size 520 | if [ $(cat $vol/applied_size_mb) -lt $(cat $vol/needed_size_mb) ] 521 | then 522 | cp $vol/needed_size_mb $vol/applied_size_mb 523 | fi 524 | else 525 | cp $vol/needed_size_mb $vol/applied_size_mb 526 | fi 527 | done 528 | 529 | # compute the overall image size 530 | { 531 | for vol in $(ls -1d "$in_layout_dir"/partitions/*) 532 | do 533 | if is_fixed_size_partition $vol 534 | then 535 | cat $vol/applied_size_mb 536 | else 537 | cat $vol/needed_size_mb 538 | fi 539 | done 540 | echo $DISK_OVERHEAD_MB 541 | } | sum_lines > "$in_layout_dir"/needed_size_mb 542 | } 543 | 544 | dump_partition_volumes_info() { 545 | in_layout_dir="$1" 546 | for vol in $(ls -1d "$in_layout_dir"/partitions/*) 547 | do 548 | echo "$(basename $vol);$(cat $vol/subtype);$(cat $vol/mountpoint);$(cat $vol/size)" 549 | done 550 | } 551 | 552 | dump_lvm_volumes_info() { 553 | in_layout_dir="$1" 554 | for vol_id in $(ls -1 "$in_layout_dir"/lvm_volumes/) 555 | do 556 | vol="$in_layout_dir"/lvm_volumes/$vol_id 557 | echo "$(cat $vol/label);$(cat $vol/subtype);$(cat $vol/mountpoint);$(cat $vol/size)" 558 | done 559 | } 560 | 561 | check_need_fatresize() { 562 | in_layout_dir="$1" 563 | # last partition may grow 564 | growable_volumes=$(ls -1d "$in_layout_dir"/partitions/* | tail -n 1) 565 | # all lvm volumes may grow too 566 | if [ "$(ls -1 "$in_layout_dir"/lvm_volumes | wc -l)" -gt 0 ] 567 | then 568 | growable_volumes="$growable_volumes $(ls -1d "$in_layout_dir"/lvm_volumes/*)" 569 | fi 570 | # check if one of them uses a fat filesystem 571 | for vol in $growable_volumes 572 | do 573 | if [ $(cat $vol/subtype) = "fat" ] 574 | then 575 | echo 1 576 | return 577 | fi 578 | done 579 | echo 0 580 | } 581 | -------------------------------------------------------------------------------- /scripts/create-image/common/lvm: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # we want to allow LVM to work with devices of different 4 | # sector sizes. 5 | tune_lvm() 6 | { 7 | if [ -f /etc/lvm/lvm.conf ] 8 | then 9 | if grep -q allow_mixed_block_sizes /etc/lvm/lvm.conf 10 | then 11 | sed -i -e 's/.*\(allow_mixed_block_sizes.*=\).*/\1 1/g' \ 12 | /etc/lvm/lvm.conf 13 | fi 14 | fi 15 | } 16 | -------------------------------------------------------------------------------- /scripts/create-image/common/mount: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | busybox_mount() 4 | { 5 | $busybox_path mount $* 6 | } 7 | 8 | # note: when running 'umount ', umount tries to 9 | # find which mount this is about, reading /etc/mtab 10 | # if available, or /proc/mounts. 11 | # Also, some versions of busybox require /proc/self/exe 12 | # to be available otherwise its internal applets, 13 | # including 'mount', will not be found (unless you 14 | # specify "busybox mount" instead of just "mount"). 15 | # As a result, /proc should be mounted first (and 16 | # unmounted last, but this is a side effect of 17 | # the failsafe mode handling anyway). 18 | # The caller should mount /proc as needed, and then 19 | # call the function below. 20 | failsafe_mount_sys_and_dev() 21 | { 22 | failsafe mount -t devtmpfs none /dev 23 | failsafe mount -t devpts none /dev/pts 24 | failsafe mount -t sysfs none /sys 25 | } 26 | -------------------------------------------------------------------------------- /scripts/create-image/common/target-helpers: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # this must be called by bash once, first 4 | # (sh has no 'declare' command) 5 | has_function() { 6 | func_name="$1" 7 | 8 | # check if we have the answer already 9 | precomputed=$(eval "echo \$${func_name}_exists") 10 | 11 | if [ -z "$precomputed" ] 12 | then 13 | if declare -f $func_name >/dev/null 14 | then 15 | echo true 16 | else 17 | echo false 18 | fi 19 | else 20 | echo $precomputed 21 | fi 22 | } 23 | 24 | setup_optional_function() { 25 | func_name="$1" 26 | if $(has_function $func_name) 27 | then 28 | eval "${func_name}_exists=true" 29 | eval "optional_$func_name() { $func_name \$@; }" 30 | else 31 | eval "${func_name}_exists=false" 32 | eval "optional_$func_name() { true; }" 33 | fi 34 | export "${func_name}_exists" 35 | } 36 | 37 | TARGET_OPTIONAL_FUNCTIONS="$(cat << EOF 38 | target_preliminary_steps 39 | target_final_customization_steps 40 | target_prepare_rootfs 41 | target_cleanup_rootfs 42 | target_custom_packages 43 | target_get_bootloader_install_command 44 | target_get_vg_rename_command 45 | EOF 46 | )" 47 | 48 | probe_target_optional_functions() { 49 | for func in $TARGET_OPTIONAL_FUNCTIONS 50 | do 51 | setup_optional_function $func 52 | done 53 | } 54 | -------------------------------------------------------------------------------- /scripts/create-image/os-detect: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # check that the given directory looks like an OS 4 | # file structure that we can handle correctly. 5 | check_fs_hierarchy() 6 | { 7 | fs_tree="$1" 8 | level="$2" 9 | case "$level" in 10 | "1") 11 | if [ ! -f "$fs_tree/bin/echo" ] 12 | then 13 | echo "E: No /bin/echo found in $fs_tree." 14 | echo "E: This does not seem to be a chroot environment." 15 | return 1 16 | fi 17 | 18 | if [ ! -f "$fs_tree/etc/os-release" ] 19 | then 20 | echo "E: No /etc/os-release file found in $fs_tree." 21 | echo "E: Cannot check compatibility. Aborting." 22 | return 1 23 | fi 24 | 25 | . "$fs_tree/etc/os-release" 26 | if [ "$ID" != "debian" -a "$ID_LIKE" != "debian" ] 27 | then 28 | echo "E: /etc/os-release file in $fs_tree does not report a debian-like OS." 29 | echo "E: debootstick currently cannot handle such a chroot environment." >&2 30 | echo "E: Run 'debootstick --help-os-support' for more info." 31 | return 1 32 | fi 33 | ;; 34 | "2") 35 | chroot "$fs_tree" echo -n >/dev/null 2>&1 || { 36 | echo "E: Unable to execute binaries (/bin/echo at least) in the chroot environment." 37 | echo "E: Please verify:" 38 | echo "E: - file permissions in the chroot environment" 39 | echo "E: - that your host CPU is able to run binaries of the target architecture" 40 | echo "E: Run 'debootstick --help-os-support' for info about compatible environments." 41 | return 1 42 | } 43 | chroot "$fs_tree" which apt-get >/dev/null 2>&1 || { 44 | echo "E: No apt-get found in $1." 45 | echo "E: debootstick cannot handle this kind of chroot environment." 46 | echo "E: Run 'debootstick --help-os-support' for more info." 47 | return 1 48 | } 49 | ;; 50 | esac 51 | } 52 | 53 | detect_target_type() 54 | { 55 | fs_tree="$1" 56 | dir="$DBSTCK_DIR/scripts/create-image/target" 57 | for subdir in $(ls "$dir") 58 | do 59 | "$dir/$subdir/detect.sh" "$fs_tree" >/dev/null && { 60 | echo "$subdir" 61 | return 62 | } 63 | done 64 | echo "E: debootstick does not know how to handle your chroot environment." >&2 65 | echo "E: Run 'debootstick --help-os-support' for more info." >&2 66 | } 67 | -------------------------------------------------------------------------------- /scripts/create-image/target/pc/cpu: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | get_target_cpu() { 4 | fs_tree="$1" 5 | if ls "$fs_tree/etc/ld.so.conf.d/i"*"86-linux-gnu.conf" >/dev/null 2>&1 6 | then 7 | echo "i386" 8 | fi 9 | if ls "$fs_tree/etc/ld.so.conf.d/x86_64-linux-gnu.conf" >/dev/null 2>&1 10 | then 11 | echo "amd64" 12 | fi 13 | } 14 | -------------------------------------------------------------------------------- /scripts/create-image/target/pc/detect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | fs_tree=$1 3 | 4 | # the files we check here are part of the libc package, 5 | # so one of them should be here, depending on CPU architecture. 6 | 7 | if ls "$fs_tree/etc/ld.so.conf.d/i"*"86-linux-gnu.conf" >/dev/null 2>&1 8 | then 9 | exit 0 # OK 10 | fi 11 | 12 | if ls "$fs_tree/etc/ld.so.conf.d/x86_64-linux-gnu.conf" >/dev/null 2>&1 13 | then 14 | exit 0 # OK 15 | fi 16 | 17 | exit 1 # not such a system 18 | -------------------------------------------------------------------------------- /scripts/create-image/target/pc/entrypoints: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | target_get_name() { 4 | echo "$(get_target_cpu "$ORIG_TREE") PC" 5 | } 6 | 7 | target_use_lvm() { 8 | echo true 9 | } 10 | 11 | target_get_vg_rename_command() { 12 | echo grub_vg_rename 13 | } 14 | 15 | # for details about the following functions 16 | # see 'packages' file 17 | 18 | target_kernel_default_package() { 19 | kernel_default_package 20 | } 21 | 22 | target_custom_packages() { 23 | custom_packages 24 | } 25 | 26 | # for details about the following functions 27 | # see 'grub' file 28 | 29 | target_configure_bootloader() { 30 | configure_bootloader 31 | } 32 | 33 | target_install_bootloader() { 34 | install_bootloader 35 | } 36 | 37 | target_get_bootloader_install_command() { 38 | echo grub-install 39 | } 40 | 41 | # for details about the following functions 42 | # see 'rootfs' file 43 | 44 | target_prepare_rootfs() { 45 | prepare_rootfs $@ 46 | } 47 | 48 | target_cleanup_rootfs() { 49 | cleanup_rootfs $@ 50 | } 51 | 52 | -------------------------------------------------------------------------------- /scripts/create-image/target/pc/grub: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # grub configuration 4 | # ------------------ 5 | 6 | # let grub find our virtual device 7 | update_grup_device_map() 8 | { 9 | cd / 10 | mkdir -p boot/grub 11 | cat > boot/grub/device.map << END_MAP 12 | (hd0) $loop_device 13 | END_MAP 14 | cd - >/dev/null 15 | } 16 | 17 | # * customize boot parameters 18 | # * fix obsolete options in /etc/default/grub 19 | # (https://bugs.launchpad.net/ubuntu/+source/grub2/+bug/1258597) 20 | update_grub_conf() 21 | { 22 | . /etc/default/grub 23 | existing_bootargs="$GRUB_CMDLINE_LINUX" 24 | recommended_bootargs="rootdelay=3" 25 | user_bootargs="$@" 26 | 27 | # order of precedence is: 28 | # user_bootargs > recommended_bootargs > existing_bootargs 29 | 30 | # In the case of grub, we add bootargs to grub's GRUB_CMDLINE_LINUX variable. 31 | # However, when deleting a bootarg, it may actually be in GRUB_CMDLINE_LINUX_DEFAULT too. 32 | 33 | GRUB_CMDLINE_LINUX="$(aggregate_kernel_cmdline $existing_bootargs $recommended_bootargs $user_bootargs)" 34 | 35 | only_minus_modifiers="$(echo "$user_bootargs" | tr ' ' '\n' | happy_grep '^-')" 36 | GRUB_CMDLINE_LINUX_DEFAULT="$(aggregate_kernel_cmdline $GRUB_CMDLINE_LINUX_DEFAULT $only_minus_modifiers)" 37 | 38 | sed -i -e "s/GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"$GRUB_CMDLINE_LINUX\"/" \ 39 | -e "s/GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"$GRUB_CMDLINE_LINUX_DEFAULT\"/" \ 40 | -e "s/^GRUB_HIDDEN/#GRUB_HIDDEN/g" \ 41 | /etc/default/grub 42 | 43 | # unless already specified, set GRUB_DISABLE_OS_PROBER to true 44 | # otherwise update-grub may detect and install boot entries of the host machine 45 | # while debootstick is building the draft image. 46 | if [ "$GRUB_DISABLE_OS_PROBER" = "" ] 47 | then 48 | echo >> /etc/default/grub 49 | echo "GRUB_DISABLE_OS_PROBER=true" >> /etc/default/grub 50 | fi 51 | 52 | # let the user know which bootargs where finally selected 53 | applied_kernel_cmdline=$(aggregate_kernel_cmdline $GRUB_CMDLINE_LINUX $GRUB_CMDLINE_LINUX_DEFAULT) 54 | } 55 | 56 | # display the grub interface on serial line 57 | update_grub_conf_serial_line() 58 | { 59 | cat >> ./etc/default/grub << EOF 60 | GRUB_TERMINAL=serial 61 | GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1" 62 | EOF 63 | } 64 | 65 | get_grub_arch() { 66 | case "$(get_target_cpu "/")" in 67 | "amd64") 68 | echo "x86_64" 69 | ;; 70 | "i386") 71 | echo "i386" 72 | ;; 73 | esac 74 | } 75 | 76 | grub_install_uefi() 77 | { 78 | arch=$(get_grub_arch) 79 | options="--target=${arch}-efi --uefi-secure-boot --no-nvram" 80 | if grub-install --help | grep -q force-extra-removable 81 | then 82 | options="$options --force-extra-removable" 83 | fi 84 | grub-install $options 85 | } 86 | 87 | grub_install_bios() 88 | { 89 | grub-install "$1" 90 | } 91 | 92 | smart_update_initramfs() 93 | { 94 | # initramfs files may be big, so when regenerating a new version 95 | # of the file with "update-initramfs -u", there may be an error 96 | # because of missing space. 97 | # here we remove the previous version of the file before generating 98 | # the new version. 99 | 100 | initramfs_file=$(ls /boot | grep 'initrd.img-') 101 | kernel_version=$(echo "$initramfs_file" | sed -e 's/initrd.img-//') 102 | rm -f "/boot/$initramfs_file" 103 | update-initramfs -c -k "$kernel_version" 104 | } 105 | 106 | quiet_grub_install() 107 | { 108 | device=$1 109 | 110 | update_grup_device_map $device 111 | 112 | # grub-install & update-grub print messages to standard 113 | # error stream although most of these are just 114 | # informational (or minor bugs). Let's discard them. 115 | output="$( 116 | grub_install_uefi 2>&1 && \ 117 | grub_install_bios $device 2>&1 && \ 118 | smart_update_initramfs 2>&1 && \ 119 | update-grub 2>&1 120 | )" || return_code=$? 121 | 122 | echo "$output" | happy_grep -v "No error" | \ 123 | happy_grep -v "Installing" | \ 124 | happy_grep -v "Generating" | \ 125 | happy_grep -v "Found .* image:" | \ 126 | happy_grep -v "lvmetad" | \ 127 | happy_grep -v "etc.modprobe.d" | \ 128 | happy_grep -v "leaked on" | \ 129 | happy_grep -v "^Sourcing file" | \ 130 | happy_grep -v "^Warning: os-prober" | \ 131 | happy_grep -v "^Systems on them" | \ 132 | happy_grep -v "^Check GRUB_DISABLE" | \ 133 | happy_grep -v "using gzip" | \ 134 | happy_grep -v "^done$" 1>&2 135 | 136 | 137 | # the return value we want is the one we caught 138 | # earlier (or none if all went well): 139 | return $return_code 140 | } 141 | 142 | configure_bootloader() 143 | { 144 | # tune grub conf 145 | update_grub_conf $kernel_bootargs 146 | if [ "$config_grub_on_serial_line" -gt 0 ] 147 | then 148 | update_grub_conf_serial_line 149 | fi 150 | 151 | # disable quickboot: 152 | # work around grub displaying error message with our LVM setup 153 | # disable vt_handoff: 154 | # the linux console should be visible during startup (especially 155 | # if we must enter the root password, or in installer-mode), do 156 | # not switch to vt7. 157 | # note: even if the file etc/grub.d/10_linux is re-created 158 | # after an upgrade of the package grub-common, our script 159 | # 09_linux_custom will be executed first and take precedence. 160 | sed -i -e 's/quick_boot=.*/quick_boot=0/' \ 161 | -e 's/vt_handoff=.*/vt_handoff=0/' etc/grub.d/10_linux 162 | mv etc/grub.d/10_linux etc/grub.d/09_linux_custom 163 | } 164 | 165 | install_bootloader() 166 | { 167 | quiet_grub_install $loop_device 168 | } 169 | 170 | -------------------------------------------------------------------------------- /scripts/create-image/target/pc/packages: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | get_debian_variant() 4 | { 5 | if [ ! -f "/etc/os-release" ] 6 | then 7 | echo 'unknown' 8 | return 9 | fi 10 | variant="$(grep -o "^ID=.*" /etc/os-release | sed -e 's/ID=//')" 11 | if [ "$variant" = "" ] 12 | then 13 | echo 'unknown' 14 | return 15 | fi 16 | echo "$variant" 17 | } 18 | 19 | custom_packages() 20 | { 21 | echo -n "grub-pc shim-signed initramfs-tools " 22 | # grub-install uses lsb-release to identify the debian variant 23 | # and place the boot files in appropriate [EFI-PART]/EFI/. 24 | # on Ubuntu, if lsb-release is missing, value will default 25 | # to 'debian' instead of 'ubuntu' and prevent the UEFI bootup to work. 26 | # lsb-release has a significant impact on resulting image size because 27 | # it depends on python3. 28 | # in the case of debian, the default value selected for is 29 | # fine, so we can avoid it. for Ubuntu, we have to include it. 30 | if [ "$(get_debian_variant)" != "debian" ] 31 | then 32 | echo "lsb-release " 33 | fi 34 | case "$(get_target_cpu /)" in 35 | "amd64") 36 | echo "grub-efi-amd64-signed" 37 | ;; 38 | "i386") 39 | echo "grub-efi-ia32-signed" 40 | ;; 41 | esac 42 | } 43 | 44 | kernel_default_package() 45 | { 46 | # * ubuntu: linux-image-generic 47 | # * debian on i386: linux-image-686-pae 48 | # * debian on amd64: linux-image-amd64 49 | case "$(get_target_cpu /)" in 50 | "amd64") 51 | echo "linux-image-((generic)|(amd64))" 52 | ;; 53 | "i386") 54 | echo "linux-image-((generic)|(686-pae))" 55 | ;; 56 | esac 57 | } 58 | -------------------------------------------------------------------------------- /scripts/create-image/target/pc/rootfs: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | prepare_rootfs() 4 | { 5 | if [ "$2" = "inside" ] 6 | then 7 | # if update-grub is called as part of the package installation 8 | # it should properly find our virtual device. 9 | # (we will properly install the bootloader on the final device 10 | # anyway, this is only useful to avoid warnings) 11 | update_grup_device_map 12 | fi 13 | } 14 | 15 | cleanup_rootfs() 16 | { 17 | if [ "$2" = "inside" ] 18 | then 19 | rm boot/grub/device.map 20 | fi 21 | } 22 | -------------------------------------------------------------------------------- /scripts/create-image/target/rpi/bootloader: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # bootloader management 4 | # --------------------- 5 | 6 | # configure the bootloader appropriately, given the variable $kernel_bootargs 7 | # and any other target-specific requirements. 8 | 9 | configure_bootloader() 10 | { 11 | # get existing conf in chroot environment if any 12 | if [ -f /boot/cmdline.txt ] 13 | then 14 | existing_bootargs=$(cat /boot/cmdline.txt) 15 | fi 16 | 17 | # our mandatory options 18 | mandatory_bootargs="root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" 19 | 20 | # and the user may specify more bootarg customization by giving 21 | # them on the command line 22 | explicit_bootargs=$kernel_bootargs 23 | 24 | # order of precedence is: 25 | # explicit_bootargs > mandatory_bootargs > existing_bootargs 26 | applied_kernel_cmdline="$(aggregate_kernel_cmdline $existing_bootargs \ 27 | $mandatory_bootargs $explicit_bootargs)" 28 | 29 | echo $applied_kernel_cmdline > /boot/cmdline.txt 30 | } 31 | 32 | # install the bootloader on $loop_device 33 | 34 | install_bootloader() 35 | { 36 | # nothing to do here, package raspberrypi-bootloader should have installed the 37 | # appropriate files in /boot. 38 | : # colon is no-op in shell 39 | } 40 | -------------------------------------------------------------------------------- /scripts/create-image/target/rpi/detect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | fs_tree=$1 3 | 4 | . "$fs_tree/etc/os-release" 5 | if [ "$ID" = "raspbian" ] 6 | then 7 | exit 0 # OK 8 | fi 9 | 10 | exit 1 # not such a system 11 | -------------------------------------------------------------------------------- /scripts/create-image/target/rpi/emulation: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | ARM_BINFMT_DEF=/proc/sys/fs/binfmt_misc/qemu-arm 4 | ERR_CPU_EMULATION="E: Cannot continue because host OS does not provide ARM cpu emulation." 5 | 6 | has_fix_binary_flag() 7 | { 8 | cat $1 | grep "flags" | grep 'F' >/dev/null 9 | } 10 | 11 | prepare_rootfs_for_emulation() 12 | { 13 | if [ ! -f $ARM_BINFMT_DEF ] 14 | then 15 | echo "$ERR_CPU_EMULATION" >&2 16 | return 1 17 | fi 18 | 19 | if has_fix_binary_flag "$ARM_BINFMT_DEF" 20 | then 21 | # ok the binary of the emulator was loaded once for all when the 22 | # binfmt_misc entry was registered, so emulation will be available 23 | # in target too 24 | # see https://www.kernel.org/doc/Documentation/admin-guide/binfmt-misc.rst 25 | # the section about flag "F". 26 | return 27 | fi 28 | 29 | set -- $(cat $ARM_BINFMT_DEF | grep interpreter) 30 | interpreter_path="$(readlink -f $2)" 31 | if [ ! -f "./$interpreter_path" ] 32 | then 33 | # emulator binary is missing in target 34 | # verify we have it on the host 35 | if [ ! -f "$interpreter_path" ] 36 | then 37 | echo "$ERR_CPU_EMULATION" >&2 38 | return 1 39 | fi 40 | 41 | # ok, copy the one from the host 42 | cp "$interpreter_path" "./$interpreter_path" 43 | 44 | # keep track of this 45 | echo "$interpreter_path" > .emulation.added 46 | fi 47 | } 48 | 49 | cleanup_rootfs_for_emulation() 50 | { 51 | if [ -f .emulation.added ] 52 | then 53 | rm "./$(cat .emulation.added)" 54 | rm .emulation.added 55 | fi 56 | } 57 | -------------------------------------------------------------------------------- /scripts/create-image/target/rpi/entrypoints: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | target_get_name() { 4 | echo "Raspberry Pi board" 5 | } 6 | 7 | # for details about the following functions 8 | # see 'steps' file 9 | 10 | target_use_lvm() { 11 | echo false 12 | } 13 | 14 | target_preliminary_steps() { 15 | echo "I: Some operations will require CPU EMULATION - Please be patient!" 16 | } 17 | 18 | # for details about the following functions 19 | # see 'packages' file 20 | 21 | target_kernel_default_package() { 22 | kernel_default_package 23 | } 24 | 25 | target_custom_packages() { 26 | custom_packages 27 | } 28 | 29 | # for details about the following functions 30 | # see 'rootfs' file 31 | 32 | target_prepare_rootfs() { 33 | prepare_rootfs $@ 34 | } 35 | 36 | target_cleanup_rootfs() { 37 | cleanup_rootfs $@ 38 | } 39 | 40 | # for details about the following functions 41 | # see 'bootloader' file 42 | 43 | target_configure_bootloader() { 44 | configure_bootloader 45 | } 46 | 47 | target_install_bootloader() { 48 | install_bootloader 49 | } 50 | -------------------------------------------------------------------------------- /scripts/create-image/target/rpi/packages: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | RPI_REPO_PUBKEY="\ 4 | -----BEGIN PGP PUBLIC KEY BLOCK----- 5 | 6 | mQENBE/d7o8BCACrwqQacGJfn3tnMzGui6mv2lLxYbsOuy/+U4rqMmGEuo3h9m92 7 | 30E2EtypsoWczkBretzLUCFv+VUOxaA6sV9+puTqYGhhQZFuKUWcG7orf7QbZRuu 8 | TxsEUepW5lg7MExmAu1JJzqM0kMQX8fVyWVDkjchZ/is4q3BPOUCJbUJOsE+kK/6 9 | 8kW6nWdhwSAjfDh06bA5wvoXNjYoDdnSZyVdcYCPEJXEg5jfF/+nmiFKMZBraHwn 10 | eQsepr7rBXxNcEvDlSOPal11fg90KXpy7Umre1UcAZYJdQeWcHu7X5uoJx/MG5J8 11 | ic6CwYmDaShIFa92f8qmFcna05+lppk76fsnABEBAAG0IFJhc3BiZXJyeSBQaSBB 12 | cmNoaXZlIFNpZ25pbmcgS2V5iQE4BBMBAgAiBQJP3e6PAhsDBgsJCAcDAgYVCAIJ 13 | CgsEFgIDAQIeAQIXgAAKCRCCsSmSf6MwPk6vB/9pePB3IukU9WC9Bammh3mpQTvL 14 | OifbkzHkmAYxzjfK6D2I8pT0xMxy949+ThzJ7uL60p6T/32ED9DR3LHIMXZvKtuc 15 | mQnSiNDX03E2p7lIP/htoxW2hDP2n8cdlNdt0M9IjaWBppsbO7IrDppG2B1aRLni 16 | uD7v8bHRL2mKTtIDLX42Enl8aLAkJYgNWpZyPkDyOqamjijarIWjGEPCkaURF7g4 17 | d44HvYhpbLMOrz1m6N5Bzoa5+nq3lmifeiWKxioFXU+Hy5bhtAM6ljVb59hbD2ra 18 | X4+3LXC9oox2flmQnyqwoyfZqVgSQa0B41qEQo8t1bz6Q1Ti7fbMLThmbRHiuQEN 19 | BE/d7o8BCADNlVtBZU63fm79SjHh5AEKFs0C3kwa0mOhp9oas/haDggmhiXdzeD3 20 | 49JWz9ZTx+vlTq0s+I+nIR1a+q+GL+hxYt4HhxoA6vlDMegVfvZKzqTX9Nr2VqQa 21 | S4Kz3W5ULv81tw3WowK6i0L7pqDmvDqgm73mMbbxfHD0SyTt8+fk7qX6Ag2pZ4a9 22 | ZdJGxvASkh0McGpbYJhk1WYD+eh4fqH3IaeJi6xtNoRdc5YXuzILnp+KaJyPE5CR 23 | qUY5JibOD3qR7zDjP0ueP93jLqmoKltCdN5+yYEExtSwz5lXniiYOJp8LWFCgv5h 24 | m8aYXkcJS1xVV9Ltno23YvX5edw9QY4hABEBAAGJAR8EGAECAAkFAk/d7o8CGwwA 25 | CgkQgrEpkn+jMD5Figf/dIC1qtDMTbu5IsI5uZPX63xydaExQNYf98cq5H2fWF6O 26 | yVR7ERzA2w33hI0yZQrqO6pU9SRnHRxCFvGv6y+mXXXMRcmjZG7GiD6tQWeN/3wb 27 | EbAn5cg6CJ/Lk/BI4iRRfBX07LbYULCohlGkwBOkRo10T+Ld4vCCnBftCh5x2OtZ 28 | TOWRULxP36y2PLGVNF+q9pho98qx+RIxvpofQM/842ZycjPJvzgVQsW4LT91KYAE 29 | 4TVf6JjwUM6HZDoiNcX6d7zOhNfQihXTsniZZ6rky287htsWVDNkqOi5T3oTxWUo 30 | m++/7s3K3L0zWopdhMVcgg6Nt9gcjzqN1c0gy55L/g== 31 | =mNSj 32 | -----END PGP PUBLIC KEY BLOCK----- 33 | " 34 | 35 | custom_packages() 36 | { 37 | echo raspberrypi-bootloader 38 | } 39 | 40 | kernel_default_package() 41 | { 42 | echo raspberrypi-kernel 43 | } 44 | 45 | update_apt_conf() 46 | { 47 | # ensure apt has the pubkey for archive.raspberrypi.org repo 48 | echo "$RPI_REPO_PUBKEY" | apt-key --keyring etc/apt/trusted.gpg add - >/dev/null 2>&1 49 | 50 | # analyse sources.list 51 | set -- $(cat etc/apt/sources.list | grep -v "^#" | grep -w main | head -n 1) 52 | version_name=$3 53 | mirror=$2 54 | shift 3 55 | components=$* 56 | 57 | mirror_host=$(echo "$mirror" | tr "/" " " | awk '{print $2}') 58 | if [ "$mirror_host" = "httpredir.debian.org" ] 59 | then 60 | # see https://bugs.launchpad.net/ubuntu/+source/qemu/+bug/1670905 61 | cat > etc/apt/sources.list << EOF 62 | deb http://mirrordirector.raspbian.org/raspbian $version_name $components 63 | EOF 64 | echo "I: draft image - fixed wrong sources.list (bogus debootstrap foreign chroot)." 65 | fi 66 | 67 | # check that we have the conf for archive.raspberrypi.org repo 68 | if [ ! -f "etc/apt/sources.list.d/raspi.list" ] 69 | then 70 | # no. let's create it. 71 | cat > etc/apt/sources.list.d/raspi.list << EOF 72 | deb http://archive.raspberrypi.org/debian/ $version_name main ui 73 | # Uncomment line below then 'apt-get update' to enable 'apt-get source' 74 | #deb-src http://archive.raspberrypi.org/debian/ $version_name main ui 75 | EOF 76 | fi 77 | } 78 | -------------------------------------------------------------------------------- /scripts/create-image/target/rpi/rootfs: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | prepare_rootfs() 4 | { 5 | if [ "$1" = "draft" -a "$2" = "outside" ] 6 | then 7 | update_apt_conf 8 | prepare_rootfs_for_emulation 9 | fi 10 | } 11 | 12 | cleanup_rootfs() 13 | { 14 | if [ "$1" = "final" -a "$2" = "outside" ] 15 | then 16 | cleanup_rootfs_for_emulation 17 | fi 18 | } 19 | -------------------------------------------------------------------------------- /scripts/create-image/target/skeleton/bootloader: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # bootloader management 4 | # --------------------- 5 | 6 | # configure the bootloader appropriately, given the variable $kernel_bootargs 7 | # and any other target-specific requirements. 8 | 9 | configure_bootloader() 10 | { 11 | # get existing conf in chroot environment if any... 12 | # sample code: 13 | # if [ -f /boot/cmdline.txt ] 14 | # then 15 | # existing_bootargs=$(cat /boot/cmdline.txt) 16 | # fi 17 | 18 | # our mandatory options 19 | mandatory_bootargs="root= rootfstype=ext4 rootwait" 20 | 21 | # The user may specify more bootarg customization by giving 22 | # them on the command line (option --config-kernel-bootargs). 23 | # Specifying "[=]" or "+[=]" 24 | # allows to add or overwrite any previous definition of . 25 | # Specifying "-" allows to remove any definition of 26 | # from the kernel cmdline. 27 | explicit_bootargs=$kernel_bootargs 28 | 29 | # order of precedence is: 30 | # explicit_bootargs > mandatory_bootargs > existing_bootargs 31 | applied_kernel_cmdline="$(aggregate_kernel_cmdline $existing_bootargs \ 32 | $mandatory_bootargs $explicit_bootargs)" 33 | 34 | # write computed $applied_kernel_cmdline in appropriate configuration file... 35 | } 36 | 37 | # install the bootloader on $loop_device 38 | 39 | install_bootloader() 40 | { 41 | my_bootloader_install $loop_device 42 | } 43 | -------------------------------------------------------------------------------- /scripts/create-image/target/skeleton/detect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | fs_tree=$1 3 | 4 | # check if $fs_tree match your target type. 5 | # Call 'exit 0' in this case. 6 | # See other target dirs for ideas. 7 | 8 | exit 1 # not such a system 9 | -------------------------------------------------------------------------------- /scripts/create-image/target/skeleton/entrypoints: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # Note: commented functions are optional. 4 | 5 | target_get_name() { 6 | echo "Skeleton target!" 7 | } 8 | 9 | # system-type=installer option support requires: 10 | # 1- target_use_lvm() to echo true 11 | # 2- target_get_bootloader_install_command() to echo 12 | # an appropriate command (e.g. grub-install) 13 | 14 | target_use_lvm() { 15 | echo false 16 | } 17 | 18 | # for details about the following functions 19 | # see 'steps' file 20 | 21 | #target_preliminary_steps() { 22 | # preliminary_steps $@ 23 | #} 24 | 25 | #target_final_customization_steps() { 26 | # final_customization_steps $@ 27 | #} 28 | 29 | # for details about the following functions 30 | # see 'packages' file 31 | 32 | target_kernel_default_package() { 33 | kernel_default_package 34 | } 35 | 36 | #target_custom_packages() { 37 | # custom_packages 38 | #} 39 | 40 | # for details about the following functions 41 | # see 'bootloader' file 42 | 43 | target_configure_bootloader() { 44 | configure_bootloader 45 | } 46 | 47 | target_install_bootloader() { 48 | install_bootloader 49 | } 50 | 51 | #target_get_bootloader_install_command() { 52 | # echo my-bootloader-install 53 | #} 54 | 55 | # for details about the following functions 56 | # see 'rootfs' file 57 | # (these functions are also optional, but 58 | # often needed.) 59 | 60 | target_prepare_rootfs() { 61 | prepare_rootfs $@ 62 | } 63 | 64 | target_cleanup_rootfs() { 65 | cleanup_rootfs $@ 66 | } 67 | -------------------------------------------------------------------------------- /scripts/create-image/target/skeleton/packages: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | custom_packages() 4 | { 5 | #echo package1 package2 6 | } 7 | 8 | kernel_default_package() 9 | { 10 | #echo linux-kernel-package 11 | } 12 | -------------------------------------------------------------------------------- /scripts/create-image/target/skeleton/rootfs: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | prepare_rootfs() 4 | { 5 | image_type="$1" # draft or final 6 | chroot_status="$2" # inside or outside 7 | # do something... 8 | } 9 | 10 | cleanup_rootfs() 11 | { 12 | image_type="$1" # draft or final 13 | chroot_status="$2" # inside or outside 14 | # do something... 15 | } 16 | -------------------------------------------------------------------------------- /scripts/create-image/target/skeleton/steps: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # this file can be deleted unless you implement one of the 4 | # related optional functions (see entrypoints file). 5 | -------------------------------------------------------------------------------- /scripts/live/init/first-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # bash is needed for proper handling of password prompt. 3 | set -e 4 | INIT_SCRIPTS_DIR=/opt/debootstick/live/init 5 | . /dbstck.conf # for config values 6 | . $INIT_SCRIPTS_DIR/tools.sh # for functions 7 | 8 | # if error, run a shell 9 | trap '[ "$?" -eq 0 ] || fallback_sh' EXIT 10 | 11 | # this script is now called at the end of the OS bootup procedure, 12 | # (getty hook), no need to mount/umount filesystems 13 | 14 | # ask and set the root password if needed 15 | if [ "$ASK_ROOT_PASSWORD_ON_FIRST_BOOT" = "1" ] 16 | then 17 | ask_and_set_pass 18 | fi 19 | 20 | # run initialization script 21 | if [ "$SYSTEM_TYPE" = "installer" ] 22 | then 23 | $INIT_SCRIPTS_DIR/migrate-to-disk.sh 24 | else # 'live' mode 25 | $INIT_SCRIPTS_DIR/occupy-space.sh 26 | fi 27 | 28 | if [ -n "$FIRST_BOOT_SCRIPT" ] 29 | then 30 | echo "Running custom first boot script." 31 | $FIRST_BOOT_SCRIPT 32 | fi 33 | -------------------------------------------------------------------------------- /scripts/live/init/getty-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . /dbstck.conf # get GETTY_COMMAND 3 | 4 | first_init() 5 | { 6 | # - we will talk to the console 7 | # - since we were called as a subprocess, 8 | # we can avoid leaking the lock fd 9 | exec 0/dev/console 2>&1 200>&- 10 | # run debootstick init procedure 11 | /opt/debootstick/live/init/first-init.sh 12 | } 13 | 14 | # several getty processes will be spawned concurrently, 15 | # we have to use a lock 16 | { 17 | flock 200 18 | if [ -f "${GETTY_COMMAND}.orig" ] 19 | then 20 | # original getty not restored yet 21 | # => this means we are first, we will do the job. 22 | (first_init) # execute in a sub-shell 23 | # restore original getty 24 | mv "${GETTY_COMMAND}.orig" "$GETTY_COMMAND" 25 | fi 26 | } 200>/var/lib/debootstick-init.lock 27 | 28 | exec "$GETTY_COMMAND" "$@" 29 | -------------------------------------------------------------------------------- /scripts/live/init/migrate-to-disk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | THIS_DIR=$(cd $(dirname $0); pwd) 4 | . $THIS_DIR/tools.sh 5 | 6 | clear 7 | echo "** ---- INSTALLER MODE -------------------" 8 | if ! root_on_lvm 9 | then 10 | echo "** ERROR: Root filesystem is not built on LVM!" 11 | echo "** ERROR: Installer mode seems broken on this target." 12 | echo "Aborted!" 13 | exit 1 14 | fi 15 | 16 | if [ -z "$BOOTLOADER_INSTALL" ] 17 | then 18 | echo "** ERROR: Unknown bootloader installation procedure!" 19 | echo "** ERROR: Installer mode seems broken on this target." 20 | echo "Aborted!" 21 | exit 1 22 | fi 23 | 24 | ORIGIN=$(get_booted_device) 25 | 26 | origin_capacity=$(get_device_capacity $ORIGIN) 27 | larger_devices="$(get_higher_capacity_devices $origin_capacity)" 28 | 29 | if [ "$larger_devices" = "" ] 30 | then 31 | echo "Error: no device larger than the one currently booted was detected." >&2 32 | echo "Aborted!" 33 | exit 1 34 | fi 35 | 36 | if [ $(echo "$larger_devices" | wc -l) -eq 1 ] 37 | then 38 | TARGET=$larger_devices 39 | else 40 | menu_input="$( 41 | for device in $larger_devices 42 | do # item # item description 43 | echo $device "$device: $(get_device_label $device)" 44 | done)" 45 | echo Several target disks are available. 46 | TARGET=$(select_menu "$menu_input") 47 | echo "$TARGET selected." 48 | fi 49 | 50 | origin_label=$(get_device_label $ORIGIN) 51 | target_label=$(get_device_label $TARGET) 52 | 53 | echo "** About to start migration!" 54 | echo "** $origin_label --> $target_label" 55 | echo 56 | echo "** WARNING: Any existing data on target disk will be lost." 57 | echo "** WARNING: Press any key NOW to cancel this process." 58 | read -t 10 -n 1 && { echo "Aborted!"; exit 1; } 59 | echo "** Going on." 60 | 61 | { 62 | echo MSG making sure ${target_label} is not used... 63 | pvs --no-headings -o pv_name | while read pv_name 64 | do 65 | [ "$(part_to_disk $pv_name)" == "$TARGET" ] || continue 66 | vg=$(vgs --select "pv_name = $pv_name" --noheadings | awk '{print $1}') 67 | if [ -n "$vg" ]; then 68 | enforce_disk_cmd vgchange -an "$vg" 69 | enforce_disk_cmd vgremove -ff -y "$vg" 70 | fi 71 | enforce_disk_cmd pvremove -ff -y $pv_name 72 | done 73 | 74 | echo MSG copying the partition scheme... 75 | copy_partition_table ${ORIGIN} ${TARGET} 76 | 77 | # migrate partitions and LVM volumes 78 | process_volumes "${ORIGIN}" "${TARGET}" "migrate" 79 | 80 | echo MSG installing the bootloader... 81 | $BOOTLOADER_INSTALL ${TARGET} 82 | 83 | set_final_vg_name 84 | 85 | echo RETURN 0 86 | } | filter_quiet 87 | 88 | echo "** Migration completed." 89 | echo "** Source media ($origin_label) can be unplugged, it is not used anymore." 90 | 91 | -------------------------------------------------------------------------------- /scripts/live/init/occupy-space.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | THIS_DIR=$(cd $(dirname $0); pwd) 4 | . $THIS_DIR/tools.sh 5 | 6 | booted_device=$(get_booted_device) 7 | disk_size_mb="$(device_size_mb "$booted_device")" 8 | 9 | if [ "$((100*disk_size_mb))" -lt $((105*IMAGE_SIZE_MB)) ] 10 | then 11 | echo "Note: debootstick will not try to span partitions over this disk because it is not significantly larger than original image." 12 | exit 13 | fi 14 | 15 | echo "** Spanning over disk space..." 16 | 17 | { 18 | process_volumes "none" "$booted_device" "expand" 19 | 20 | set_final_vg_name 21 | 22 | echo RETURN 0 23 | } | filter_quiet 24 | 25 | echo "** Done." 26 | 27 | -------------------------------------------------------------------------------- /scripts/live/init/tools.sh: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | . /dbstck.conf # get conf 3 | PROGRESS_BAR_SIZE=40 4 | NICE_FACTOR_SCALE=100 5 | VG="DBSTCK_$STICK_OS_ID" 6 | 7 | show_progress_bar() 8 | { 9 | achieved=$1 10 | total=$2 11 | # ensure achieved <= total 12 | if [ $achieved -gt $total ] 13 | then 14 | achieved=$total 15 | fi 16 | progress_cnt=$((achieved*PROGRESS_BAR_SIZE/total)) 17 | progress_pts=$(printf "%${progress_cnt}s" "" | tr ' ' '*') 18 | progress_bar=$(printf "|%-${PROGRESS_BAR_SIZE}s|" "$progress_pts") 19 | printf "$progress_bar %d%%\r" $((achieved*100/total)) 20 | } 21 | 22 | disk_partitions() 23 | { 24 | lsblk -lno NAME,TYPE | while read disk_name type 25 | do 26 | test "$type" = disk || continue 27 | disk="/dev/$disk_name" 28 | lsblk -lno NAME,TYPE "$disk" | while read part_name type 29 | do 30 | test "$type" = part || continue 31 | part="/dev/$part_name" 32 | echo $disk $part 33 | done 34 | done 35 | } 36 | 37 | # check if root filesystem (i.e. "/") is on an LVM volume 38 | root_on_lvm() 39 | { 40 | rootfs_device=$(findmnt -no SOURCE /) 41 | echo "$rootfs_device" | sed 's/--/-/g' | grep "$VG" >/dev/null 42 | } 43 | 44 | get_booted_device() 45 | { 46 | if root_on_lvm 47 | then 48 | # rootfs is on LVM 49 | get_booted_device_from_vg $VG 50 | else 51 | # rootfs is on a partition 52 | rootfs_device=$(findmnt -no SOURCE /) 53 | part_to_disk $rootfs_device 54 | fi 55 | } 56 | 57 | part_to_disk() 58 | { 59 | set -- $(disk_partitions | grep -w "$1") 60 | echo $1 61 | } 62 | 63 | get_part_device() 64 | { 65 | disk=$1 66 | part_num=$2 67 | set -- $(disk_partitions | grep -w "$disk" | grep "$part_num$") 68 | echo $2 69 | } 70 | 71 | get_part_num() 72 | { 73 | echo -n $1 | tail -c 1 74 | } 75 | 76 | get_booted_device_from_vg() 77 | { 78 | set -- $(pvs | grep $VG) 79 | part_to_disk $1 80 | } 81 | 82 | get_higher_capacity_devices() 83 | { 84 | threshold=$1 85 | lsblk -lno PATH,TYPE | while read device type 86 | do 87 | test "$type" = disk || continue 88 | device_size=$(get_device_capacity "$device") 89 | if [ $device_size -gt $threshold ] 90 | then 91 | echo $device 92 | fi 93 | done 94 | } 95 | 96 | get_device_capacity() 97 | { 98 | device_name=$(echo $1 | sed -e 's/\/dev\///') 99 | cat /proc/partitions | while read major minor size name 100 | do 101 | if [ "$name" = "$device_name" ] 102 | then 103 | echo $((size*1024)) 104 | return 105 | fi 106 | done 107 | } 108 | 109 | M=$((1000000)) 110 | G=$((1000*M)) 111 | T=$((1000*G)) 112 | P=$((1000*T)) 113 | 114 | human_readable_disk_size() 115 | { 116 | bytes=$1 117 | if [ $bytes -ge $P ]; then echo $((bytes/P))P; return; fi 118 | if [ $bytes -ge $T ]; then echo $((bytes/T))T; return; fi 119 | if [ $bytes -ge $G ]; then echo $((bytes/G))G; return; fi 120 | echo $((bytes/M))M 121 | } 122 | 123 | get_device_label() 124 | { 125 | size=$(get_device_capacity $1) 126 | shortname=$(echo $1 | sed -e 's/\/dev\///') 127 | if [ -f "/sys/class/block/$shortname/device/model" ] 128 | then 129 | model=$(cat /sys/class/block/$shortname/device/model) 130 | else 131 | model="DISK" 132 | fi 133 | echo $model $(human_readable_disk_size $size) 134 | } 135 | 136 | select_menu() 137 | { 138 | input="$1" 139 | num_entries=$(echo "$input" | wc -l) 140 | height=$((num_entries+1)) 141 | move_key="n" 142 | select_key="s" 143 | { 144 | # prepare a blank screen of $height lines 145 | for i in $(seq $height) 146 | do 147 | echo 148 | done 149 | selected=1 150 | while [ 1 ] 151 | do 152 | # return to the top of the screen 153 | echo -en "\033[${height}A\r" 154 | # print the screen 155 | echo "$input" | { 156 | i=1 157 | while read dev dev_label 158 | do 159 | if [ $i = $selected ] 160 | then 161 | echo "> $dev_label " 162 | else 163 | echo " $dev_label " 164 | fi 165 | i=$((i+1)) 166 | done 167 | } 168 | echo "Press <$move_key> to move selection or <$select_key> to select." 169 | # read user input 170 | stty -echo; read -n 1 key; stty echo 171 | # react to user input 172 | if [ "$key" = "$move_key" ] 173 | then 174 | selected=$((selected % num_entries)) 175 | selected=$((selected + 1)) 176 | fi 177 | if [ "$key" = "$select_key" ] 178 | then 179 | break 180 | fi 181 | done 182 | } >&2 # UI goes to stderr, result to stdout 183 | echo "$input" | head -n $selected | tail -n 1 | { 184 | read dev dev_label 185 | echo -n $dev 186 | } 187 | } 188 | 189 | fallback_sh() 190 | { 191 | echo 'An error occured. Starting a shell.' 192 | echo '(the system will be rebooted on exit.)' 193 | sh 194 | reboot -f 195 | } 196 | 197 | filter_quiet() 198 | { 199 | while read action line 200 | do 201 | case $action in 202 | "MSG") 203 | echo $line 204 | ;; 205 | "REFRESHING_MSG") 206 | case "$line" in 207 | *%) # this is a percentage 208 | percent_float=$(echo $line | sed 's/.$//') 209 | percent_int=$(LC_ALL=C printf "%.0f" $percent_float) 210 | show_progress_bar $percent_int 100 211 | ;; 212 | *) 213 | echo -en "$line\r" 214 | ;; 215 | esac 216 | ;; 217 | "REFRESHING_DONE") 218 | echo 219 | ;; 220 | "RETURN") 221 | return $line 222 | ;; 223 | *) 224 | ;; 225 | esac 226 | done 227 | # a read failed and we did not get any RETURN, 228 | # return an error status 229 | return 1 230 | } 231 | 232 | # we deliberately let the echo enabled will typing 233 | # the password in order to let the user verify what 234 | # he is typing (i.e. the keymap may not be what he is 235 | # used to). Once validated, we replace chars with stars. 236 | ask_and_set_pass() 237 | { 238 | echo "Enter a root password for this system." 239 | # prompt for the password 240 | read -p 'password: ' password 241 | # return 1 line up 242 | echo -en "\033[1A\r" 243 | # overwrite with stars 244 | echo "password: $(echo "$password" | sed -e 's/./*/g')" 245 | # set the password 246 | echo "root:$password" | chpasswd 247 | } 248 | 249 | dump_partition_info() 250 | { 251 | disk_device="$1" 252 | echo "$PARTITIONS" | while IFS=";" read part_id subtype mountpoint size 253 | do 254 | device="$(get_part_device $disk_device $part_id)" 255 | echo "part;$device;$subtype;$mountpoint;$size" 256 | done 257 | } 258 | 259 | dump_lvm_info() 260 | { 261 | if [ "$LVM_VOLUMES" != "" ] 262 | then 263 | echo "$LVM_VOLUMES" | while IFS=";" read label subtype mountpoint size 264 | do 265 | echo "lvm;/dev/$VG/$label;$subtype;$mountpoint;$size" 266 | done 267 | fi 268 | } 269 | 270 | dump_volumes_info() 271 | { 272 | dump_partition_info "$1" 273 | dump_lvm_info 274 | } 275 | 276 | lvm_partition_size_mb() 277 | { 278 | dump_partition_info "$1" | while IFS=";" read vol_type device subtype mountpoint size 279 | do 280 | if [ "$subtype" = "lvm" ] 281 | then 282 | device_size_mb "$device" 283 | break # done 284 | fi 285 | done 286 | } 287 | 288 | lvm_sum_size_mb() 289 | { 290 | dump_lvm_info | while IFS=";" read vol_type device subtype mountpoint size 291 | do 292 | device_size_mb "$device" 293 | done | sum_lines 294 | } 295 | 296 | size_as_mb() 297 | { 298 | echo $(($(echo $1 | sed -e "s/M//" -e "s/G/*1024/"))) 299 | } 300 | 301 | sum_lines() { 302 | exp="$(paste -sd+ -)" 303 | [ "$exp" = "" ] && echo 0 || echo "$(($exp))" 304 | } 305 | 306 | device_size_mb() { 307 | size_b=$(blockdev --getsize64 $1) 308 | echo $((size_b/1024/1024)) 309 | } 310 | 311 | analysis_step1() { 312 | nice_factor="$1" 313 | disk_size_mb="$2" 314 | 315 | while IFS=";" read voltype device subtype mountpoint size 316 | do 317 | current_size_mb=$(device_size_mb $device) 318 | case "$size" in 319 | *%) 320 | # percentage 321 | percent_requested=$(echo $size | tr -d '%') 322 | size_mb=$((percent_requested*nice_factor*disk_size_mb/NICE_FACTOR_SCALE/100)) 323 | if [ $current_size_mb -ge $size_mb ] 324 | then # percentage too low regarding current size, convert to 'auto' 325 | echo "$voltype;auto;$device;$subtype;$mountpoint;$current_size_mb" 326 | else # convert to fixed size, to ease later processing 327 | echo "$voltype;fixed;$device;$subtype;$mountpoint;$size_mb" 328 | fi 329 | ;; 330 | *[MG]) 331 | # fixed size 332 | size_requested_mb=$(size_as_mb $size) 333 | size_mb=$((size_requested_mb*nice_factor/NICE_FACTOR_SCALE)) 334 | if [ $size_mb -le $current_size_mb ] 335 | then # fixed size too low regarding current size (because of nice factor), convert to 'auto' 336 | echo "$voltype;auto;$device;$subtype;$mountpoint;$current_size_mb" 337 | else 338 | echo "$voltype;fixed;$device;$subtype;$mountpoint;$size_mb" 339 | fi 340 | ;; 341 | max) 342 | echo "$voltype;max;$device;$subtype;$mountpoint;$current_size_mb" 343 | ;; 344 | auto) 345 | echo "$voltype;auto;$device;$subtype;$mountpoint;$current_size_mb" 346 | ;; 347 | *) 348 | echo "unknown size! '$size'" >&2 349 | return 1 350 | ;; 351 | esac 352 | done 353 | } 354 | 355 | last_field() 356 | { 357 | awk 'BEGIN {FS = ";"}; {print $NF}' 358 | } 359 | 360 | compute_applied_sizes() 361 | { 362 | volumes_type="$1" 363 | volumes_info="$2" 364 | disk_size_mb="$3" 365 | total_size_mb="$4" 366 | 367 | # compute applied size of volumes 368 | nice_factor=$NICE_FACTOR_SCALE # init nice_factor at ratio 1.0 369 | 370 | while true 371 | do 372 | volume_analysis_step1="$(echo "$volumes_info" | analysis_step1 $nice_factor $disk_size_mb)" 373 | 374 | static_size_mb=$(echo "$volume_analysis_step1" | grep -v ";fixed;" | last_field | sum_lines) 375 | space_size_mb=$((total_size_mb-static_size_mb)) 376 | sum_fixed_mb=$(echo "$volume_analysis_step1" | grep ";fixed;" | last_field | sum_lines) 377 | 378 | if [ $sum_fixed_mb -gt $space_size_mb ] 379 | then 380 | prev_nice_factor="$nice_factor" 381 | # there is not enough free space for the sum of percents or fixed sizes requested. 382 | # we have to share free space in a proportional way. 383 | # instead of giving each volume the fixed size requested (or fixed size corresponding to the percentage requested), 384 | # we give (fixed_mb/sum_fixed_mb*space_size_mb) megabytes, 385 | # thus, we apply each size requested a 'nice-factor' of (space_size_mb/sum_fixed_mb). 386 | nice_factor=$((space_size_mb*NICE_FACTOR_SCALE/sum_fixed_mb)) 387 | # ensure approximation will not be a problem 388 | if [ $nice_factor -ge $prev_nice_factor ] 389 | then 390 | nice_factor=$((prev_nice_factor-1)) 391 | fi 392 | if [ "$volumes_type" = "partition" ] 393 | then 394 | echo "Note: requested partition sizes and percentages are too high regarding the size of this disk." >&2 395 | else 396 | echo "Note: requested lvm volume sizes and percentages are too high regarding the size of the lvm partition." >&2 397 | fi 398 | echo "Note: debootstick will adapt them." >&2 399 | else 400 | break # Ok 401 | fi 402 | done 403 | 404 | echo "$volume_analysis_step1" | \ 405 | while IFS=";" read voltype sizetype device subtype mountpoint current_size_mb 406 | do 407 | case $sizetype in 408 | "fixed") 409 | echo "$voltype;$device;$subtype;$mountpoint;$current_size_mb" 410 | ;; 411 | "auto") 412 | echo "$voltype;$device;$subtype;$mountpoint;keep" 413 | ;; 414 | "max") 415 | echo "$voltype;$device;$subtype;$mountpoint;max" 416 | ;; 417 | esac 418 | done 419 | } 420 | 421 | get_sector_size() 422 | { 423 | blockdev --getss "$1" 424 | } 425 | 426 | get_part_table_type() 427 | { 428 | do_quiet_err_only sfdisk --dump "$1" | grep "^label:" | awk '{print $2}' 429 | } 430 | 431 | # this function is a little more complex than expected 432 | # because it has to deal with possibly different sector sizes. 433 | copy_partition_table() 434 | { 435 | source_disk="$1" 436 | target_disk="$2" 437 | 438 | source_disk_ss=$(get_sector_size $source_disk) 439 | target_disk_ss=$(get_sector_size $target_disk) 440 | 441 | multiplier=1 442 | divider=1 443 | if [ $source_disk_ss -gt $target_disk_ss ] 444 | then 445 | multiplier=$((source_disk_ss / target_disk_ss)) 446 | elif [ $target_disk_ss -gt $source_disk_ss ] 447 | then 448 | divider=$((target_disk_ss / source_disk_ss)) 449 | fi 450 | 451 | { 452 | echo "label: $(get_part_table_type $source_disk)" 453 | echo 454 | partx -o NR,START,SECTORS,TYPE -P $source_disk | while read line 455 | do 456 | eval $line 457 | START=$((START*multiplier/divider)) 458 | SECTORS=$((SECTORS*multiplier/divider)) 459 | echo " $NR : start=$START, type=$TYPE, size=$SECTORS" 460 | done 461 | } | sfdisk --no-reread $target_disk >/dev/null 462 | partx -u ${target_disk} # notify the kernel 463 | } 464 | 465 | resize_last_partition() 466 | { 467 | disk="$1" 468 | applied_size_mb="$2" 469 | disk_sector_size=$(get_sector_size $disk) 470 | partx_sector_size=512 # partx unit is always 512-bytes sectors 471 | 472 | # note: we need to pass partition offsets and size to sfdisk 473 | # using the disk sector size as unit. 474 | # conversions should be carefully written to avoid integer overflows 475 | # (partition offset and size may be large if converted to bytes...) 476 | 477 | if [ "$(blkid -o value -s PTTYPE $disk)" = "gpt" ] 478 | then 479 | # move backup GPT data structures to the end of the disk, otherwise 480 | # sfdisk might not allow the partition to span 481 | sgdisk -e $disk 482 | fi 483 | 484 | eval $(partx -o NR,START,TYPE -P $disk | tail -n 1) 485 | # convert partition start offset unit from 'partx sector size' to 'disk sector size' 486 | START=$((START/(disk_sector_size/partx_sector_size))) 487 | 488 | if [ "$applied_size_mb" = "max" ] 489 | then 490 | # do not specify the size => it will extend to the end of the disk 491 | part_def=" $NR : start=$START, type=$TYPE" 492 | else 493 | part_size_in_sectors=$((applied_size_mb*(1024*1024/disk_sector_size))) 494 | part_def=" $NR : start=$START, type=$TYPE, size=$part_size_in_sectors" 495 | fi 496 | 497 | # we delete, then re-create the partition with same information 498 | # except the size 499 | sfdisk --no-reread $disk >/dev/null 2>&1 << EOF || true 500 | $(sfdisk -d $disk | head -n -1) 501 | $part_def 502 | EOF 503 | partx -u ${disk} # notify the kernel 504 | } 505 | 506 | enforce_disk_cmd() { 507 | udevadm settle; sync; sync 508 | i=0 509 | while [ $i -lt 10 ]; do 510 | # handle rare failures 511 | "$@" 2>/dev/null && break || sleep 1 512 | i=$((i+1)) 513 | done 514 | if [ $i -eq 10 ] 515 | then # Still failing after 10 times! 516 | echo "MSG ERROR: command '$@' fails!" 517 | return 1 518 | fi 519 | } 520 | 521 | resize_lvm_volume() 522 | { 523 | device="$1" 524 | applied_size_mb="$2" 525 | 526 | if [ "$applied_size_mb" = "max" ] 527 | then 528 | free_extents=$(vgs --select "vg_name = $VG" --no-headings -o vg_free_count) 529 | if [ $free_extents -eq 0 ] 530 | then 531 | echo "MSG Not resized (no more free space)." 532 | else 533 | enforce_disk_cmd lvextend -l+100%FREE "$device" 534 | fi 535 | else 536 | enforce_disk_cmd lvextend -L${applied_size_mb}M "$device" 537 | fi 538 | } 539 | 540 | device_name() 541 | { 542 | if [ "$1" = "part" ] 543 | then 544 | echo "partition $2" 545 | else 546 | echo "lvm logical volume $(basename $2)" 547 | fi 548 | } 549 | 550 | # some commands print informational messages or minor warnings on stderr, 551 | # silence them together with stdout unless the command really fails. 552 | do_quiet() 553 | { 554 | return_code=0 555 | output="$( 556 | "$@" 2>&1 557 | )" || return_code=$? 558 | if [ $return_code -ne 0 ] 559 | then 560 | echo "$output" >&2 561 | return $return_code 562 | fi 563 | } 564 | 565 | # same thing, but just filter out stderr, keep stdout 566 | # (if the command fails, restore stderr output). 567 | do_quiet_err_only() 568 | { 569 | { 570 | return_code=0 571 | output="$( 572 | "$@" 3>&1 1>&2 2>&3 # swap stdout <-> stderr 573 | )" || return_code=$? 574 | if [ $return_code -ne 0 ] 575 | then 576 | echo "$output" >&1 577 | return $return_code 578 | fi 579 | } 3>&1 1>&2 2>&3 # restore stdout <-> stderr 580 | } 581 | 582 | # fatresize prints a warning when it has to convert FAT16 to FAT32 583 | # to handle a larger size. Hide it unless the command really fails. 584 | quiet_fatresize() 585 | { 586 | device="$1" 587 | dev_size=$(blockdev --getsize64 "$device") 588 | do_quiet fatresize -s $dev_size "$device" 589 | } 590 | 591 | get_origin_voldevice() 592 | { 593 | voldevice="$1" 594 | voltype="$2" 595 | origin_device="$3" 596 | if [ "$voltype" = "lvm" -o "$origin_device" = "none" ] 597 | then 598 | echo "none" 599 | else 600 | part_num=$(get_part_num "$voldevice") 601 | echo $(get_part_device "$origin_device" $part_num) 602 | fi 603 | } 604 | 605 | unmount_tree() 606 | { 607 | for mp in $(findmnt --list --submounts --mountpoint "$1" -o TARGET --noheadings | tac) 608 | do 609 | umount $mp || umount -lf $mp || { 610 | echo "MSG unmounting failed, but everything should be fine on next reboot." 611 | break 612 | } 613 | done 614 | } 615 | 616 | make_filesystem() { 617 | voldevice="$1" 618 | subtype="$2" 619 | label="$3" 620 | uuid="$4" 621 | 622 | options="" 623 | case "$subtype" in 624 | efi|fat) 625 | fstype="vfat" 626 | if [ ! -z "$label" ] 627 | then 628 | options="-n $label" 629 | fi 630 | if [ ! -z "$uuid" ] 631 | then 632 | uuid=$(echo "$uuid" | tr -d "-") 633 | options="$options -i $uuid" 634 | fi 635 | ;; 636 | ext4) 637 | fstype="ext4" 638 | if [ ! -z "$label" ] 639 | then 640 | options="-L $label" 641 | fi 642 | if [ ! -z "$uuid" ] 643 | then 644 | options="$options -U $uuid" 645 | fi 646 | ;; 647 | esac 648 | 649 | mkfs -t $fstype $options "$voldevice" 650 | } 651 | 652 | copy_files() { 653 | src_dir="$1" 654 | dst_dir="$2" 655 | cp -a "$src_dir/." "$dst_dir" 656 | } 657 | 658 | copy_partition() { 659 | origin_voldevice="$1" 660 | voldevice="$2" 661 | subtype="$3" 662 | mountpoint="$4" 663 | temp_dir="$5" 664 | 665 | # retrieve uuid & label 666 | uuid="$(blkid -o value -s UUID "$origin_voldevice")" 667 | label="$(blkid -o value -s LABEL "$origin_voldevice")" 668 | 669 | # make filesystem on target 670 | make_filesystem $voldevice "$subtype" "$label" "$uuid" 671 | 672 | # (re)mount origin and target on fixed temp mountpoints 673 | old_mountpoint="$temp_dir/old" 674 | new_mountpoint="$temp_dir/new" 675 | mkdir -p "$old_mountpoint" "$new_mountpoint" 676 | if [ "$mountpoint" != "none" ] 677 | then 678 | unmount_tree "$mountpoint" 679 | fi 680 | mount "$origin_voldevice" "$old_mountpoint" 681 | mount "$voldevice" "$new_mountpoint" 682 | 683 | # copy partition files 684 | copy_files "$old_mountpoint" "$new_mountpoint" 685 | 686 | # umount temp mountpoints 687 | umount "$old_mountpoint" 688 | umount "$new_mountpoint" 689 | } 690 | 691 | get_steps() { 692 | case "$1" in 693 | expand-keep-*) 694 | # nothing to do 695 | ;; 696 | expand-*-part-*) 697 | # we know it is the last partition 698 | echo "resize_last_partition resize_content" 699 | ;; 700 | expand-*-lvm-*) 701 | echo "resize_lvm_volume resize_content" 702 | ;; 703 | migrate-keep-part-lvm) 704 | echo "init_lvm_pv migrate_lvm" 705 | ;; 706 | migrate-keep-part-bios) 707 | # bootloader installation will initialize the target partition 708 | echo "wipe_orig_part" 709 | ;; 710 | migrate-keep-part-*) 711 | echo "copy_partition wipe_orig_part" 712 | ;; 713 | migrate-*-part-lvm) 714 | # we know it is the last partition 715 | echo "resize_last_partition init_lvm_pv migrate_lvm" 716 | ;; 717 | migrate-*-part-bios) # this should be unusual!! 718 | # we know it is the last partition 719 | echo "resize_last_partition wipe_orig_part" 720 | ;; 721 | migrate-*-part-*) 722 | # we know it is the last partition 723 | echo "resize_last_partition copy_partition wipe_orig_part" 724 | ;; 725 | migrate-keep-lvm-*) 726 | # nothing to do 727 | ;; 728 | migrate-*-lvm-*) 729 | echo "resize_lvm_volume resize_content" 730 | ;; 731 | *) 732 | echo "BUG: volume descriptor $1 unexpected!" >&2 733 | return 1 # error 734 | esac 735 | } 736 | 737 | process_volumes() { 738 | origin_device="$1" 739 | target_device="$2" 740 | operation="$3" 741 | 742 | if [ "$LVM_VOLUMES" != "" ] 743 | then 744 | # note: target_device variable is always available (when called from 745 | # 'occupy-space.sh' and from 'migrate-to-disk.sh'); and in the later 746 | # case, partition table has already been copied from origin_device. 747 | orig_lvm_part_size_mb="$(lvm_partition_size_mb "$target_device")" 748 | orig_lvm_sum_size_mb="$(lvm_sum_size_mb)" 749 | lvm_overhead_mb="$((orig_lvm_part_size_mb - orig_lvm_sum_size_mb))" # should be 4mb 750 | fi 751 | 752 | echo MSG gathering partition resize data... 753 | disk_size_mb="$(device_size_mb "$target_device")" 754 | volumes_info="$(dump_partition_info "$target_device")" 755 | partitions_format_info="$(compute_applied_sizes partition "$volumes_info" "$disk_size_mb" "$disk_size_mb")" 756 | process_part_of_volumes "$origin_device" "$target_device" "$operation" "$partitions_format_info" 757 | 758 | if [ "$LVM_VOLUMES" != "" ] 759 | then 760 | echo MSG gathering lvm volume resize data... 761 | lvm_part_size_mb="$(lvm_partition_size_mb "$target_device")" 762 | if [ "$((100*lvm_part_size_mb))" -lt $((105*orig_lvm_part_size_mb)) ] 763 | then 764 | echo "MSG Note: debootstick will not try to resize lvm volumes since lvm partition was not resized (or not significantly resized)." 765 | else 766 | volumes_info="$(dump_lvm_info)" 767 | lvm_available_size_mb="$((lvm_part_size_mb - lvm_overhead_mb))" 768 | lvm_format_info="$(compute_applied_sizes lvm_volume "$volumes_info" "$disk_size_mb" "$lvm_available_size_mb")" 769 | process_part_of_volumes "$origin_device" "$target_device" "$operation" "$lvm_format_info" 770 | fi 771 | fi 772 | 773 | if [ "$operation" = "migrate" ] 774 | then 775 | echo MSG making sure ${origin_device} is not used anymore... 776 | enforce_disk_cmd partx -d ${origin_device} || true 777 | 778 | echo "MSG ensuring all filesystems are (re-)mounted..." 779 | enforce_disk_cmd partx -u ${target_device} && \ 780 | enforce_disk_cmd mount -a || echo "MSG this failed, but everything should be fine on next reboot." 781 | fi 782 | } 783 | 784 | process_part_of_volumes() { 785 | origin_device="$1" 786 | target_device="$2" 787 | operation="$3" 788 | format_info="$4" 789 | temp_dir="$(mktemp -d)" 790 | 791 | # we process lines with "applied_size=max" last (sort key) 792 | echo "$format_info" | sort -t ";" -k 5,5 | \ 793 | while IFS=";" read voltype voldevice subtype mountpoint applied_size 794 | do 795 | dev_name=$(device_name $voltype $voldevice) 796 | origin_voldevice=$(get_origin_voldevice $voldevice $voltype $origin_device) 797 | 798 | steps="$(get_steps "$operation-$applied_size-$voltype-$subtype")" 799 | 800 | for step in $steps 801 | do 802 | case "$step" in 803 | resize_last_partition) 804 | echo MSG resizing last partition... 805 | resize_last_partition "$target_device" "$applied_size" 806 | ;; 807 | resize_content) 808 | case "$subtype" in 809 | "ext4") 810 | echo MSG resizing ext4 filesystem on $dev_name... 811 | do_quiet resize2fs "$voldevice" 812 | ;; 813 | "fat"|"efi") 814 | echo MSG resizing FAT filesystem on $dev_name... 815 | umount "$voldevice" 816 | quiet_fatresize "$voldevice" 817 | mount "$voldevice" 818 | ;; 819 | "lvm") # physical volume on a partition 820 | echo MSG extending lvm physical volume on $dev_name... 821 | enforce_disk_cmd pvresize "$voldevice" 822 | ;; 823 | esac 824 | ;; 825 | resize_lvm_volume) 826 | echo MSG resizing $dev_name... 827 | resize_lvm_volume "$voldevice" "$applied_size" 828 | ;; 829 | init_lvm_pv) 830 | echo MSG initializing LVM physical volume on $dev_name... 831 | enforce_disk_cmd pvcreate -ff -y "$voldevice" 832 | ;; 833 | migrate_lvm) 834 | echo MSG moving the lvm volume content on $target_device... 835 | enforce_disk_cmd vgextend $VG "$voldevice" 836 | enforce_disk_cmd pvchange -x n "$origin_voldevice" 837 | enforce_disk_cmd pvmove -i 1 "$origin_voldevice" | while read pv action percent 838 | do 839 | echo REFRESHING_MSG "$percent" 840 | done 841 | enforce_disk_cmd vgreduce $VG "$origin_voldevice" 842 | enforce_disk_cmd pvremove -ff -y "$origin_voldevice" 843 | echo REFRESHING_DONE 844 | ;; 845 | copy_partition) 846 | echo "MSG copying partition $origin_voldevice -> $voldevice..." 847 | copy_partition "$origin_voldevice" "$voldevice" "$subtype" \ 848 | "$mountpoint" "$temp_dir" 849 | ;; 850 | wipe_orig_part) 851 | echo "MSG wiping $origin_voldevice..." 852 | wipefs -a "$origin_voldevice" 853 | ;; 854 | *) 855 | echo "MSG BUG: unexpected step '$step'!" 856 | return 1 # error 857 | esac 858 | done 859 | done 860 | rm -rf "$temp_dir" 861 | } 862 | 863 | grub_vg_rename() { 864 | sed -i -e "s/$VG/$FINAL_VG_NAME/g" /boot/grub/grub.cfg 865 | echo "MSG Note: File /boot/grub/grub.cfg was modified with new volume group name." 866 | echo "MSG Note: If you need to reconfigure or update the bootloader, please reboot first." 867 | vgrename "$VG" "$FINAL_VG_NAME" 868 | } 869 | 870 | set_final_vg_name() 871 | { 872 | if [ -z "$FINAL_VG_NAME" ] 873 | then 874 | # nothing to do 875 | return 876 | fi 877 | 878 | echo "MSG renaming the LVM volume group $VG -> $FINAL_VG_NAME..." 879 | # check that final vg name does not already exist on one of the disks 880 | exists=$(vgs -o vg_name --noheadings | grep -w "$FINAL_VG_NAME" | wc -l) 881 | if [ "$exists" -eq 0 ] 882 | then # ok, let's do it 883 | $VG_RENAME 884 | echo "MSG Note: LVM volume group was successfully renamed to '$FINAL_VG_NAME'." 885 | echo "MSG Note: (however some commands may still print the previous name until next reboot.)" 886 | else 887 | echo "WARNING: Could not rename LVM volume group because '$FINAL_VG_NAME' already exists!" >&2 888 | fi 889 | } 890 | 891 | grub-install() 892 | { 893 | # grub-install prints messages to standard 894 | # error stream although most of these are just 895 | # informational (or minor issues). This function masks 896 | # the grub-install program to discard those spurious 897 | # messages. 898 | # caution with shebangs: bash is needed to allow a 899 | # function name containing '-' char. 900 | do_quiet env grub-install "$@" 901 | } 902 | --------------------------------------------------------------------------------