數位防災包
網路時代最怕手機電腦無法連上網,讀不到在雲端的資料不太妙,要帶電腦逃難不如帶硬碟,但問題來了,我已安裝的軟體、寫的程式無法直接插在別人的電腦直接用,尤其是Windows用戶感受更深!那麼,要如何準備數位防災包呢?

萬一將來哪天什麼不幸的事發生了,防災包內的東東可以讓我們一段時日維持基本的需求,裡面該裝些什麼,大家已有許多討論了;資產和錢錢要怎麼避險,放在哪裡較好?我想也有許多人在做了,想起那句經典名言:投資基金有賺有賠,購買前請詳閱公開說明書。

沒網路就讀不到

以上那些資產,我沒有多到要煩惱,不過電腦裡倒是有不少無形的資產,這陣子在慢慢一個個整理並放上網,這些大多是這幾年的服事感想以及一些生活的小記事,除了保存資料,也是跟大家分享。話說,現在很流行把東東放在雲端,因為有網路即可讀取,還可以大家共同編輯文件,不會占用手機或是電腦的硬碟空間更是令人感到輕省,不過,萬一哪天網路斷線了,雲端的東東可是連讀都讀不到喔!當然也無法編輯,所以,重要的資料一定要在本機備份後再上傳網路空間。

(上上幾代的固態硬碟2.5吋SSD堅固耐用,外有硬殼且方便好攜帶。傳輸速度不比現在的硬碟,不過傳資料也夠用了。記得挑內建DRAM的,速度會比較穩)
(上上幾代的固態硬碟2.5吋SSD堅固耐用,外有硬殼且方便好攜帶。傳輸速度不比現在的硬碟,不過傳資料也夠用了。記得挑內建DRAM的,速度會比較穩)

帶顆巴掌大的硬碟

萬一哪天要避難了,帶糧食和水就很辛苦了,總不可能再搬一台電腦四處跑,所以,能帶的就是記錄許多東東的硬碟了,不過問題來了,我無法完全地、100%把自己硬碟上的所有東東拿到別人的電腦上去使用,這什麼意思?我硬碟裡的文件、投影片、試算表、影片、音樂…等等,只要對方有相對應的軟體就可以了開啟,如果沒有,就沒輒了!例如別人跟微軟(Microsoft Office)的合約到期不給開.docx檔之類的文件,就算我的硬碟有安裝,一外接到他的電腦去也無法開啟 (LibreOffice就沒到期的問題),換言之,在別人的電腦上,我無法使用自己安裝的文書軟體來開文件!除非用綠色版 (Portable可攜式)的軟體。文書軟體是其中之一,我今天安裝的所有其它軟體只要離開了原本的電腦主機板,就只能望著一堆檔案卻什麼都不能做。

若要開機呼叫硬碟裡的作業系統來跑軟體呢?很抱歉,這就是問題的所在,不同電腦的硬體差異 (尤其晶片組、開機模式、驅動程式) 會使原來的作業系統無法接到別台電腦開機或正常運作。

真 · 行動硬碟

今天,數位防災包正是要做一顆能開機的行動硬碟,它外接到任何一台電腦都可以使用,如同在自己電腦上跑任何已安裝的軟體一樣!這不是普通的 Live USB,而是完整、可更新、可升級、可帶著所有設定與軟體走的真 · Linux系統行動硬碟,要打造把自己舉起來的行動硬碟(Self-Booting Disk),必須先調整一些地方,在此之前,我先整理一下它的威力:

功能 說明
🔌 可攜開機 插上任何電腦都能直接開機(支援 BIOS + UEFI)
🧠 完整系統 不是 Live 模式,而是完整安裝版,有自己的 /, /home, /boot, /efi
💾 資料、設定、軟體全保留 瀏覽器、程式、AI 模型、音樂工程、文件全部跟著走
🧱 完全獨立 不讀不寫主機內部的其它硬碟
🛠️ 可更新、升級 一樣能 apt update && apt upgrade,跟一般內裝系統無差別
🔐 自帶 bootloader 自己的 GRUB + kernelstub,到哪台機器都能開機
🧰 具備 BIOS + UEFI 開機雙保險 舊筆電、新筆電全通吃
支援 GPU / CUDA / AI 工具 RTX 4060等顯卡都能發揮(安裝 NVIDIA 驅動後自動偵測)

與一般 USB Live 系統不同之處

