mkarchiso 13.8 KB
Newer Older
1
#!/bin/bash
2

3
4
set -e -u

5
6
export LANG=C

7
8
app_name=${0##*/}
arch=$(uname -m)
9
pkg_list=()
10
run_cmd=""
11
12
quiet="y"
pacman_conf="/etc/pacman.conf"
13
iso_label="ARCH_$(date +%Y%m)"
14
15
16
iso_publisher="Arch Linux <http://www.archlinux.org>"
iso_application="Arch Linux Live/Rescue CD"
install_dir="arch"
17
18
work_dir="work"
out_dir="out"
19
sfs_mode="sfs"
20
sfs_comp="xz"
21
gpg_key=
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# Show an INFO message
# $1: message string
_msg_info() {
    local _msg="${1}"
    echo "[mkarchiso] INFO: ${_msg}"
}

# Show an ERROR message then exit with status
# $1: message string
# $2: exit code number (with 0 does not exit)
_msg_error() {
    local _msg="${1}"
    local _error=${2}
    echo
    echo "[mkarchiso] ERROR: ${_msg}"
    echo
    if [[ ${_error} -gt 0 ]]; then
40
        exit "${_error}"
41
42
43
    fi
}

44
_chroot_init() {
45
    mkdir -p ${work_dir}/airootfs
46
    _pacman base syslinux
47
48
49
}

_chroot_run() {
50
    eval arch-chroot ${work_dir}/airootfs "${run_cmd}"
51
52
}

53
54
55
56
57
58
_mount_airootfs() {
    trap "_umount_airootfs" EXIT HUP INT TERM
    mkdir -p "${work_dir}/mnt/airootfs"
    _msg_info "Mounting '${work_dir}/airootfs.img' on '${work_dir}/mnt/airootfs'"
    mount "${work_dir}/airootfs.img" "${work_dir}/mnt/airootfs"
    _msg_info "Done!"
59
60
}

61
62
_umount_airootfs() {
    _msg_info "Unmounting '${work_dir}/mnt/airootfs'"
63
    umount -d "${work_dir}/mnt/airootfs"
64
65
    _msg_info "Done!"
    rmdir "${work_dir}/mnt/airootfs"
66
67
68
69
70
71
    trap - EXIT HUP INT TERM
}

# Show help usage, with an exit status.
# $1: exit status number.
_usage ()
72
{
73
    echo "usage ${app_name} [options] command <command options>"
74
    echo " general options:"
75
    echo "    -p PACKAGE(S)    Package(s) to install, can be used multiple times"
76
    echo "    -r <command>     Run <command> inside airootfs"
77
78
    echo "    -C <file>        Config file for pacman."
    echo "                     Default: '${pacman_conf}'"
79
    echo "    -L <label>       Set a label for the disk"
80
    echo "                     Default: '${iso_label}'"
81
    echo "    -P <publisher>   Set a publisher for the disk"
82
    echo "                     Default: '${iso_publisher}'"
83
    echo "    -A <application> Set an application name for the disk"
84
    echo "                     Default: '${iso_application}'"
85
    echo "    -D <install_dir> Set an install_dir. All files will by located here."
86
    echo "                     Default: '${install_dir}'"
87
    echo "                     NOTE: Max 8 characters, use only [a-z0-9]"
88
    echo "    -w <work_dir>    Set the working directory"
89
    echo "                     Default: '${work_dir}'"
90
    echo "    -o <out_dir>     Set the output directory"
91
    echo "                     Default: '${out_dir}'"
92
93
94
95
    echo "    -s <sfs_mode>    Set SquashFS image mode (img or sfs)"
    echo "                     img: prepare airootfs.sfs for dm-snapshot usage"
    echo "                     sfs: prepare airootfs.sfs for overlayfs usage"
    echo "                     Default: ${sfs_mode}"
96
    echo "    -c <comp_type>   Set SquashFS compression type (gzip, lzma, lzo, xz, zstd)"
97
    echo "                     Default: '${sfs_comp}'"
98
99
    echo "    -v               Enable verbose output"
    echo "    -h               This message"
100
    echo " commands:"
101
102
103
104
105
106
    echo "   init"
    echo "      Make base layout and install base group"
    echo "   install"
    echo "      Install all specified packages (-p)"
    echo "   run"
    echo "      run command specified by -r"
107
    echo "   prepare"
108
    echo "      build all images"
109
    echo "   pkglist"
110
    echo "      make a pkglist.txt of packages installed on airootfs"
111
    echo "   iso <image name>"
112
    echo "      build an iso image from the working dir"
113
    exit "${1}"
114
115
}

116
# Shows configuration according to command mode.
117
# $1: init | install | run | prepare | iso
118
119
120
121
122
123
124
125
126
_show_config () {
    local _mode="$1"
    echo
    _msg_info "Configuration settings"
    _msg_info "                  Command:   ${command_name}"
    _msg_info "             Architecture:   ${arch}"
    _msg_info "        Working directory:   ${work_dir}"
    _msg_info "   Installation directory:   ${install_dir}"
    case "${_mode}" in
127
128
129
130
        init)
            _msg_info "       Pacman config file:   ${pacman_conf}"
            ;;
        install)
