8 script_path=$(readlink -f ${0%/*})
10 cache_dir="${script_path}/cache"
11 channels_dir="${script_path}/channels"
12 isolinux_dir="${script_path}/isolinux"
15 mirror="http://ftp.jaist.ac.jp/pub/Linux/ubuntu/"
20 work_dir="${script_path}/work"
21 out_dir="${script_path}/out"
22 iso_label="${os_name}_${codename}_${arch}"
23 iso_publisher='Fascode Network <https://fascode.net>'
24 iso_application="${os_name} Live/Rescue CD"
25 iso_version="${codename}-$(date +%Y.%m.%d)"
26 iso_filename="${iso_name}-${iso_version}-${arch}.iso"
38 start_time="$(date +%s)"
41 if [[ "${debug}" = true ]]; then
42 local _current_time="$(date +%s)"
45 _time="$(("${_current_time}"-"${start_time}"))"
47 if [[ "${_time}" -ge 3600 ]]; then
48 echo "[$(date -d @${_time} +%H:%M.%S)]$("${script_path}/echo_color" -t 6 "[LUBS Core]")"
49 elif [[ "${_time}" -ge 60 ]]; then
50 echo "[00:$(date -d @${_time} +%M.%S)]$("${script_path}/echo_color" -t 6 "[LUBS Core]")"
52 echo "[00:00.$(date -d @${_time} +%S)] $("${script_path}/echo_color" -t 6 "[LUBS Core]")"
55 "${script_path}/echo_color" -t 6 "[LUBS Core]"
59 # Show an INFO message
63 echo "$(_msg_common) $("${script_path}/echo_color" -t 2 "Info:") ${_msg}"
66 # Show an debug message
67 # _msg_debug <message>
69 if [[ "${debug}" = true ]]; then
71 echo "$(_msg_common) $("${script_path}/echo_color" -t 3 "Debug:") ${_msg}"
75 # Show an ERROR message then exit with status
76 # _msg_error <message> <exit code>
80 echo "$(_msg_common) $("${script_path}/echo_color" -t 1 "Error:") ${_msg}"
81 if [[ ! ${_error} = 0 ]]; then
89 for mount in $(mount | awk '{print $3}' | grep $(realpath ${work_dir}) | sort -r); do
90 _msg_info "Unmounting ${mount}"
95 # Helper function to run make_*() only one time.
100 if [[ "run_bootfiles" == "$1" ]]; then
106 if [[ ! -e "${work_dir}/build.${name}" ]]; then
107 _msg_info "$(echo $name | sed "s@_@ @g") is starting."
109 if [[ "run_bootfiles" == "$1" ]]; then
115 _msg_info "$(echo $name | sed "s@_@ @g") was done!"
116 touch "${work_dir}/build.${name}"
122 for mount in "dev" "dev/pts" "proc" "sys" "run/systemd/resolve/stub-resolv.conf"; do
123 [[ "${mount}" == "run/systemd/resolve/stub-resolv.conf" ]] && mount --bind /etc/resolv.conf "${work_dir}/airootfs/${mount}" || mount --bind /${mount} "${work_dir}/airootfs/${mount}"
125 chroot "${work_dir}/airootfs" "${@}"
126 for mount in $(mount | awk '{print $3}' | grep $(realpath ${work_dir}) | sort -r); do
127 umount -fl "${mount}"
132 cd "${bootfiles_dir}"
138 run_cmd apt-get --no-install-recommends --yes install ${@}
142 # Delete the file if it exists.
143 # For directories, rm -rf is used.
144 # If the file does not exist, skip it.
145 # remove <file> <file> ...
150 for _file in "${_list[@]}"; do
151 _msg_debug "Removeing ${_file}"
152 if [[ -f ${_file} ]]; then
154 elif [[ -d ${_file} ]]; then
162 echo "usage ${0} [options] [channel]"
164 echo " General options:"
166 echo " -a | --arch <str> Set architecture"
167 echo " Default: ${arch}"
168 echo " -c | --codename <str> Set ubuntu codename"
169 echo " Default: ${codename}"
170 echo " -m | --mirror <url> Set apt mirror server."
171 echo " Default: ${mirror}"
172 echo " -o | --out <out_dir> Set the output directory"
173 echo " Default: ${out_dir}"
174 echo " -w | --work <work_dir> Set the working directory"
175 echo " Default: ${work_dir}"
177 echo " -d | --debug "
178 echo " -h | --help This help message and exit"
180 echo "You can switch between installed packages, files included in images, etc. by channel."
186 for _channel in $(ls -l "${channels_dir}" | awk '$1 ~ /d/ {print $9 }'); do
187 if [[ -n $(ls "${channels_dir}/${_channel}") ]] && [[ ! "${_channel}" = "share" ]]; then
188 channel_list="${channel_list[@]} ${_channel}"
191 for _channel in ${channel_list[@]}; do
192 if [[ -f "${channels_dir}/${_channel}/description.txt" ]]; then
193 description=$(cat "${channels_dir}/${_channel}/description.txt")
195 description="This channel does not have a description.txt."
197 echo -ne " ${_channel}"
198 for i in $( seq 1 $(( 23 - ${#_channel} )) ); do
201 echo -ne "${description}\n"
207 if [[ ${EUID} -ne 0 ]]; then
208 _msg_error "This script must be run as root." 1
211 [[ ! -d "${work_dir}" ]] && mkdir -p "${work_dir}"
212 [[ ! -d "${out_dir}" ]] && mkdir -p "${out_dir}"
217 if [[ -z $(grep -h -v ^'#' ${channels_dir}/${channel_name}/codename.${arch} | grep -x ${codename}) ]]; then
218 _msg_error "This codename (${channel_name}) is not supported on this channel (${codename})."
225 local debootstrap_status
226 statusfile="${cache_dir}/${codename}/status"
228 debootstrap_status() {
229 if [[ ! -d "$(dirname ${statusfile})" ]]; then
230 mkdir -p "$(dirname ${statusfile})"
232 echo "${1}" > "${statusfile}"
235 if [[ -f "${statusfile}" ]] && [[ $(cat "${statusfile}" 2> /dev/null) = "Done" ]]; then
236 _msg_info "${codename} cache is found."
238 remove "${cache_dir}/${codename}"
239 debootstrap_status "Running"
240 _msg_info "Installing Ubuntu to '${cache_dir}/${codename}/airootfs'..."
241 mkdir -p "${cache_dir}/${codename}/airootfs"
242 debootstrap --arch=${arch} --include=linux-image-generic --verbose --merged-usr "${codename}" "${cache_dir}/${codename}/airootfs" ${mirror}
243 _msg_info "${codename} installed successfully!"
244 debootstrap_status "Done"
246 if [[ "${cache_only}" = true ]]; then
250 rm -rf "${work_dir}/airootfs" && mkdir -p "${work_dir}/airootfs"
251 _msg_info "copy base files from '${cache_dir}/${codename}/airootfs' to '${work_dir}/airootfs'..."
252 rsync -au "${cache_dir}/${codename}/airootfs/" "${work_dir}/airootfs"
253 echo 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}' >> "${work_dir}/airootfs/etc/bash.bashrc"
254 run_cmd apt-get update
255 # run_cmd apt-get upgrade
259 cp ${script_path}/source.list.d/${codename}/* ${work_dir}/airootfs/etc/apt
260 run_cmd apt-get update
262 if [[ -n $(find ${channels_dir}/*/repo -type f) ]]; then
265 for repo in $(find ${channels_dir}/*/repo -name *.list); do
266 key="$(dirname ${repo})/$(basename ${repo} | sed "s/list/key/")"
268 if [[ -f "${key}" ]]; then
269 if $(file ${key} | grep -q "PGP/GPG key"); then
270 cp ${key} ${work_dir}/airootfs/$(basename ${key})
272 wget -q -O ${work_dir}/airootfs/$(basename ${key}) $(cat ${key})
275 run_cmd apt-key add $(basename ${key})
276 rm ${work_dir}/airootfs/$(basename ${key})
277 cp ${repo} ${work_dir}/airootfs/etc/apt/sources.list.d
281 run_cmd apt-get update
289 PPA_FILELIST=("${channels_dir}/share/ppa_list.${arch}" "${channels_dir}/${channel_name}/ppa_list.${arch}")
290 _apt_install software-properties-common
291 for _PPA_FILE in ${PPA_FILELIST[@]}; do
292 if [[ -f "${_PPA_FILE}" ]]; then
293 for _ppa in $(grep -h -v ^'#' ${_PPA_FILE}); do
294 run_cmd add-apt-repository --yes "${_ppa}"
301 _apt_install systemd-sysv
304 make_apt_packages() {
305 remove "${work_dir}/airootfs/aptpkglist"
307 if [[ -f "${channels_dir}/share/packages.${arch}" ]]; then
308 grep -h -v ^'#' "${channels_dir}/share/packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/aptpkglist"
310 if [[ -f "${channels_dir}/${channel_name}/packages.${arch}" ]]; then
311 grep -h -v ^'#' "${channels_dir}/${channel_name}/packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/aptpkglist"
313 if [[ -n "$(echo $(<"${work_dir}/airootfs/aptpkglist"))" ]]; then
314 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends --yes install $(echo $(<aptpkglist))'
316 remove "${work_dir}/airootfs/aptpkglist"
319 make_snap_packages() {
320 remove "${work_dir}/airootfs/snappkglist"
322 if [[ -f "${channels_dir}/share/snap-packages.${arch}" ]]; then
323 grep -h -v ^'#' "${channels_dir}/share/snap-packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/snappkglist"
325 if [[ -f "${channels_dir}/${channel_name}/snap-packages.${arch}" ]]; then
326 grep -h -v ^'#' "${channels_dir}/${channel_name}/snap-packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/snappkglist"
328 if [[ -n "$(echo $(<"${work_dir}/airootfs/snappkglist"))" ]]; then
330 run_cmd env -i bash -c 'snap install $(echo $(<snappkglist))'
332 remove "${work_dir}/airootfs/snappkglist"
337 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8 LC_ALL=C LANGUAGE=en_US.UTF-8 dpkg-reconfigure locales'
340 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive dpkg-reconfigure resolvconf'
343 cp ${channels_dir}/share/airootfs/etc/NetworkManager/NetworkManager.conf ${work_dir}/airootfs/etc/NetworkManager/NetworkManager.conf
346 #run_cmd echo -ne "UTC" > '/etc/timezone'
347 #run_cmd dpkg-reconfigure -f noninteractive tzdata
349 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive dpkg-reconfigure network-manager'
350 run_cmd truncate -s 0 /etc/machine-id
353 make_customize_airootfs() {
354 #-- Overwrite airootfs files --#
358 if [[ -d "${_dir}" ]]; then
359 cp -af "${_dir}"/* "${work_dir}/airootfs"
362 copy_airootfs "${channels_dir}/share/airootfs"
363 copy_airootfs "${channels_dir}/${channel_name}/airootfs"
367 local _customize_siript_options="-a ${arch} -u '${username}' -p '${password}' -s '${usershell}'"
368 if ${bash_debug}; then
369 _customize_siript_options="$_customize_siript_options -x"
371 _customize_siript_options="$_customize_siript_options -d"
374 # run_customize_script <script path from lubs> <script path in chroot>
375 local run_customize_script
376 run_customize_script() {
377 if [[ -f "${1}" ]]; then
379 run_cmd "${2} ${_customize_siript_options}"
382 run_customize_script "${work_dir}/airootfs/root/customize_airootfs.sh" "/root/customize_airootfs.sh"
383 run_customize_script "${work_dir}/airootfs/root/customize_airootfs_${channel_name}.sh" "/root/customize_airootfs_${channel_name}.sh"
387 run_cmd apt-get clean
388 run_cmd apt-get --yes autoremove
389 run_cmd rm -rf "/tmp/* ~/.bash_history"
393 run_cmd env -i bash -c 'update-initramfs -c -k all'
394 _apt_install memtest86+
395 [[ -d "${bootfiles_dir}" ]] && rm -r "${bootfiles_dir}"
396 mkdir -p ${bootfiles_dir}/{casper,isolinux,install}
397 cp "${work_dir}/airootfs/boot/vmlinuz" "${bootfiles_dir}/casper/vmlinuz"
398 cp "${work_dir}/airootfs/boot/initrd.img" "${bootfiles_dir}/casper/initrd"
399 cp "${work_dir}/airootfs/boot/memtest86+.bin" "${bootfiles_dir}/install/memtest86+"
401 if [[ ! -f "${cache_dir}/memtest86-usb.zip" ]]; then
402 wget -O ${cache_dir}/memtest86-usb.zip https://www.memtest86.com/downloads/memtest86-usb.zip
405 (unzip -p ${cache_dir}/memtest86-usb.zip memtest86-usb.img > ${bootfiles_dir}/install/memtest86)
409 touch "${bootfiles_dir}/ubuntu"
410 cp ${isolinux_dir}/grub.cfg ${bootfiles_dir}/isolinux/grub.cfg
414 run_cmd dpkg-query -W --showformat='${Package} ${Version}\n' | tee ${bootfiles_dir}/casper/filesystem.manifest
415 cp -v ${bootfiles_dir}/casper/filesystem.manifest "${bootfiles_dir}/casper/filesystem.manifest-desktop"
416 sed -i '/ubiquity/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
417 sed -i '/casper/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
418 sed -i '/discover/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
419 sed -i '/laptop-detect/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
420 sed -i '/os-prober/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
424 mksquashfs "${work_dir}/airootfs" "${bootfiles_dir}/casper/filesystem.squashfs"
425 printf $(du -sx --block-size=1 "${work_dir}/airootfs" | cut -f1) > ${bootfiles_dir}/casper/filesystem.size
429 cp ${isolinux_dir}/README.diskdefines ${bootfiles_dir}/README.diskdefines
433 if [[ "${arch}" = "amd64" ]]; then
436 local _arch="${arch}"
439 --format=${_arch}-efi \
440 --output=isolinux/bootx64.efi \
443 "boot/grub/grub.cfg=isolinux/grub.cfg"
446 dd if=/dev/zero of=efiboot.img bs=1M count=10 && \
447 sudo mkfs.vfat efiboot.img && \
448 LC_CTYPE=C mmd -i efiboot.img efi efi/boot && \
449 LC_CTYPE=C mcopy -i efiboot.img ./bootx64.efi ::efi/boot/
453 --output=isolinux/core.img \
454 --install-modules="linux16 linux normal iso9660 biosdisk memdisk search tar ls" \
455 --modules="linux16 linux normal iso9660 biosdisk search" \
459 "boot/grub/grub.cfg=isolinux/grub.cfg"
460 cat /usr/lib/grub/i386-pc/cdboot.img isolinux/core.img > isolinux/bios.img
464 /bin/bash -c "(find . -type f -print0 | xargs -0 md5sum | grep -v "\./md5sum.txt" > md5sum.txt)"
471 -full-iso9660-filenames \
472 -volid "${iso_label}" \
473 -appid "${iso_application}" \
474 -publisher "${iso_publisher}" \
475 -preparer "prepared by LUBS" \
476 -eltorito-boot boot/grub/bios.img \
480 --eltorito-catalog boot/grub/boot.cat \
482 --grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img \
486 -append_partition 2 0xef isolinux/efiboot.img \
487 -output "${out_dir}/${iso_filename}" \
490 /boot/grub/bios.img=isolinux/bios.img \
491 /EFI/efiboot.img=isolinux/efiboot.img
496 _msg_info "Creating md5 checksum ..."
497 md5sum "${iso_filename}" > "${iso_filename}.md5"
499 _msg_info "Creating sha256 checksum ..."
500 sha256sum "${iso_filename}" > "${iso_filename}.sha256"
501 cd - > /dev/null 2>&1
506 # 参考記事:https://0e0.pw/ci83 https://0e0.pw/VJlg
508 _opt_short="w:o:ha:-:m:c:d"
509 _opt_long="help,arch:,codename:,debug,help,mirror:,out:,work,cache-only"
510 OPT=$(getopt -o ${_opt_short} -l ${_opt_long} -- "${@}")
511 if [[ ${?} != 0 ]]; then
518 if [[ -z ${2} ]]; then
519 _msg_error "Please specify the architecture."
527 if [[ -z ${2} ]]; then
528 _msg_error "Please specify the codename."
544 if [[ -z ${2} ]]; then
545 _msg_error "Please specify the mirror server."
553 if [[ -z ${2} ]]; then
554 _msg_error "Please specify the out dir."
560 if [[ -z ${2} ]]; then
561 _msg_error "Please specify the out dir."
577 _msg_error "Invalid argument '${1}'"
583 bootfiles_dir="${work_dir}/bootfiles"
584 trap umount_chroot 0 2 15
586 if [[ -n "${1}" ]]; then
593 for _channel in $(ls -l "${channels_dir}" | awk '$1 ~ /d/ {print $9 }'); do
594 if [[ -n $(ls "${channels_dir}/${_channel}") ]] && [[ ! "${_channel}" = "share" ]]; then
595 channel_list="${channel_list[@]} ${_channel}"
598 for i in ${channel_list[@]}; do
599 if [[ "${i}" = "${channel_name}" ]]; then
608 if [[ $(check_channel ${channel_name}) = false ]]; then
609 _msg_error "Invalid channel ${channel_name}"
617 run_once make_sourcelist
618 run_once make_systemd
619 run_once make_apt_packages
620 #run_once make_snap_packages
622 run_once make_customize_airootfs
624 run_once make_bootfiles
625 run_once make_grubcfg
626 run_once make_manifest
627 run_once make_squashfs
628 run_once make_deifnes
629 run_once run_bootfiles make_isolinux
630 run_once run_bootfiles make_md5sum
631 run_once run_bootfiles make_iso
632 run_once make_checksum