項目 真行動系統硬碟 傳統 Live USB
可寫入 ✅ 可保存資料、設定 ❌ 重開後消失
可更新 ✅ apt upgrade 可用 ❌ 不建議更新
可安裝新軟體 ✅ 完整功能 ⚠️ 需持久層才行
開機模式 BIOS + UEFI 通常僅 UEFI
獨立性 ✅ 不依附任何電腦 ⚠️ 有時改寫主機 EFI
壽命 SSD/NVMe 長久耐用 USB 快閃碟壽命有限

硬碟不能四處開機的原因:

電腦在安裝作業系統時,除了會把開機程式寫入硬碟,也會在主機板的 UEFI 區裡那塊 NVRAM 登記一筆啟動記錄,當電腦啟動時,UEFI 會依這筆記錄去尋找對應硬碟的開機檔案,若這顆硬碟被拔走,主機板找不到那筆啟動資訊,自然就無法開機。

今天,我們要做的,就是把所有開機資訊都寫在硬碟自己裡面,讓它不再完全依賴主機板。為了避免遇到舊型 BIOS 電腦開不起來,我同時也安裝了 BIOS 版的開機程式,這樣一來,不論是舊機還是新機,通通都能開。

😀 Linux 沒這困擾

Linux並不像Windows把硬碟綁定各個硬體ID,一離開原來安裝的硬體就無法用了,Linux的核心 (kernel) 是隨機偵測硬體、動態載入模組,所以,顯示卡換了會自動下載新的驅動;網卡、音效卡幾乎能自動辨識;主機板和CPU更不用說了,內建自動偵測型號和功能集,因為如此,安裝Linux作業系統是帶著硬碟四處跑,到哪都可以裝到別台電腦上近乎完整使用的防災包首選,沒有之一。

問題來源 沒做設定時的情況 結果
🧱 開機程式 (bootloader) 預設安裝到當前內建硬碟 外接硬碟沒有對上主機板的開機碼,所以拔到別台無法啟動
⚙️ UEFI 指向錯誤 EFI 區只登錄在原先那台主機 BIOS 的 NVRAM 換主機後找不到「開機項目」,變成黑畫面或 No bootable device
💾 BIOS 模式未安裝 系統只裝了 grub-efi(UEFI專用),沒有 grub-pc 舊電腦(Legacy BIOS)完全無法啟動
🧠 os-prober 未關 GRUB 偵測到舊主機的開機項 換一台主機後就掛了:選單中那些項目路徑失效,開不了機

一鍵完成的設定腳本

以下指令都有附說明,Linux有趣的地方就像玩積木,可以把軟體做各種變化和組合,以及寫腳本方便再次使用或者給別人用,執行只要三步驟而已。

  1. 把指令存檔並命名portable_boot.sh

  2. 給權限chmod +x portable_boot.sh

  3. 然後在.sh腳本所在的目錄執行./portable_boot.sh

這是與AI來回討論後得到的腳本,簡介客製化的特性以及指令做了哪些事。

💡 成品特性

  • 插哪台電腦都能開(BIOS / UEFI 皆可)
  • 不依賴主機板 NVRAM
  • 不影響其它硬碟
  • 可備份、可還原、可更新
  • 真 · 數位防災行動系統
  • 外接盒採用常見的 USB 3.0 介面,理論速度幾乎逼近 2.5 吋 SSD 的上限,實用又方便且不浪費每一分速度。

這支 portable_boot.sh 的需求

  • 必備:ESP(UEFI)
    • 腳本只會「使用」硬碟的ESP,不會自動建立
    • 需要先在分割硬碟時預留大約300–600 MB 的 FAT32 分割區,並設 boot, esp 旗標(常見的大小設為 512 MB)。
  • 可選:bios_grub(BIOS)
    • 雖然腳本會自動補 2 MiB 的 bios_grub 分割(只限 GPT格式),但最好還是先預留bios_grub分割區,自動補的前提是磁碟開頭或尾端有未分配空間。
    • 若整顆磁碟已滿、沒有任何 free space,腳本就沒辦法補;你需先用 GParted 挪出 ≥ 2 MiB 的空白。

簡言之:要先準備ESP;腳本可自動補bios_grub,但要有 2 MiB 空白處且磁碟是 GPT格式。

🗡️ 這支腳本很周到

目的