131
            _msg_info "       Pacman config file:   ${pacman_conf}"
132
            _msg_info "                 Packages:   ${pkg_list[*]}"
133
            ;;
134
135
136
        run)
            _msg_info "              Run command:   ${run_cmd}"
            ;;
137
138
        prepare)
            ;;
139
140
        pkglist)
            ;;
141
142
143
144
145
146
        iso)
            _msg_info "               Image name:   ${img_name}"
            _msg_info "               Disk label:   ${iso_label}"
            _msg_info "           Disk publisher:   ${iso_publisher}"
            _msg_info "         Disk application:   ${iso_application}"
            ;;
147
    esac
148
149
    echo
}
150

151
# Install desired packages to airootfs
152
153
_pacman ()
{
154
    _msg_info "Installing packages to '${work_dir}/airootfs/'..."
155
156

    if [[ "${quiet}" = "y" ]]; then
157
        pacstrap -C "${pacman_conf}" -c -G -M "${work_dir}/airootfs" "$@" &> /dev/null
158
    else
159
        pacstrap -C "${pacman_conf}" -c -G -M "${work_dir}/airootfs" "$@"
160
    fi
161

162
    _msg_info "Packages installed successfully!"
163
164
}

165
# Cleanup airootfs
166
_cleanup () {
167
    _msg_info "Cleaning up what we can on airootfs..."
168

169
    # Delete initcpio image(s)
170
171
    if [[ -d "${work_dir}/airootfs/boot" ]]; then
        find "${work_dir}/airootfs/boot" -type f -name '*.img' -delete
172
173
    fi
    # Delete kernel(s)
174
175
    if [[ -d "${work_dir}/airootfs/boot" ]]; then
        find "${work_dir}/airootfs/boot" -type f -name 'vmlinuz*' -delete
176
177
    fi
    # Delete pacman database sync cache files (*.tar.gz)
178
179
    if [[ -d "${work_dir}/airootfs/var/lib/pacman" ]]; then
        find "${work_dir}/airootfs/var/lib/pacman" -maxdepth 1 -type f -delete
180
181
    fi
    # Delete pacman database sync cache
182
183
    if [[ -d "${work_dir}/airootfs/var/lib/pacman/sync" ]]; then
        find "${work_dir}/airootfs/var/lib/pacman/sync" -delete
184
185
    fi
    # Delete pacman package cache
186
187
    if [[ -d "${work_dir}/airootfs/var/cache/pacman/pkg" ]]; then
        find "${work_dir}/airootfs/var/cache/pacman/pkg" -type f -delete
188
189
    fi
    # Delete all log files, keeps empty dirs.
190
191
    if [[ -d "${work_dir}/airootfs/var/log" ]]; then
        find "${work_dir}/airootfs/var/log" -type f -delete
192
193
    fi
    # Delete all temporary files and dirs
194
195
    if [[ -d "${work_dir}/airootfs/var/tmp" ]]; then
        find "${work_dir}/airootfs/var/tmp" -mindepth 1 -delete
196
    fi
197
    # Delete package pacman related files.
198
    find "${work_dir}" \( -name "*.pacnew" -o -name "*.pacsave" -o -name "*.pacorig" \) -delete
199
    _msg_info "Done!"
200
}
201

