KDE Plasma を使うならと、Fedora にした
こんにちわ。Daizo です。KDE Plasma を使うことにしたのですが、それなら、もっと安定性をと考えて、ChatGPT に聞いたところ、それなら、Fedora 43 KDE Plasma Desktop Edition でしょうと言われたので、インストールしてみました。インストールしていて気がついたのですが、Fedora のデフォルトファイルシステムって、openSUSE と同じ、btrfs なんですね。ならばと、システムアップデート前にスナップショットを取ってバックアップするスクリプトを ChatGPT に書いてもらいました。#!/usr/bin/env bash set -euo pipefail # ========= 設定 ========= KEEP="${KEEP:-10}" # 保持世代数(各FS) SNAP_PREFIX="${SNAP_PREFIX:-fedora-dnf}" MOUNT_BASE="${MOUNT_BASE:-/run/btrfs-top}" DO_UPDATE="${DO_UPDATE:-1}" # ======================= log(){ echo "[$(date '+%F %T')] $*"; } need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: command not found: $1" >&2; exit 1; } } # findmnt OPTIONS から subvol=/... を確実に抜く get_subvol_from_options() { local mp="$1" local opts opts="$(findmnt -no OPTIONS "$mp")" # opts例: ... subvolid=256,subvol=/root local subvol subvol="$(awk -v RS=',' ' $1 ~ /^subvol=/ { sub(/^subvol=/,"",$1); print $1 } ' <<< "$opts" | tail -n1)" if [[ -z "${subvol:-}" ]]; then echo "ERROR: cannot detect subvol= from findmnt OPTIONS for $mp" >&2 echo " OPTIONS: $opts" >&2 exit 1 fi echo "$subvol" } # findmnt SOURCE からブロックデバイス部分だけ抜く(/dev/sda3 や UUID=...) get_device_from_source() { local mp="$1" local src src="$(findmnt -no SOURCE "$mp")" # /dev/sda3[/root] -> /dev/sda3 # UUID=....[/root] -> UUID=.... echo "${src%%\[*}" } snapshot_mountpoint() { local mp="$1" # "/" or "/home" local label="$2" # "root" or "home" local fstype dev subvol topmp ts snapdir fstype="$(findmnt -no FSTYPE "$mp" || true)" if [[ "$fstype" != "btrfs" ]]; then log "SKIP: $mp is not btrfs (FSTYPE=$fstype)" return 0 fi dev="$(get_device_from_source "$mp")" subvol="$(get_subvol_from_options "$mp")" # 例: /root, /home topmp="${MOUNT_BASE}-${label}" ts="$(date '+%Y%m%d-%H%M%S')" snapdir=".snapshots/${SNAP_PREFIX}-${ts}" log "Target: $mp dev=$dev subvol=$subvol" sudo mkdir -p "$topmp" if ! mountpoint -q "$topmp"; then log "Mount toplevel (subvolid=5) -> $topmp" sudo mount -o subvolid=5 "$dev" "$topmp" fi sudo mkdir -p "$topmp/.snapshots" sudo mkdir -p "$topmp/$snapdir" # subvol が /root のように / で始まるので、toplevel側パスは "${subvol#/}" で安全に連結 local src_path="$topmp/${subvol#/}" local dst_path="$topmp/$snapdir/$label" if [[ ! -d "$src_path" ]]; then echo "ERROR: cannot find subvolume path: $src_path" >&2 echo "HINT: dev=$dev subvol=$subvol mp=$mp" >&2 exit 1 fi log "Create read-only snapshot: $dst_path" sudo btrfs subvolume snapshot -r "$src_path" "$dst_path" >/dev/null # 世代数制御(古いものから削除): ディレクトリの中身に subvol がある前提で消す log "Retention: keep last $KEEP snapshots (FS=$label)" local all=() mapfile -t all < <(sudo ls -1 "$topmp/.snapshots" 2>/dev/null | grep -E "^${SNAP_PREFIX}-" | sort || true) local count="${#all[@]}" if (( count > KEEP )); then local del_n=$((count - KEEP)) for ((i=0; i<del_n; i++)); do local old="${all[$i]}" local old_subvol="$topmp/.snapshots/$old/$label" log "Delete old snapshot: $old_subvol" if sudo btrfs subvolume show "$old_subvol" >/dev/null 2>&1; then sudo btrfs subvolume delete "$old_subvol" >/dev/null || true fi sudo rm -rf "$topmp/.snapshots/$old" done fi } cleanup_mounts() { for p in "${MOUNT_BASE}-root" "${MOUNT_BASE}-home"; do if mountpoint -q "$p"; then log "Umount $p" sudo umount "$p" || true fi done } trap cleanup_mounts EXIT main() { need_cmd findmnt need_cmd btrfs need_cmd sudo need_cmd rpm need_cmd uname local DNF_BIN DNF_BIN="$(command -v dnf5 || command -v dnf)" if [[ -z "${DNF_BIN:-}" ]]; then echo "ERROR: neither dnf5 nor dnf found" >&2 exit 1 fi log "=== Btrfs snapshot -> dnf upgrade ===" log "KEEP=$KEEP SNAP_PREFIX=$SNAP_PREFIX DNF=$DNF_BIN" # 1) スナップショット作成 snapshot_mountpoint "/" "root" snapshot_mountpoint "/home" "home" # 2) アップデート if (( DO_UPDATE )); then log "$DNF_BIN upgrade --refresh -y" sudo "$DNF_BIN" upgrade --refresh -y fi # 3) 再起動判定 if command -v needs-restarting >/dev/null 2>&1; then if needs-restarting -r >/dev/null 2>&1; then log "Reboot is NOT required (needs-restarting -r)." else log "REBOOT RECOMMENDED/REQUIRED (needs-restarting -r)." echo echo ">>> 再起動を推奨します: sudo systemctl reboot" echo fi else # フォールバック:カーネル更新検知u if rpm -q kernel >/dev/null 2>&1; then local latest running latest="$(rpm -q kernel --qf '%{VERSION}-%{RELEASE}.%{ARCH}\n' | sort -V | tail -n1 || true)" running="$(uname -r || true)" if [[ -n "$latest" && -n "$running" && "$running" != *"$latest"* ]]; then log "Kernel updated (running=$running, latest=$latest) -> reboot recommended." echo echo ">>> カーネルが更新されています。再起動を推奨します: sudo systemctl reboot" echo " running: $running" echo " latest : $latest" echo else log "Reboot not indicated by kernel version check." fi fi log "NOTE: needs-restarting が無い場合があります(dnf4/5構成次第)。" fi # --- force grub snapshot menu sync --- log "Syncing GRUB snapshot menu..." sudo systemctl restart grub-btrfsd sleep 3 sudo grub2-mkconfig -o /boot/grub2/grub.cfg >/dev/null log "Done." echo echo "スナップショットは subvolid=5 側の .snapshots/ に作成されています。" echo "例: <toplevel>/.snapshots/${SNAP_PREFIX}-YYYYmmdd-HHMMSS/root など" } main "$@"とこんな感じでスナップショットを取ってからシステムをアップデートするようにしました。ここまでは楽だったんだけど、ここから、基本的に自動で、起動時に取ったスナップショットを選んで、起動させるようにするのに、ものすごく苦労しました。openSUSE の初期機能なんだけど、恐るべしですね。これで、万が一アップデート失敗しても問題ないようになりました。苦労した・・・・・