讓任何 Linux 發行版(Debian / Ubuntu / Pop!_OS …等)能被轉製成「可攜、獨立、雙開機(UEFI + BIOS)」的行動硬碟,並附加完整的 備份 / 還原 / 修復 / 選單互動操作

🧭 必要步驟與條件

🔍 1. 磁碟偵測與確認

  • 自動列出所有可用硬碟與分割表(lsblk + parted)
  • 使用者可互動選擇目標硬碟
  • 自動判斷:
    • GPT 或 MBR 分割表
    • Root(系統)分割區
    • EFI(FAT32, boot, esp)
    • BIOS boot 分割區(bios_grub)
    • /home 是否獨立分割
  • /boot/efi 沒掛載,會詢問或自動掛載

🧱 2. 檢查 / 建立 BIOS Boot 分割區(bios_grub)

  • 若磁碟為 GPT 且無 bios_grub:

    1. 先嘗試在磁碟開頭(1MiB–3MiB)建立 2MiB bios_grub
    2. 若開頭已佔滿,再回頭偵測尾端是否有 ≥2MiB 空間可用
    3. 若空間足夠,自動建立尾端 bios_grub 分割
  • 為分割設定旗標:

    parted /dev/sdX set N bios_grub on

🧩 3. 確保 EFI 系統分割(ESP)存在

  • 若偵測不到 FAT32 boot,esp:

    • 提示使用 GParted 建立一個 512MiB~1GiB FAT32 分割區

    • 自動設定:

      parted /dev/sdX set N boot on
      parted /dev/sdX set N esp on
  • 自動掛載至 /mnt/boot/efi/boot/efi

⚙️ 4. 系統掛載與 chroot 準備

  • 將整個目標系統掛載到 /mnt

  • 綁定 /dev /proc /sys /run

  • 關閉宿主機的自動更新服務:

    systemctl stop packagekit apt-daily.service apt-daily-upgrade.service
  • 進入 chroot 內部操作(確保環境一致性)

💾 5. 安裝必要工具

在 chroot 內執行:

apt-get update
apt-get install -y \
  grub-pc grub-pc-bin \
  grub-efi-amd64 grub-efi-amd64-bin shim-signed os-prober

若為 Pop!_OS,還會偵測並執行 kernelstub --force 以確保內核與 initrd 被複製進 ESP。

🚀 6. 安裝 GRUB(雙開機)

BIOS 開機碼:

grub-install --target=i386-pc --boot-directory=/boot /dev/sdX

UEFI 開機碼:

grub-install --target=x86_64-efi \
  --efi-directory=/boot/efi --boot-directory=/boot \
  --recheck --removable

--removable 是關鍵,確保即使主機板沒記錄此硬碟的 NVRAM, 也能以 /EFI/BOOT/BOOTX64.EFI 自行啟動(真正可攜)。

🔒 7. 開機選單獨立化

  • 若要完全獨立(不列出別的硬碟系統):

    GRUB_DISABLE_OS_PROBER=true
  • 若要可切換雙系統:

    GRUB_DISABLE_OS_PROBER=false

🧩 8. 生成 GRUB 設定

update-grub  # 或 grub-mkconfig -o /boot/grub/grub.cfg

🧾 9. 驗證(BIOS + UEFI)

檢查兩項:

  1. UEFI 檔案是否存在

    ls /boot/efi/EFI/BOOT/BOOTX64.EFI
  2. MBR 是否含 GRUB 簽名

    sudo dd if=/dev/sdX bs=512 count=1 2>/dev/null | strings -a | grep GRUB

兩者皆存在 → ✅「完全獨立可攜系統」

💽 10. (可選)完整備份/還原

  • 備份項目:
    • MBR / GPT 開頭(前 1MiB)
    • 分割表(sfdisk -d 輸出)
    • ESP(FAT32)
    • /boot、GRUB 設定與 kernelstub 檔案
  • 還原時只需:
    1. 以 LiveUSB 掛載硬碟
    2. 導回 MBR + ESP + /boot
    3. 執行 grub-install + update-grub

✅ 條件與成果

條件 必要性 說明
分割表為 GPT ✅ 建議 為了能同時擁有 bios_grub 與 ESP
有 FAT32(boot,esp) ✅ 必要 UEFI 模式開機必需
有 2MiB 空間建立 bios_grub ⚙️ 可選 若想支援舊 BIOS 機器
系統能 chroot 進入 ✅ 必要 用於安裝 GRUB 與更新設定
網路可用 ⚙️ 建議 apt 安裝 grub 與 kernelstub
關閉 os-prober 🔒 建議 讓系統完全獨立、不受主機影響

portable_boot.sh 腳本

#!/usr/bin/env bash
# portable_boot.sh
# 目的:把一顆 Linux 磁碟(Debian/Ubuntu/Pop!_OS)做成「可攜、獨立、雙開(UEFI+BIOS)」。
# 功能:
#  1) 自動偵測磁碟與分割(ESP/bios_grub/root)
#  2) 若「缺 bios_grub」,在 GPT 磁碟的「開頭或結尾」自動建立 2MiB 的 BIOS Boot 分割(flags=bios_grub)
#  3) 安裝 GRUB(i386-pc + x86_64-efi),並停用 os-prober 讓選單僅保留本機系統(獨立)
#  4) 備份開機資產(MBR/GPT前置、分割表、ESP、/boot、grub、kernelstub)
#  5) 切換獨立/非獨立(關/開 os-prober)
# 使用:
#   sudo ./portable_boot.sh           # 正式執行
#   sudo ./portable_boot.sh --dry-run # 預演,不寫入

set -Eeuo pipefail
shopt -s extglob

# --- 參數:--dry-run 會預演所有步驟,不改動系統 ---
DRY=false; [[ "${1-}" == "--dry-run" ]] && DRY=true && echo "🟡 DRY-RUN:預演模式(不會寫入磁碟)"

# --- 小工具:統一跑指令並顯示 ---
RUN(){ echo "👉 $*"; $DRY || eval "$@"; }

# --- 收尾:確保掛載都卸掉,避免殘留 ---
cleanup(){
  echo "🧹 收尾卸載..."
  for d in dev proc sys run; do mountpoint -q "/mnt/$d" && umount "/mnt/$d" || true; done
  mountpoint -q /mnt/boot/efi && umount /mnt/boot/efi || true
  mountpoint -q /mnt && umount /mnt || true
}
trap cleanup EXIT

err(){ echo "❌ $*" >&2; exit 1; }

# ========== 共用輔助 ==========
list_disks(){ echo "📀 可用磁碟(僅列出 TYPE=disk):"; lsblk -d -o NAME,SIZE,MODEL,TYPE | awk '$4=="disk"{printf("  %s  %8s  %s\n",$1,$2,$3)}'; }
pick_disk(){
  list_disks
  read -rp "請輸入『目標磁碟』(例:sda 或 nvme0n1): " X
  [[ -z "${X:-}" ]] && err "未輸入磁碟"
  DISK="/dev/$X"; [[ -b "$DISK" ]] || err "找不到磁碟 $DISK"
  echo "🧭 目標磁碟:$DISK"
}
scan_parts(){
  echo "🔎 掃描分割:$DISK"
  lsblk -o NAME,SIZE,FSTYPE,PARTLABEL,PARTFLAGS,MOUNTPOINT "$DISK"
  # 偵測 ESP(vfat,且 flags 含 boot/esp)
  ESP_PART="$(lsblk -prno NAME,FSTYPE,PARTFLAGS "$DISK" | awk '/ vfat / && /boot|esp/ {print $1; exit}')"
  # 偵測 bios_grub(flags 含 bios_grub)
  BIOS_PART="$(lsblk -prno NAME,PARTFLAGS "$DISK" | awk '/bios_grub/ {print $1; exit}')"
  # 偵測 root 候選(取同顆磁碟最大的 ext4)
  ROOT_PART="$(lsblk -prbo NAME,SIZE,FSTYPE "$DISK" | awk '$3=="ext4"{print $1" "$2}' | sort -k2 -nr | head -1 | awk '{print $1}')"
  echo "   ➤ ESP(UEFI):${ESP_PART:-<未找到>}"
  echo "   ➤ BIOS區   :${BIOS_PART:-<未找到>}"
  echo "   ➤ ROOT候選 :${ROOT_PART:-<未找到>}"
  # 允許手動覆蓋
  read -rp "覆蓋 ESP(直接輸入 /dev/XXX;留空=使用偵測): " a; [[ -n "${a:-}" ]] && ESP_PART="$a"
  read -rp "覆蓋 BIOS(/dev/XXX;留空=使用偵測): " a; [[ -n "${a:-}" ]] && BIOS_PART="$a"
  read -rp "覆蓋 ROOT(/dev/XXX;留空=使用偵測): " a; [[ -n "${a:-}" ]] && ROOT_PART="$a"
  [[ -n "${ROOT_PART:-}" ]] || err "找不到 root 分割(ext4)。"
  [[ -n "${ESP_PART:-}" ]] || err "找不到 ESP(FAT32+boot,esp)。請先用 GParted 建立。"
}
ensure_mounts(){
  echo "🗂 掛載 ROOT 與 EFI(準備 chroot)"
  RUN "umount -R /mnt 2>/dev/null || true"
  RUN "mount $ROOT_PART /mnt"
  RUN "mkdir -p /mnt/boot/efi"
  RUN "mount $ESP_PART /mnt/boot/efi"
  for d in dev proc sys run; do RUN "mount --bind /$d /mnt/$d"; done
}
stop_apt_locks(){
  echo "⏸️ 停用自動更新守護程式(避免 apt/dpkg 鎖)"
  RUN "systemctl stop packagekit 2>/dev/null || true"
  RUN "systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true"
}
fix_dpkg(){
  echo "🔧 修復 dpkg/apt 狀態並更新索引"
  RUN "chroot /mnt /bin/bash -c 'dpkg --configure -a || true; apt-get -y -f install || true; apt-get update'"
}
install_grub_tools(){
  echo "📦 安裝 GRUB 工具(盡量同時準備 BIOS+UEFI;失敗則退而求其次)"
  if ! $DRY; then
    chroot /mnt /bin/bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y grub-pc grub-pc-bin grub-efi-amd64 grub-efi-amd64-bin shim-signed" || \
    chroot /mnt /bin/bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y grub-pc grub-pc-bin || true"
  else
    echo "(DRY)略過 apt 安裝"
  fi
}
disable_os_prober(){
  echo "🔒 停用 os-prober(讓 GRUB 『只顯示本機系統』,完全獨立)"
  RUN "chroot /mnt /bin/bash -c \"sed -i 's/^#\\?GRUB_DISABLE_OS_PROBER=.*/GRUB_DISABLE_OS_PROBER=true/' /etc/default/grub; grep -q GRUB_DISABLE_OS_PROBER /etc/default/grub || echo GRUB_DISABLE_OS_PROBER=true >> /etc/default/grub\""
}
enable_os_prober(){
  echo "🔓 開啟 os-prober(GRUB 會列出其他硬碟上的系統)"
  RUN "chroot /mnt /bin/bash -c \"sed -i 's/^#\\?GRUB_DISABLE_OS_PROBER=.*/GRUB_DISABLE_OS_PROBER=false/' /etc/default/grub; grep -q GRUB_DISABLE_OS_PROBER /etc/default/grub || echo GRUB_DISABLE_OS_PROBER=false >> /etc/default/grub\""
}
write_grub(){
  echo "💾 安裝 GRUB:BIOS + UEFI"
  local disk="/dev/$(lsblk -no pkname "$ROOT_PART")"
  [[ -b "$disk" ]] || err "無法推得磁碟於 $ROOT_PART"
  # BIOS(需要 bios_grub)
  if [[ -n "${BIOS_PART:-}" ]]; then
    RUN "chroot /mnt grub-install --target=i386-pc --boot-directory=/boot $disk"
  else
    echo "⚠️ 未找到 bios_grub;跳過 BIOS 安裝(僅做 UEFI)"
  fi
  # UEFI
  RUN "chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --boot-directory=/boot --recheck --removable || true"
}
run_kernelstub_if_exists(){
  echo "🧰 Pop!_OS:若有 kernelstub,強制同步 kernel/initrd 到 EFI"
  RUN "chroot /mnt /bin/bash -c 'if command -v kernelstub >/dev/null 2>&1; then kernelstub --force || true; fi'"
}
gen_grub_cfg(){
  echo "⚙️ 生成 grub.cfg"
  RUN "chroot /mnt /bin/bash -c 'update-grub || grub-mkconfig -o /boot/grub/grub.cfg'"
}