202
203
# Makes a ext4 filesystem inside a SquashFS from a source directory.
_mkairootfs_img () {
204
205
    if [[ ! -e "${work_dir}/airootfs" ]]; then
        _msg_error "The path '${work_dir}/airootfs' does not exist" 1
206
207
    fi

208
    _msg_info "Creating ext4 image of 32GiB..."
209
    truncate -s 32G "${work_dir}/airootfs.img"
210
    local _qflag=""
211
    if [[ "${quiet}" == "y" ]]; then
212
        _qflag="-q"
213
    fi
214
    mkfs.ext4 ${_qflag} -O ^has_journal,^resize_inode -E lazy_itable_init=0 -m 0 -F "${work_dir}/airootfs.img"
215
    tune2fs -c 0 -i 0 "${work_dir}/airootfs.img" &> /dev/null
216
    _msg_info "Done!"
217
    _mount_airootfs
218
219
    _msg_info "Copying '${work_dir}/airootfs/' to '${work_dir}/mnt/airootfs/'..."
    cp -aT "${work_dir}/airootfs/" "${work_dir}/mnt/airootfs/"
220
    chown root:root "${work_dir}/mnt/airootfs/"
221
    _msg_info "Done!"
222
    _umount_airootfs
223
    mkdir -p "${work_dir}/iso/${install_dir}/${arch}"
224
225
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
226
227
        mksquashfs "${work_dir}/airootfs.img" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
            -comp "${sfs_comp}" -no-progress &> /dev/null
228
    else
229
230
        mksquashfs "${work_dir}/airootfs.img" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
            -comp "${sfs_comp}" -no-progress
231
232
    fi
    _msg_info "Done!"
233
    rm "${work_dir}/airootfs.img"
234
235
}

236
237
238
239
240
241
242
243
244
# Makes a SquashFS filesystem from a source directory.
_mkairootfs_sfs () {
    if [[ ! -e "${work_dir}/airootfs" ]]; then
        _msg_error "The path '${work_dir}/airootfs' does not exist" 1
    fi

    mkdir -p "${work_dir}/iso/${install_dir}/${arch}"
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
245
246
        mksquashfs "${work_dir}/airootfs" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
            -comp "${sfs_comp}" -no-progress &> /dev/null
247
    else
248
249
        mksquashfs "${work_dir}/airootfs" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
            -comp "${sfs_comp}" -no-progress
250
251
252
253
    fi
    _msg_info "Done!"
}

254
255
256
_mkchecksum () {
    _msg_info "Creating checksum file for self-test..."
    cd "${work_dir}/iso/${install_dir}/${arch}"
257
    sha512sum airootfs.sfs > airootfs.sha512
258
    cd "${OLDPWD}"
259
    _msg_info "Done!"
260
261
}

262
263
264
_mksignature () {
    _msg_info "Creating signature file..."
    cd "${work_dir}/iso/${install_dir}/${arch}"
265
266
    gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs
    cd "${OLDPWD}"
267
268
269
    _msg_info "Done!"
}

270
271
272
command_pkglist () {
    _show_config pkglist

273
    _msg_info "Creating a list of installed packages on live-enviroment..."
274
    pacman -Q --sysroot "${work_dir}/airootfs" > \
275
276
        "${work_dir}/iso/${install_dir}/pkglist.${arch}.txt"
    _msg_info "Done!"
277
278
279

}

280
# Create an ISO9660 filesystem from "iso" directory.
281
command_iso () {
282
    local _iso_efi_boot_args=()
283

284
285
286
    if [[ ! -f "${work_dir}/iso/isolinux/isolinux.bin" ]]; then
         _msg_error "The file '${work_dir}/iso/isolinux/isolinux.bin' does not exist." 1
    fi
287
288
289
    if [[ ! -f "${work_dir}/iso/isolinux/isohdpfx.bin" ]]; then
         _msg_error "The file '${work_dir}/iso/isolinux/isohdpfx.bin' does not exist." 1
    fi
290

291
292
    # If exists, add an EFI "El Torito" boot image (FAT filesystem) to ISO-9660 image.
    if [[ -f "${work_dir}/iso/EFI/archiso/efiboot.img" ]]; then
293
294
295
296
297
298
        _iso_efi_boot_args+=(
            '-eltorito-alt-boot'
            '-e' 'EFI/archiso/efiboot.img'
            '-no-emul-boot'
            '-isohybrid-gpt-basdat'
        )
299
300
    fi

301
302
    _show_config iso

303
    mkdir -p "${out_dir}"
304
305
    _msg_info "Creating ISO image..."
    local _qflag=""
306
    if [[ "${quiet}" == "y" ]]; then
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
        xorriso -as mkisofs -quiet \
            -iso-level 3 \
            -full-iso9660-filenames \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by mkarchiso" \
            -eltorito-boot isolinux/isolinux.bin \
            -eltorito-catalog isolinux/boot.cat \
            -no-emul-boot -boot-load-size 4 -boot-info-table \
            -isohybrid-mbr "${work_dir}/iso/isolinux/isohdpfx.bin" \
            "${_iso_efi_boot_args[@]}" \
            -output "${out_dir}/${img_name}" \
            "${work_dir}/iso/"
    else
        xorriso -as mkisofs \
            -iso-level 3 \
            -full-iso9660-filenames \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by mkarchiso" \
            -eltorito-boot isolinux/isolinux.bin \
            -eltorito-catalog isolinux/boot.cat \
            -no-emul-boot -boot-load-size 4 -boot-info-table \
            -isohybrid-mbr "${work_dir}/iso/isolinux/isohdpfx.bin" \
            "${_iso_efi_boot_args[@]}" \
            -output "${out_dir}/${img_name}" \
            "${work_dir}/iso/"
336
    fi
337
    _msg_info "Done! | $(ls -sh "${out_dir}/${img_name}")"
338
339
}

