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 sed -i "s@http://archive.ubuntu.com/ubuntu/@${mirror}@g" ${work_dir}/airootfs/etc/apt/sources.list
261 run_cmd apt-get update
263 if [[ -n $(find ${channels_dir}/*/repo -type f) ]]; then
266 for repo in $(find ${channels_dir}/*/repo -name *.list); do
267 key="$(dirname ${repo})/$(basename ${repo} | sed "s/list/key/")"
269 if [[ -f "${key}" ]]; then
270 if $(file ${key} | grep -q "PGP/GPG key"); then
271 cp ${key} ${work_dir}/airootfs/$(basename ${key})
273 wget -q -O ${work_dir}/airootfs/$(basename ${key}) $(cat ${key})
276 run_cmd apt-key add $(basename ${key})
277 rm ${work_dir}/airootfs/$(basename ${key})
278 cp ${repo} ${work_dir}/airootfs/etc/apt/sources.list.d
282 run_cmd apt-get update
290 PPA_FILELIST=("${channels_dir}/share/ppa_list.${arch}" "${channels_dir}/${channel_name}/ppa_list.${arch}")
291 _apt_install software-properties-common
292 for _PPA_FILE in ${PPA_FILELIST[@]}; do
293 if [[ -f "${_PPA_FILE}" ]]; then
294 for _ppa in $(grep -h -v ^'#' ${_PPA_FILE}); do
295 run_cmd add-apt-repository --yes "${_ppa}"
302 _apt_install systemd-sysv
303 run_cmd dbus-uuidgen --ensure=/etc/machine-id
304 run_cmd ln -sf /etc/machine-id /var/lib/dbus/machine-id
307 make_apt_packages() {
308 remove "${work_dir}/airootfs/aptpkglist"
310 if [[ -f "${channels_dir}/share/packages.${arch}" ]]; then
311 grep -h -v ^'#' "${channels_dir}/share/packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/aptpkglist"
313 if [[ -f "${channels_dir}/${channel_name}/packages.${arch}" ]]; then
314 grep -h -v ^'#' "${channels_dir}/${channel_name}/packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/aptpkglist"
316 if [[ -n "$(echo $(<"${work_dir}/airootfs/aptpkglist"))" ]]; then
317 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends --yes install $(echo $(<aptpkglist))'
319 remove "${work_dir}/airootfs/aptpkglist"
322 make_snap_packages() {
323 remove "${work_dir}/airootfs/snappkglist"
325 if [[ -f "${channels_dir}/share/snap-packages.${arch}" ]]; then
326 grep -h -v ^'#' "${channels_dir}/share/snap-packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/snappkglist"
328 if [[ -f "${channels_dir}/${channel_name}/snap-packages.${arch}" ]]; then
329 grep -h -v ^'#' "${channels_dir}/${channel_name}/snap-packages.${arch}" | grep -v "^$" >> "${work_dir}/airootfs/snappkglist"
331 if [[ -n "$(echo $(<"${work_dir}/airootfs/snappkglist"))" ]]; then
333 run_cmd env -i bash -c 'snap install $(echo $(<snappkglist))'
335 remove "${work_dir}/airootfs/snappkglist"
340 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'
343 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive dpkg-reconfigure resolvconf'
346 cp ${channels_dir}/share/airootfs/etc/NetworkManager/NetworkManager.conf ${work_dir}/airootfs/etc/NetworkManager/NetworkManager.conf
349 #run_cmd echo -ne "UTC" > '/etc/timezone'
350 #run_cmd dpkg-reconfigure -f noninteractive tzdata
352 run_cmd env -i bash -c 'DEBIAN_FRONTEND=noninteractive dpkg-reconfigure network-manager'
353 run_cmd truncate -s 0 /etc/machine-id
356 make_customize_airootfs() {
357 #-- Overwrite airootfs files --#
361 if [[ -d "${_dir}" ]]; then
362 cp -af "${_dir}"/* "${work_dir}/airootfs"
365 copy_airootfs "${channels_dir}/share/airootfs"
366 copy_airootfs "${channels_dir}/${channel_name}/airootfs"
370 local _customize_siript_options="-a ${arch} -u '${username}' -p '${password}' -s '${usershell}'"
371 if ${bash_debug}; then
372 _customize_siript_options="$_customize_siript_options -x"
374 _customize_siript_options="$_customize_siript_options -d"
377 # run_customize_script <script path from lubs> <script path in chroot>
378 local run_customize_script
379 run_customize_script() {
380 if [[ -f "${1}" ]]; then
382 run_cmd "${2} ${_customize_siript_options}"
385 run_customize_script "${work_dir}/airootfs/root/customize_airootfs.sh" "/root/customize_airootfs.sh"
386 run_customize_script "${work_dir}/airootfs/root/customize_airootfs_${channel_name}.sh" "/root/customize_airootfs_${channel_name}.sh"
390 sed -i "s@${mirror}@http://archive.ubuntu.com/ubuntu/@g" ${work_dir}/airootfs/etc/apt/sources.list
391 run_cmd apt-get update
392 run_cmd apt-get clean
393 run_cmd apt-get --yes autoremove
394 run_cmd rm -rf "/tmp/* ~/.bash_history"
398 run_cmd env -i bash -c 'update-initramfs -c -k all'
399 _apt_install memtest86+
400 [[ -d "${bootfiles_dir}" ]] && rm -r "${bootfiles_dir}"
401 mkdir -p ${bootfiles_dir}/{casper,isolinux,install}
402 cp "${work_dir}/airootfs/boot/vmlinuz" "${bootfiles_dir}/casper/vmlinuz"
403 cp "${work_dir}/airootfs/boot/initrd.img" "${bootfiles_dir}/casper/initrd"
404 cp "${work_dir}/airootfs/boot/memtest86+.bin" "${bootfiles_dir}/install/memtest86+"
406 if [[ ! -f "${cache_dir}/memtest86-usb.zip" ]]; then
407 wget -O ${cache_dir}/memtest86-usb.zip https://www.memtest86.com/downloads/memtest86-usb.zip
410 (unzip -p ${cache_dir}/memtest86-usb.zip memtest86-usb.img > ${bootfiles_dir}/install/memtest86)
414 touch "${bootfiles_dir}/ubuntu"
415 cp ${isolinux_dir}/grub.cfg ${bootfiles_dir}/isolinux/grub.cfg
419 run_cmd dpkg-query -W --showformat='${Package} ${Version}\n' | tee ${bootfiles_dir}/casper/filesystem.manifest
420 cp -v ${bootfiles_dir}/casper/filesystem.manifest "${bootfiles_dir}/casper/filesystem.manifest-desktop"
421 sed -i '/ubiquity/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
422 sed -i '/casper/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
423 sed -i '/discover/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
424 sed -i '/laptop-detect/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
425 sed -i '/os-prober/d' "${bootfiles_dir}/casper/filesystem.manifest-desktop"
429 mksquashfs "${work_dir}/airootfs" "${bootfiles_dir}/casper/filesystem.squashfs"
430 printf $(du -sx --block-size=1 "${work_dir}/airootfs" | cut -f1) > ${bootfiles_dir}/casper/filesystem.size
434 cp ${isolinux_dir}/README.diskdefines ${bootfiles_dir}/README.diskdefines
438 if [[ "${arch}" = "amd64" ]]; then
441 local _arch="${arch}"
444 --format=${_arch}-efi \
445 --output=isolinux/bootx64.efi \
448 "boot/grub/grub.cfg=isolinux/grub.cfg"
451 dd if=/dev/zero of=efiboot.img bs=1M count=10 && \
452 sudo mkfs.vfat efiboot.img && \
453 LC_CTYPE=C mmd -i efiboot.img efi efi/boot && \
454 LC_CTYPE=C mcopy -i efiboot.img ./bootx64.efi ::efi/boot/
458 --output=isolinux/core.img \
459 --install-modules="linux16 linux normal iso9660 biosdisk memdisk search tar ls" \
460 --modules="linux16 linux normal iso9660 biosdisk search" \
464 "boot/grub/grub.cfg=isolinux/grub.cfg"
465 cat /usr/lib/grub/i386-pc/cdboot.img isolinux/core.img > isolinux/bios.img
469 /bin/bash -c "(find . -type f -print0 | xargs -0 md5sum | grep -v "\./md5sum.txt" > md5sum.txt)"
476 -full-iso9660-filenames \
477 -volid "${iso_label}" \
478 -appid "${iso_application}" \
479 -publisher "${iso_publisher}" \
480 -preparer "prepared by LUBS" \
481 -eltorito-boot boot/grub/bios.img \
485 --eltorito-catalog boot/grub/boot.cat \
487 --grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img \
491 -append_partition 2 0xef isolinux/efiboot.img \
492 -output "${out_dir}/${iso_filename}" \
495 /boot/grub/bios.img=isolinux/bios.img \
496 /EFI/efiboot.img=isolinux/efiboot.img
501 _msg_info "Creating md5 checksum ..."
502 md5sum "${iso_filename}" > "${iso_filename}.md5"
504 _msg_info "Creating sha256 checksum ..."
505 sha256sum "${iso_filename}" > "${iso_filename}.sha256"
506 cd - > /dev/null 2>&1
511 # 参考記事:https://0e0.pw/ci83 https://0e0.pw/VJlg
513 _opt_short="w:o:ha:-:m:c:d"
514 _opt_long="help,arch:,codename:,debug,help,mirror:,out:,work,cache-only"
515 OPT=$(getopt -o ${_opt_short} -l ${_opt_long} -- "${@}")
516 if [[ ${?} != 0 ]]; then
523 if [[ -z ${2} ]]; then
524 _msg_error "Please specify the architecture."
532 if [[ -z ${2} ]]; then
533 _msg_error "Please specify the codename."
549 if [[ -z ${2} ]]; then
550 _msg_error "Please specify the mirror server."
558 if [[ -z ${2} ]]; then
559 _msg_error "Please specify the out dir."
567 if [[ -z ${2} ]]; then
568 _msg_error "Please specify the out dir."
584 _msg_error "Invalid argument '${1}'"
590 bootfiles_dir="${work_dir}/bootfiles"
591 trap umount_chroot 0 2 15
593 if [[ -n "${1}" ]]; then
600 for _channel in $(ls -l "${channels_dir}" | awk '$1 ~ /d/ {print $9 }'); do
601 if [[ -n $(ls "${channels_dir}/${_channel}") ]] && [[ ! "${_channel}" = "share" ]]; then
602 channel_list="${channel_list[@]} ${_channel}"
605 for i in ${channel_list[@]}; do
606 if [[ "${i}" = "${channel_name}" ]]; then
615 if [[ $(check_channel ${channel_name}) = false ]]; then
616 _msg_error "Invalid channel ${channel_name}"
624 run_once make_sourcelist
625 run_once make_systemd
626 run_once make_apt_packages
627 #run_once make_snap_packages
629 run_once make_customize_airootfs
631 run_once make_bootfiles
632 run_once make_grubcfg
633 run_once make_manifest
634 run_once make_squashfs
635 run_once make_deifnes
636 run_once run_bootfiles make_isolinux
637 run_once run_bootfiles make_md5sum
638 run_once run_bootfiles make_iso
639 run_once make_checksum