# ========== 自動建立 bios_grub(若缺) ==========
# 說明:僅在「磁碟為 GPT」且「有足夠未配置空間」時嘗試建立 2MiB 分割並設 flags=bios_grub
ensure_bios_grub(){
  # 先檢查是否已有
  if [[ -n "${BIOS_PART:-}" ]]; then
    echo "✅ 已存在 bios_grub 分割:$BIOS_PART(略過建立)"
    return 0
  fi
  echo "🧱 未偵測到 bios_grub,嘗試自動建立 2MiB BIOS Boot 分割(僅限 GPT)"

  # 檢查分割表型別
  local label; label="$(parted -s "$DISK" print | awk -F: '/Partition Table/ {gsub(/^[ \t]+/,"",$2); print $2}')"
  [[ "$label" == "gpt" ]] || err "此磁碟非 GPT(目前僅支援 GPT 自動建立 bios_grub)。請改用 GParted 建立後再跑。"

  # 取得 free 區資訊(MiB 單位)
  local free_head="" free_tail=""
  # 開頭 free:查詢 print free 第一個 free 區塊
  free_head="$(parted -m "$DISK" unit MiB print free | awk -F: 'NR>2 && $1!~/^[0-9]+$/ && /free/ {gsub(/MiB/,"",$2); gsub(/MiB/,"",$3); print $2,$3; exit}')"
  # 結尾 free:取最後一個 free 區塊
  free_tail="$(parted -m "$DISK" unit MiB print free | awk -F: 'NR>2 && /free/ {last=$0} END{if(last!=""){split(last,a,":"); gsub(/MiB/,"",a[2]); gsub(/MiB/,"",a[3]); print a[2],a[3]}}')"

  create_bios_grub_at(){
    local start="$1" end="$2"
    echo "🛠️ 嘗試建立 bios_grub:${start}MiB → ${end}MiB"
    RUN "parted -s $DISK unit MiB mkpart primary $start $end"
    # 取最新分割編號(最後一個分割)
    local lastp; lastp="$(lsblk -prno NAME "$DISK" | tail -1)"
    RUN "parted -s $DISK set ${lastp##*[^0-9]} bios_grub on"
    BIOS_PART="$lastp"
    echo "✅ 建立完成:$BIOS_PART(flags=bios_grub)"
  }

  # 嘗試優先在開頭預留 1→3 MiB;若不足,再在結尾預留 2 MiB
  if [[ -n "$free_head" ]]; then
    read s e <<<"$free_head"
    # 需要至少 3MiB(保留 1→3 的小窗),避免覆蓋 GPT 保護區
    if (( $(echo "$e - $s >= 3" | bc -l) )); then
      create_bios_grub_at "1" "3"
      return 0
    fi
  fi
  if [[ -n "$free_tail" ]]; then
    read s e <<<"$free_tail"
    # 需要至少 2MiB
    if (( $(echo "$e - $s >= 2" | bc -l) )); then
      # 從尾端往回 2MiB
      local start end
      end="$(printf "%.0f" "$e")"
      start="$(( end - 2 ))"
      create_bios_grub_at "$start" "$end"
      return 0
    fi
  fi

  err "找不到可用的未分配空間(≥2MiB)可建立 bios_grub;請手動用 GParted 挪出 2MiB 再跑。"
}

# ========== 備份(含說明檔) ==========
backup_boot_assets(){
  echo "📦 建立開機資產備份(MBR/GPT前置、分割表、ESP、/boot、grub、kernelstub)"
  read -rp "備份輸出目錄(例:/mnt/usbbackup): " DEST
  [[ -n "${DEST:-}" ]] || err "未輸入輸出目錄"
  local ts base work
  ts="$(date +%Y%m%d-%H%M%S)"
  base="boot-backup-$(basename "$DISK")-$(hostname)-$ts"
  work="/tmp/$base"
  $DRY || mkdir -p "$work" "$DEST"

  echo "🧲 備份磁碟前置區(4MiB:MBR/GPT header + 空間)"
  RUN "dd if=$DISK of=$work/${base}.first4MiB.img bs=1M count=4 status=none"

  echo "📃 匯出分割表(sfdisk)"
  RUN "sfdisk -d $DISK > $work/${base}.sfdisk.txt"

  echo "📁 備份 ESP 內容"
  $DRY || mkdir -p /mnt/esp
  RUN "mount $ESP_PART /mnt/esp"
  RUN "rsync -aHAX --delete /mnt/esp/ $work/esp/"
  RUN "umount /mnt/esp"

  echo "🧷 備份 /boot 與 grub 設定"
  $DRY || mkdir -p "$work/boot"
  [[ -d /boot ]] && RUN "rsync -aHAX /boot/ $work/boot/" || true
  [[ -f /etc/default/grub ]] && RUN "cp -a /etc/default/grub $work/" || true
  [[ -d /etc/default/grub.d ]] && RUN "rsync -aHAX /etc/default/grub.d $work/" || true

  echo "🧰 備份 kernelstub(Pop!_OS)"
  RUN "chroot /mnt /bin/bash -c '[ -f /etc/kernelstub/configuration ] && cp -a /etc/kernelstub/configuration /tmp/kernelstub.conf || true'"
  $DRY || { [ -f /mnt/tmp/kernelstub.conf ] && cp -a /mnt/tmp/kernelstub.conf "$work/kernelstub.configuration" || true; }

  echo "🗜 打包壓縮(tar.gz)"
  RUN "tar -C $work -czf $DEST/${base}.tar.gz ."
  if ! $DRY; then
    echo "✅ 備份完成:$DEST/${base}.tar.gz"
    cat > "$DEST/${base}-RESTORE.txt" <<'DOC'
還原指南:
1) 還原分割表(如需重建整顆碟):
   sudo sfdisk /dev/sdX < boot-backup-*.sfdisk.txt
2) 還原 ESP 內容:
   sudo mount /dev/sdXn_efi /mnt/esp
   sudo rsync -aHAX --delete ./esp/ /mnt/esp/
   sudo umount /mnt/esp
3) 還原 /boot 與 grub 設定(已 chroot 到該系統):
   sudo rsync -aHAX ./boot/ /boot/
   sudo cp -a ./grub /etc/default/grub 2>/dev/null || true
   sudo rsync -aHAX ./grub.d/ /etc/default/grub.d/ 2>/dev/null || true
4) 重裝 GRUB(雙開):
   grub-install --target=i386-pc --boot-directory=/boot /dev/sdX
   grub-install --target=x86_64-efi --efi-directory=/boot/efi --boot-directory=/boot --recheck --removable
   update-grub || grub-mkconfig -o /boot/grub/grub.cfg
DOC
  fi
}

# ========== 動作集合 ==========
action_make_independent_dualboot(){
  pick_disk
  scan_parts
  # 自動補齊 bios_grub(若缺)
  ensure_bios_grub
  # 按偵測結果掛載並 chroot
  ensure_mounts
  stop_apt_locks
  fix_dpkg
  install_grub_tools
  disable_os_prober            # → 讓它「完全獨立」
  write_grub                   # → 安裝 BIOS + UEFI
  run_kernelstub_if_exists     # → Pop!_OS 同步 kernel 到 EFI
  gen_grub_cfg                 # → 產生 grub.cfg(只剩本機)
  echo "✅ 完成:此系統已成為『可攜、獨立、雙開(UEFI+BIOS)』"
}

action_toggle_isolation(){
  pick_disk
  scan_parts
  ensure_mounts
  read -rp "輸入 1=關閉 os-prober(獨立) / 2=開啟 os-prober(列出他系統):[1/2] " m
  case "$m" in
    1) disable_os_prober ;;
    2) enable_os_prober ;;
    *) err "輸入無效" ;;
  esac
  gen_grub_cfg
  echo "✅ 已更新 GRUB 選單"
}

action_backup(){
  pick_disk
  scan_parts
  ensure_mounts
  backup_boot_assets
}

action_create_bios_grub_only(){
  pick_disk
  scan_parts
  ensure_bios_grub
  echo "✅ bios_grub 已就緒:$BIOS_PART"
}

# ========== 主選單 ==========
if [[ "${1-}" != "--dry-run" && -n "${1-}" ]]; then
  echo "用法:sudo $0 [--dry-run]"
  exit 1
fi

echo "=============================="
echo " 可攜獨立雙開工具 v2(Debian/Ubuntu/Pop!_OS)"
echo "=============================="
echo "1) 讓此系統成為『可攜、獨立、雙開(UEFI+BIOS)』"
echo "2) 切換『獨立/非獨立』(關/開 os-prober)"
echo "3) 備份開機資產(MBR/GPT前置、分割表、ESP、/boot、grub…)"
echo "4) 自動建立 BIOS Boot 分割(若缺,GPT 且有空間)"
echo "q) 離開"
read -rp "請選擇: " choice

case "$choice" in
  1) action_make_independent_dualboot ;;
  2) action_toggle_isolation ;;
  3) action_backup ;;
  4) action_create_bios_grub_only ;;
  q|Q) echo "bye"; exit 0 ;;
  *) err "選項無效" ;;
esac

上次修改於 2025-10-24