340
# create airootfs.sfs filesystem, and push it in "iso" directory.
341
342
343
344
command_prepare () {
    _show_config prepare

    _cleanup
345
    if [[ "${sfs_mode}" == "sfs" ]]; then
346
347
348
349
        _mkairootfs_sfs
    else
        _mkairootfs_img
    fi
350
    _mkchecksum
351
    if [[ "${gpg_key}" ]]; then
Gerardo Exequiel Pozzi's avatar
Gerardo Exequiel Pozzi committed
352
353
      _mksignature
    fi
354
355
}

356
# Install packages on airootfs.
357
# A basic check to avoid double execution/reinstallation is done via hashing package names.
358
command_install () {
359
360
361
362
    if [[ ! -f "${pacman_conf}" ]]; then
        _msg_error "Pacman config file '${pacman_conf}' does not exist" 1
    fi

363
    if [[ "${#pkg_list[@]}" -eq 0 ]]; then
364
365
366
367
        _msg_error "Packages must be specified" 0
        _usage 1
    fi

368
    _show_config install
369

370
    _pacman "${pkg_list[@]}"
371
372
}

373
374
375
376
377
378
379
380
381
command_init() {
    _show_config init
    _chroot_init
}

command_run() {
    _show_config run
    _chroot_run
}
382

383
if [[ "${EUID}" -ne 0 ]]; then
384
385
386
    _msg_error "This script must be run as root." 1
fi

387
388
umask 0022

389
while getopts 'p:r:C:L:P:A:D:w:o:s:c:g:vh' arg; do
390
    case "${arg}" in
391
392
        p)
            read -r -a opt_pkg_list <<< "${OPTARG}"
393
394
            pkg_list+=("${opt_pkg_list[@]}")
            ;;
395
        r) run_cmd="${OPTARG}" ;;
396
397
398
399
400
        C) pacman_conf="${OPTARG}" ;;
        L) iso_label="${OPTARG}" ;;
        P) iso_publisher="${OPTARG}" ;;
        A) iso_application="${OPTARG}" ;;
        D) install_dir="${OPTARG}" ;;
401
402
        w) work_dir="${OPTARG}" ;;
        o) out_dir="${OPTARG}" ;;
403
        s) sfs_mode="${OPTARG}" ;;
404
        c) sfs_comp="${OPTARG}" ;;
405
        g) gpg_key="${OPTARG}" ;;
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        v) quiet="n" ;;
        h|?) _usage 0 ;;
        *)
            _msg_error "Invalid argument '${arg}'" 0
            _usage 1
            ;;
    esac
done

shift $((OPTIND - 1))

if [[ $# -lt 1 ]]; then
    _msg_error "No command specified" 0
    _usage 1
420
fi
421
422
423
command_name="${1}"

case "${command_name}" in
424
425
426
427
428
429
430
431
    init)
        command_init
        ;;
    install)
        command_install
        ;;
    run)
        command_run
432
433
434
435
        ;;
    prepare)
        command_prepare
        ;;
436
437
438
    pkglist)
        command_pkglist
        ;;
439
    iso)
440
        if [[ $# -lt 2 ]]; then
441
442
443
            _msg_error "No image specified" 0
            _usage 1
        fi
444
        img_name="${2}"
445
446
447
448
449
450
451
        command_iso
        ;;
    *)
        _msg_error "Invalid command name '${command_name}'" 0
        _usage 1
        ;;
esac
452
453

# vim:ts=4:sw=4:et: