Ubuntu Desktop 26.04をインストール(2026年5月版)

Ubuntuをデスクトップ環境で使う方法をまとめました。

後半にISOを置きISOブートでインストール

デスクトップ環境で使うなら不具合時の復元しやすさを重視して後半パーティションにISOイメージを保存し、そこからインストールするのが楽だと思います。まだ作っていないならインストール時に作成してもよいですし、あらかじめ作成してそこからブートしてクリーンインストールしても良いかと。

ISO用のパーティションを/isoにマウントするスクリプト

パーティションのみ作成してこれからコピーする場合。下記スクリプトはそのパーティションを /iso にマウントするスクリプト。一時的にマウントすることも、永続的にマウントすることも可能です。もっともクリーンインストールするならあえて/isoにマウントする必要もないかもしれませんが。ただ、このスクリプトをISOイメージのあるパーティションに保存しておけば、クリーンインストールしたあとも役立つので。
後半パーティションをマウントし、右クリックしてターミナルを表示して下記を実行。

nano mount-setup.sh
sudo bash mount-setup.sh
#!/bin/bash
# ============================================================
# 対話式 fstab マウント登録スクリプト
#   - ディスク一覧を表示して番号で選択
#   - 永続マウント(fstab 登録)か一時マウントかを選択
#   - /iso ディレクトリがなければ作成・権限設定
#   - UUID を /etc/fstab に追記してマウント(永続時のみ)
# ============================================================
set -euo pipefail

MOUNT_POINT="/iso"
FSTAB="/etc/fstab"

info()    { echo -e "\033[1;32m[INFO]\033[0m  $*"; }
warn()    { echo -e "\033[1;33m[WARN]\033[0m  $*"; }
error()   { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }
confirm() {
  local msg="$1"
  local ans
  read -rp "$(echo -e "\033[1;33m[確認]\033[0m $msg [y/N]: ")" ans
  [[ "${ans,,}" == "y" ]]
}

# root 確認
[[ "$EUID" -ne 0 ]] && error "このスクリプトは sudo で実行してください。"

# ============================================================
# 1. パーティション一覧を表示
# ============================================================
echo ""
echo "======================================================"
echo " 現在のディスク・パーティション一覧"
echo "======================================================"
echo ""
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT,UUID | grep -v "^loop"
echo ""

# パーティションのみ抽出(ディスク本体除く)
mapfile -t PARTS < <(lsblk -lnpo NAME,SIZE,FSTYPE,MOUNTPOINT \
  | awk '$3!="" && $1!~/^(loop|sr)/' \
  | grep -v "^$")

if [[ ${#PARTS[@]} -eq 0 ]]; then
  error "マウント可能なパーティションが見つかりませんでした。"
fi

echo "------------------------------------------------------"
printf " %-3s  %-15s %-8s %-10s %s\n" "No." "デバイス" "サイズ" "FS種別" "現在のマウント先"
echo "------------------------------------------------------"
for i in "${!PARTS[@]}"; do
  read -r dev size fstype mount <<< "${PARTS[$i]}"
  mount="${mount:-(未マウント)}"
  printf " %-3s  %-15s %-8s %-10s %s\n" "$((i+1))" "$dev" "$size" "$fstype" "$mount"
done
echo "------------------------------------------------------"
echo ""

# ============================================================
# 2. パーティション選択
# ============================================================
while true; do
  read -rp "$(echo -e "\033[1;36m[入力]\033[0m マウントするパーティションの番号を入力してください: ")" SEL
  if [[ "$SEL" =~ ^[0-9]+$ ]] && (( SEL >= 1 && SEL <= ${#PARTS[@]} )); then
    break
  fi
  warn "1〜${#PARTS[@]} の番号を入力してください。"
done

read -r SELECTED_DEV _ SELECTED_FS CURRENT_MOUNT <<< "${PARTS[$((SEL-1))]}"
CURRENT_MOUNT="${CURRENT_MOUNT:-(未マウント)}"

echo ""
info "選択: $SELECTED_DEV  FS: $SELECTED_FS  現在: $CURRENT_MOUNT"

# すでに /iso にマウント済みなら確認
if [[ "$CURRENT_MOUNT" == "$MOUNT_POINT" ]]; then
  warn "$SELECTED_DEV はすでに $MOUNT_POINT にマウントされています。"
  confirm "再登録を続けますか?" || exit 0
fi

# 別の場所にマウント済みなら警告
if [[ "$CURRENT_MOUNT" != "(未マウント)" && "$CURRENT_MOUNT" != "$MOUNT_POINT" ]]; then
  warn "$SELECTED_DEV は現在 $CURRENT_MOUNT にマウントされています。"
  confirm "続けますか?(現在のマウントは変更しません)" || exit 0
fi

# ============================================================
# 3. マウント方式の選択
# ============================================================
echo ""
echo "======================================================"
echo " マウント方式を選択してください"
echo "======================================================"
echo ""
echo "  1) 永続マウント  - fstab に登録し、再起動後も自動マウント"
echo "  2) 一時マウント  - 今回のみマウント(再起動で解除)"
echo ""

MOUNT_MODE=""
while true; do
  read -rp "$(echo -e "\033[1;36m[入力]\033[0m 番号を入力してください [1/2]: ")" MODE_SEL
  case "$MODE_SEL" in
    1) MOUNT_MODE="persistent"; break ;;
    2) MOUNT_MODE="temporary";  break ;;
    *) warn "1 または 2 を入力してください。" ;;
  esac
done

echo ""
if [[ "$MOUNT_MODE" == "persistent" ]]; then
  info "モード: 永続マウント(fstab 登録あり)"
else
  info "モード: 一時マウント(fstab 登録なし)"
fi

# ============================================================
# 4. UUID 取得
# ============================================================
UUID=$(blkid -s UUID -o value "$SELECTED_DEV" 2>/dev/null || true)
if [[ -z "$UUID" ]]; then
  error "$SELECTED_DEV の UUID を取得できませんでした。FS が認識されているか確認してください。"
fi
info "UUID: $UUID"

# FS タイプの決定(空なら auto)
FS_TYPE="${SELECTED_FS:-auto}"
# ntfs の場合は ntfs-3g に補正
[[ "$FS_TYPE" == "ntfs" ]] && FS_TYPE="ntfs-3g"

# ============================================================
# 5. /iso ディレクトリの準備
# ============================================================
if [[ ! -d "$MOUNT_POINT" ]]; then
  info "$MOUNT_POINT が存在しないため作成します..."
  mkdir -p "$MOUNT_POINT"
  # 所有者を実行前のユーザーに設定
  REAL_USER="${SUDO_USER:-root}"
  chown "${REAL_USER}:${REAL_USER}" "$MOUNT_POINT"
  chmod 775 "$MOUNT_POINT"
  info "作成完了: $MOUNT_POINT(所有者: ${REAL_USER}, パーミッション: 775)"
else
  info "$MOUNT_POINT はすでに存在します。"
fi

# ============================================================
# 6. fstab の重複チェックと追記(永続マウント時のみ)
# ============================================================
if [[ "$MOUNT_MODE" == "persistent" ]]; then

  # 同じ UUID がすでに fstab にないか確認
  if grep -q "$UUID" "$FSTAB"; then
    warn "UUID=$UUID はすでに $FSTAB に登録されています。"
    grep "$UUID" "$FSTAB"
    confirm "上書き(既存行をコメントアウトして追記)しますか?" || exit 0
    # 既存行をコメントアウト
    sed -i "s|.*${UUID}.*|# &  # mount-setup.sh により無効化 $(date '+%Y-%m-%d')|" "$FSTAB"
  fi

  # fstab に /iso のマウント先がすでにあるか確認
  if grep -qP "^\s*[^#].*\s${MOUNT_POINT}\s" "$FSTAB"; then
    warn "$MOUNT_POINT のエントリがすでに $FSTAB にあります。"
    grep -P "^\s*[^#].*\s${MOUNT_POINT}\s" "$FSTAB"
    confirm "既存行をコメントアウトして上書きしますか?" || exit 0
    sed -i "s|^\([^#].*\s${MOUNT_POINT}\s.*\)|# \1  # mount-setup.sh により無効化 $(date '+%Y-%m-%d')|" "$FSTAB"
  fi

  FSTAB_LINE="UUID=${UUID}  ${MOUNT_POINT}  ${FS_TYPE}  defaults  0  2"

  echo ""
  info "以下の行を $FSTAB に追記します:"
  echo ""
  echo "  $FSTAB_LINE"
  echo ""
  confirm "追記しますか?" || exit 0

  # バックアップ
  cp "$FSTAB" "${FSTAB}.bak.$(date '+%Y%m%d%H%M%S')"
  info "バックアップ: ${FSTAB}.bak.* を作成しました。"

  echo "" >> "$FSTAB"
  echo "# /iso - mount-setup.sh により追加 $(date '+%Y-%m-%d %H:%M:%S')" >> "$FSTAB"
  echo "$FSTAB_LINE" >> "$FSTAB"
  info "fstab への追記が完了しました。"

fi

# ============================================================
# 7. マウント実行
# ============================================================
echo ""
if [[ "$MOUNT_MODE" == "persistent" ]]; then
  info "mount -a でマウントを実行します..."
  if mount -a 2>&1; then
    info "マウント成功!"
  else
    warn "mount -a でエラーが発生しました。fstab の内容を確認してください:"
    tail -5 "$FSTAB"
    exit 1
  fi
else
  info "mount -t ${FS_TYPE} ${SELECTED_DEV} ${MOUNT_POINT} でマウントを実行します..."
  if mount -t "${FS_TYPE}" "${SELECTED_DEV}" "${MOUNT_POINT}" 2>&1; then
    info "マウント成功!"
  else
    warn "マウントに失敗しました。デバイスと FS タイプを確認してください。"
    exit 1
  fi
fi

# ============================================================
# 8. 確認
# ============================================================
echo ""
echo "======================================================"
if [[ "$MOUNT_MODE" == "persistent" ]]; then
  echo " 完了!(永続マウント)マウント状態:"
else
  echo " 完了!(一時マウント・再起動で解除)マウント状態:"
fi
echo "======================================================"
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT | grep -E "NAME|$(basename "$SELECTED_DEV")"
echo ""
df -h "$MOUNT_POINT"
echo "======================================================"

grubメニューにISOブートをエントリを追加するスクリプト

続いてスクリプトを保存して実行します。スクリプトの保存先を/isoにしておけば、このISOブートを利用してクリーンインストールした時など、再度GRUBメニューに項目を追加したい場合に便利です。
(追記:新しく作ったスクリプトのほうが便利です)

cd /iso
nano isoboot-grub.sh
sudo bash isoboot-grub.sh
#!/usr/bin/env bash
# =============================================================================
# isoboot-grub.sh
# ISOブート用パーティション確認 → GRUB エントリ追加スクリプト
# 前提:ISOファイル入りのext4パーティションが既に存在すること
# =============================================================================

set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'

info()    { echo -e "${CYAN}[INFO]${RESET}  $*"; }
success() { echo -e "${GREEN}[OK]${RESET}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
error()   { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
die()     { error "$*"; exit 1; }
hr()      { echo -e "${CYAN}$(printf '=%.0s' {1..70})${RESET}"; }

confirm() {
    local ans
    while true; do
        echo -en "${YELLOW}[確認]${RESET} $1 [y/N]: "
        read -r ans
        case "$ans" in
            [yY]*) return 0 ;;
            [nN]*|"") return 1 ;;
            *) echo "  y または n を入力してください。" ;;
        esac
    done
}

# -----------------------------------------------------------------------------
check_root() {
    [[ $EUID -eq 0 ]] || die "sudo で実行してください。\n  例: sudo bash $0"
}

check_deps() {
    local missing=()
    for cmd in lsblk blkid mount umount file grub-mkconfig update-grub; do
        command -v "$cmd" &>/dev/null || missing+=("$cmd")
    done
    [[ ${#missing[@]} -eq 0 ]] || die "コマンドが見つかりません: ${missing[*]}"
}

# -----------------------------------------------------------------------------
# ステップ1: ISOが入ったパーティションを選択
# -----------------------------------------------------------------------------
select_partition() {
    hr
    echo -e "${BOLD}【ステップ 1/3】ISO パーティションの選択${RESET}"
    hr
    echo ""
    info "現在のパーティション一覧:"
    echo ""

    # ループデバイス除外・パーティションのみを配列に収集
    mapfile -t part_list < <(
        lsblk -lno NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS \
        | grep -v '^loop' \
        | awk 'NF && $1 ~ /[0-9]$/ { print $0 }'
    )

    if [[ ${#part_list[@]} -eq 0 ]]; then
        die "パーティションが見つかりませんでした。"
    fi

    # 番号付きで表示
    echo -e "  ${BOLD}No.  NAME            SIZE  FSTYPE  LABEL  MOUNTPOINTS${RESET}"
    printf '  %s\n' "$(printf -- '-%.0s' {1..60})"
    for i in "${!part_list[@]}"; do
        printf "  ${CYAN}[%2d]${RESET}  %s\n" "$((i+1))" "${part_list[$i]}"
    done
    echo ""

    while true; do
        echo -en "${BOLD}番号を入力してください${RESET} [1-${#part_list[@]}]: "
        read -r sel

        # 数値チェック
        if ! [[ "$sel" =~ ^[0-9]+$ ]] || \
           (( sel < 1 || sel > ${#part_list[@]} )); then
            error "1 〜 ${#part_list[@]} の番号を入力してください。"
            continue
        fi

        local chosen_line="${part_list[$((sel-1))]}"
        local part_name
        part_name=$(echo "$chosen_line" | awk '{print $1}')
        PART_DEV="/dev/${part_name}"

        if [[ ! -b "$PART_DEV" ]]; then
            error "${PART_DEV} はブロックデバイスではありません。再選択してください。"
            continue
        fi

        local fstype
        fstype=$(lsblk -no FSTYPE "$PART_DEV" 2>/dev/null || true)
        if [[ "$fstype" != "ext4" ]]; then
            warn "${PART_DEV} のファイルシステムは ${fstype:-不明} です(ext4 を想定)。"
            confirm "このまま続けますか?" || continue
        fi

        echo ""
        info "選択されたパーティション: ${PART_DEV}"
        lsblk -o NAME,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS "$PART_DEV" 2>/dev/null || true
        echo ""
        confirm "${PART_DEV} を使用しますか?" && break
    done

    success "対象パーティション: ${PART_DEV}"

    # UUID取得(デバイス名非依存・VM/実機共通)
    PART_UUID=$(blkid -s UUID -o value "$PART_DEV")
    if [[ -z "$PART_UUID" ]]; then
        die "UUIDを取得できませんでした: ${PART_DEV}"
    fi
    success "UUID: ${PART_UUID}"
}

# -----------------------------------------------------------------------------
# ステップ2: ISOファイルの確認と起動パス検出
# -----------------------------------------------------------------------------
inspect_isos() {
    hr
    echo -e "${BOLD}【ステップ 2/3】ISO ファイルの確認${RESET}"
    hr
    echo ""

    MOUNT_POINT="/mnt/isoboot_grub_$$"
    mkdir -p "$MOUNT_POINT"
    mount "$PART_DEV" "$MOUNT_POINT"
    info "${PART_DEV} を ${MOUNT_POINT} にマウントしました"
    echo ""

    # ISOファイルを列挙
    mapfile -t iso_candidates < <(find "$MOUNT_POINT" -maxdepth 2 -name "*.iso" 2>/dev/null)

    if [[ ${#iso_candidates[@]} -eq 0 ]]; then
        umount "$MOUNT_POINT"; rmdir "$MOUNT_POINT"
        die "ISOファイルが見つかりませんでした。パーティションにISOを配置してから再実行してください。"
    fi

    info "見つかったISOファイル:"
    for i in "${!iso_candidates[@]}"; do
        local size
        size=$(du -sh "${iso_candidates[$i]}" 2>/dev/null | cut -f1)
        echo "  [$((i+1))] ${iso_candidates[$i]##"$MOUNT_POINT"}  (${size})"
    done
    echo ""

    ISO_ENTRIES=()  # "iso_path:vmlinuz:initrd:boot_type" の配列

    for iso_full in "${iso_candidates[@]}"; do
        local iso_rel="${iso_full##"$MOUNT_POINT"}"
        local iso_name
        iso_name=$(basename "$iso_full")
        echo ""
        info "--- ${iso_name} を検査中 ---"

        if confirm "${iso_name} をGRUBメニューに追加しますか?"; then
            local paths
            paths=$(detect_boot_paths "$iso_full")
            local vmlinuz="${paths%%:*}"
            local rest="${paths#*:}"
            local initrd="${rest%%:*}"
            local boot_type="${rest##*:}"

            if [[ "$vmlinuz" == "UNKNOWN" ]]; then
                warn "カーネルパスを自動検出できませんでした。手動入力してください。"
                echo -en "  vmlinuz のパス (例: /casper/vmlinuz): "
                read -r vmlinuz
                echo -en "  initrd のパス  (例: /casper/initrd): "
                read -r initrd
                boot_type="custom"
            else
                success "カーネル : ${vmlinuz}"
                success "initrd   : ${initrd}"
                success "起動方式 : ${boot_type}"
            fi

            ISO_ENTRIES+=("${iso_rel}:${vmlinuz}:${initrd}:${boot_type}")
        else
            info "スキップ: ${iso_name}"
        fi
    done

    umount "$MOUNT_POINT"
    rmdir "$MOUNT_POINT"
    success "アンマウント完了"

    if [[ ${#ISO_ENTRIES[@]} -eq 0 ]]; then
        die "追加するエントリがありません。終了します。"
    fi
}

# ISOをループマウントして起動パスを自動検出
detect_boot_paths() {
    local iso_path="$1"
    local tmp="/mnt/iso_inspect_$$"
    mkdir -p "$tmp"

    if ! mount -o loop,ro "$iso_path" "$tmp" 2>/dev/null; then
        echo "UNKNOWN:UNKNOWN:custom"
        return
    fi

    local vmlinuz="" initrd="" boot_type=""

    if   [[ -f "$tmp/casper/vmlinuz" ]];     then vmlinuz="/casper/vmlinuz";   initrd="/casper/initrd";     boot_type="casper"
    elif [[ -f "$tmp/casper/vmlinuz.efi" ]]; then vmlinuz="/casper/vmlinuz.efi"; initrd="/casper/initrd.lz"; boot_type="casper"
    elif [[ -f "$tmp/live/vmlinuz" ]];        then vmlinuz="/live/vmlinuz";     initrd="/live/initrd.img";   boot_type="live"
    elif [[ -f "$tmp/live/vmlinuz.efi" ]];    then vmlinuz="/live/vmlinuz.efi"; initrd="/live/initrd.img";   boot_type="live"
    else
        # フォールバック:vmlinuz を再帰検索
        local found
        found=$(find "$tmp" -name "vmlinuz*" | head -1 || true)
        if [[ -n "$found" ]]; then
            vmlinuz="${found##"$tmp"}"
            local dir
            dir=$(dirname "$found")
            initrd=$(find "$dir" -name "initrd*" | head -1 || true)
            initrd="${initrd##"$tmp"}"
            boot_type="custom"
        else
            vmlinuz="UNKNOWN"; initrd="UNKNOWN"; boot_type="custom"
        fi
    fi

    umount "$tmp"; rmdir "$tmp"
    echo "${vmlinuz}:${initrd}:${boot_type}"
}

# -----------------------------------------------------------------------------
# ステップ3: GRUBエントリ追加
# -----------------------------------------------------------------------------
add_grub_entries() {
    hr
    echo -e "${BOLD}【ステップ 3/3】GRUB エントリの追加${RESET}"
    hr
    echo ""

    local custom_file="/etc/grub.d/40_custom"
    cp "$custom_file" "${custom_file}.bak.$(date +%Y%m%d_%H%M%S)"
    info "40_custom をバックアップしました"
    echo ""

    for entry in "${ISO_ENTRIES[@]}"; do
        local iso_rel vmlinuz initrd boot_type menu_label
        IFS=':' read -r iso_rel vmlinuz initrd boot_type <<< "$entry"
        menu_label=$(basename "$iso_rel" .iso)

        local params=""
        case "$boot_type" in
            casper) params="boot=casper iso-scan/filename=\$isofile quiet splash ---" ;;
            live)   params="boot=live iso-scan/filename=\$isofile quiet splash" ;;
            *)      params="iso-scan/filename=\$isofile quiet splash" ;;
        esac

        local grub_entry
        grub_entry=$(cat <<EOF

menuentry "${menu_label} (ISO Loop Boot)" {
    insmod part_gpt
    insmod ext2
    insmod loopback
    insmod iso9660
    search --no-floppy --fs-uuid --set=isodev ${PART_UUID}
    set isofile="${iso_rel}"
    loopback loop (\$isodev)\$isofile
    linux  (loop)${vmlinuz} ${params}
    initrd (loop)${initrd}
}
EOF
)
        echo -e "${CYAN}追加予定エントリ:${RESET}"
        echo "$grub_entry"
        echo ""

        if confirm "このエントリを追加しますか?"; then
            echo "$grub_entry" >> "$custom_file"
            success "追加しました: ${menu_label}"
        else
            warn "スキップ: ${menu_label}"
        fi
    done

    echo ""
    info "GRUB を更新中..."
    if update-grub 2>&1; then
        success "update-grub 完了"
    else
        warn "update-grub でエラーが発生しました。手動で確認してください:"
        warn "  sudo update-grub"
    fi
}

# -----------------------------------------------------------------------------
# 完了サマリー
# -----------------------------------------------------------------------------
print_summary() {
    hr
    echo -e "${BOLD}${GREEN}【完了】セットアップサマリー${RESET}"
    hr
    echo ""
    echo -e "  対象パーティション : ${BOLD}${PART_DEV}${RESET}"
    echo -e "  パーティションUUID : ${BOLD}${PART_UUID}${RESET}"
    echo -e "  追加エントリ数     : ${BOLD}${#ISO_ENTRIES[@]}${RESET}"
    echo ""
    echo -e "${YELLOW}【次のステップ】${RESET}"
    echo "  1. 再起動して GRUB メニューに新しいエントリが表示されることを確認"
    echo "  2. 起動に失敗した場合は /etc/grub.d/40_custom を確認し"
    echo "     カーネルパスを修正後 sudo update-grub を再実行"
    echo ""
    warn "バックアップ: /etc/grub.d/40_custom.bak.* に保存済み"
    hr
}

# -----------------------------------------------------------------------------
main() {
    clear
    hr
    echo -e "${BOLD}  isoboot-grub.sh — ISO ブート GRUB エントリ 追加スクリプト${RESET}"
    echo -e "  前提: ISOファイル入りのext4パーティションが既に存在すること"
    hr
    echo ""
    check_root
    check_deps
    select_partition
    inspect_isos
    add_grub_entries
    print_summary
}

main "$@"

現在のGRUBエントリーを確認

cd /iso
nano list-grub-entries.sh
chmod +x list-grub-entries.sh
bash list-grub-entries.sh
#!/usr/bin/env bash
# ============================================================
#  list-grub-entries.sh
#  GRUBエントリー一覧表示スクリプト (Ubuntu 26.04 対応)
# ============================================================

set -euo pipefail

# ---------- 色定義 ----------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'

# ---------- ヘルパー関数 ----------
info()    { echo -e "${CYAN}[INFO]${RESET}  $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
error()   { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
header()  { echo -e "\n${BOLD}${GREEN}$*${RESET}"; echo -e "${GREEN}$(printf '=%.0s' {1..60})${RESET}"; }

# ---------- grub-mkconfig / grub2-mkconfig の確認 ----------
find_grub_cfg() {
    local candidates=(
        /boot/grub/grub.cfg
        /boot/grub2/grub.cfg
        /boot/efi/EFI/ubuntu/grub.cfg
    )
    for f in "${candidates[@]}"; do
        [[ -f "$f" ]] && echo "$f" && return
    done
    echo ""
}

# ---------- メイン ----------
main() {
    header "GRUB エントリー一覧表示ツール"
    echo -e "実行日時: $(date '+%Y-%m-%d %H:%M:%S')\n"

    # ---- grub.cfg を探す ----
    GRUB_CFG=$(find_grub_cfg)

    if [[ -z "$GRUB_CFG" ]]; then
        error "grub.cfg が見つかりませんでした。"
        error "次のいずれかに存在するか確認してください:"
        error "  /boot/grub/grub.cfg"
        error "  /boot/grub2/grub.cfg"
        error "  /boot/efi/EFI/ubuntu/grub.cfg"
        exit 1
    fi

    info "設定ファイル: ${BOLD}${GRUB_CFG}${RESET}"

    # ---- 読み取り権限チェック ----
    if [[ ! -r "$GRUB_CFG" ]]; then
        warn "読み取り権限がありません。sudo で再実行します..."
        if ! sudo -n true 2>/dev/null; then
            warn "sudoパスワードが必要です。"
        fi
        GRUB_CFG_CONTENT=$(sudo cat "$GRUB_CFG")
    else
        GRUB_CFG_CONTENT=$(cat "$GRUB_CFG")
    fi

    # ---- エントリーを抽出 ----
    header "■ menuentry 一覧"

    # menuentry 行を抽出 (インデント・サブメニューも含む)
    ENTRIES=$(echo "$GRUB_CFG_CONTENT" | grep -n '^\(menuentry\|submenu\)' || true)

    if [[ -z "$ENTRIES" ]]; then
        warn "menuentryが見つかりませんでした。"
    else
        INDEX=0
        while IFS= read -r line; do
            LINENO_=$(echo "$line" | cut -d: -f1)
            ENTRY=$(echo "$line" | cut -d: -f2-)

            # menuentry か submenu かを判定
            if echo "$ENTRY" | grep -q '^submenu'; then
                TYPE="${YELLOW}[サブメニュー]${RESET}"
            else
                TYPE="${GREEN}[エントリー ]${RESET}"
            fi

            # エントリー名を抽出(クォート内の文字列)
            NAME=$(echo "$ENTRY" | sed "s/.*menuentry[[:space:]]*['\"]\\([^'\"]*\\)['\"].*/\\1/")

            printf "  ${BOLD}%3d${RESET}  %b  %s  ${CYAN}(行 %s)${RESET}\n" \
                "$INDEX" "$TYPE" "$NAME" "$LINENO_"
            ((INDEX++)) || true
        done <<< "$ENTRIES"
    fi

    # ---- デフォルトエントリー ----
    header "■ デフォルトエントリー"

    DEFAULT_GRUB="/etc/default/grub"
    if [[ -f "$DEFAULT_GRUB" ]]; then
        DEFAULT=$(grep '^GRUB_DEFAULT=' "$DEFAULT_GRUB" | cut -d= -f2 | tr -d '"' || echo "未設定")
        echo -e "  GRUB_DEFAULT = ${BOLD}${DEFAULT}${RESET}"

        TIMEOUT=$(grep '^GRUB_TIMEOUT=' "$DEFAULT_GRUB" | cut -d= -f2 | tr -d '"' || echo "未設定")
        echo -e "  GRUB_TIMEOUT = ${BOLD}${TIMEOUT}${RESET} 秒"
    else
        warn "${DEFAULT_GRUB} が見つかりません。"
    fi

    # ---- grubenv の確認 ----
    header "■ grubenv (保存されたデフォルト)"

    GRUBENV_CANDIDATES=(
        /boot/grub/grubenv
        /boot/grub2/grubenv
    )
    FOUND_ENV=0
    for env_file in "${GRUBENV_CANDIDATES[@]}"; do
        if [[ -f "$env_file" ]]; then
            info "grubenv: $env_file"
            if [[ -r "$env_file" ]]; then
                grep -v '^#' "$env_file" | grep -v '^$' | while IFS= read -r envline; do
                    echo -e "    ${envline}"
                done
            else
                sudo grep -v '^#' "$env_file" | grep -v '^$' | while IFS= read -r envline; do
                    echo -e "    ${envline}"
                done
            fi
            FOUND_ENV=1
            break
        fi
    done
    [[ $FOUND_ENV -eq 0 ]] && warn "grubenv が見つかりませんでした。"

    # ---- EFIブートエントリー (efibootmgr) ----
    header "■ EFI ブートエントリー (efibootmgr)"

    if command -v efibootmgr &>/dev/null; then
        if efibootmgr &>/dev/null 2>&1; then
            efibootmgr | while IFS= read -r efil; do
                if echo "$efil" | grep -q '^\*'; then
                    echo -e "  ${GREEN}${efil}${RESET}  ${CYAN}← 現在のブートエントリー${RESET}"
                else
                    echo -e "  ${efil}"
                fi
            done
        else
            # sudo が必要な場合
            sudo efibootmgr 2>/dev/null | while IFS= read -r efil; do
                if echo "$efil" | grep -q '^\*'; then
                    echo -e "  ${GREEN}${efil}${RESET}  ${CYAN}← 現在のブートエントリー${RESET}"
                else
                    echo -e "  ${efil}"
                fi
            done
        fi
    else
        warn "efibootmgr がインストールされていません。"
        info "インストール: sudo apt install efibootmgr"
    fi

    echo ""
    info "完了しました。"
    echo ""
}

main "$@"

Ubuntuインストール直後。アップデート・Tailscale・SSH

Ubuntuのインストールが完了したらまずはアップデート。

sudo apt update
sudo apt upgrade -y

Tailscale

sudo apt install curl 
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

SSH

sudo apt install -y openssh-server

別PCからの接続時エラーの場合。何度も繰り返して接続していると発生。接続元で。

ssh-keygen -R ホスト名やIPアドレス

ノートPCなどでDesktop版をサーバにしている場合

デスクトップ共有を有効化。キーリングへのパスワードは空のままで保存。

自動画面ロックを無効化

プライバシーとセキュリティ」-「画面ロック」で画面が暗くなるまでの時間や自動画面ロックの設定を行います。ちなみに「電源管理」-「自動画面ブランク」の設定も、ここの設定が反映されます。

そのほか「電源管理」-「省電力」の項目もチェック。

ロック時でもデスクトップ共有

デスクトップ共有の場合、ロックされてもアクセス出来るように。
まずGNOMEのシェル拡張機能をインストール。

sudo apt install gnome-shell-extension-manager

「探す」で「Allow locked Remote Desktop」を検索してインストールすれば有効に。

Allow locked Remote Desktop

ノートPCで画面が閉じてもスリープしない設定

sudo sed -i 's/#HandleLidSwitch=suspend/HandleLidSwitch=ignore/' /etc/systemd/logind.conf

一度再起動。以降はSSHやリモートデスクトップで。

sudo reboot

モニターを接続していない環境でデスクトップ共有

デスクトップ共有はモニター接続が必要ですが、接続していない場合でもデスクトップ共有するならこちら

ISOブートしていた場合

ブートローダーもクリーンインストールした場合はISOブートがメニューから消えているので再設定。一番後ろのパーティションをマウントして右クリックしてターミナルを開き実行。

sudo bash isoboot-grub.sh

永続的に/isoへマウントするなら

もし、ISOパーティションに複数のISOを保存しており、今後VMでもそのISOを利用するなら永続的に/isoへマウントする設定を実施します(ISO1個くらいなら一時的なマウントで良いと思うので不要)。
最初に行ったスクリプトを保存していたなら、それを実行。

sudo bash mount-setup.sh

Timeshift

スナップショットを保存するために。rsyncバージョンだとしてもこのあたりで一度取っておくと楽なのでは。

sudo apt install -y timeshift

一通りセットアップした後、ここで紹介している方法でVMなどを構築したなら、/opt以下も対象外にするように除外フォルダを追加しておくと良いかも。またLXDのストレージプール/var/snap/lxd/common/lxd/も外しておいて良いでしょう。

Google Chrome

# 必要なパッケージを準備
sudo apt update
sudo apt install curl gnupg

# GoogleのGPGキーを追加
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/google-chrome.gpg

# リポジトリを追加
echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/google-chrome.gpg] https://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list

# インストール
sudo apt update
sudo apt install google-chrome-stable

# 起動
google-chrome-stable

Yahoo!ニュースの動画やもしかしたらTVerなどの動画を見たいなら。Chromeウェブストアで下記拡張機能を検索してインストールします。

User-Agent Switcher and Manager

デスクトップにショートカットを置いたり、プロファイルごとのショートカットを作るならこちら

起動時にマウスカーソルが長時間クルクルするのを止めるには下記。

cp /usr/share/applications/google-chrome.desktop ~/.local/share/applications/ && sed -i 's/StartupNotify=true/StartupNotify=false/g' ~/.local/share/applications/google-chrome.desktop

code-server

これはセルフホスト版。そのパソコンで直接操作するなら普通にVSCodeをインストールしたほうが良いです。

sudo mkdir -p /opt/script
sudo chown $USER:$USER /opt/script
chmod u+rwx /opt/script
cd /opt/script
nano install-codeserver.sh
chmod +x install-codeserver.sh
sudo bash install-codeserver.sh
#!/bin/bash
# ============================================================
#  Code-Server インストール(Ubuntu 26.04 直接インストール版)
#  構成:
#    - code-server を localhost のみで待受
#    - tailscale serve で HTTPS化(tailnet 内のみ公開)
#    - Japanese Language Pack 自動インストール済み
#  前提:
#    - tailscale up 済み
#    - 実行: sudo bash install-codeserver2.sh
# ============================================================

set -euo pipefail

GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
info()    { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC}   $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC} $*"; }
error()   { echo -e "${RED}[ERR]${NC}  $*"; exit 1; }

[[ $EUID -ne 0 ]] && error "root権限で実行してください: sudo bash $0"

REAL_USER="${SUDO_USER:-${USER:-$(id -un)}}"
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
CODE_SERVER_PORT=8089
PASSWORD=""

# ============================================================
# ① パスワードを最初に聞く(デフォルト: M=手動入力)
# ============================================================
prompt_password() {
  echo ""
  echo -e "${CYAN}╔══════════════════════════════════════════════════════╗${NC}"
  echo -e "${CYAN}║   Code-Server + Tailscale Serve セットアップ         ║${NC}"
  echo -e "${CYAN}║   Ubuntu 26.04 / tailnet 内 HTTPS 公開               ║${NC}"
  echo -e "${CYAN}║   日本語化セットアップ済み                            ║${NC}"
  echo -e "${CYAN}╚══════════════════════════════════════════════════════╝${NC}"
  echo ""
  echo -e "インストール対象ユーザー: ${YELLOW}${REAL_USER}${NC} (${REAL_HOME})"
  echo ""
  echo "code-server のパスワードを設定してください。"
  echo -e "  ${CYAN}[A]${NC} 自動生成する(ランダム32文字)"
  echo -e "  ${CYAN}[M]${NC} 手動で入力する"
  echo ""
  read -r -p "選択 [A/M] (Enterで手動入力): " choice
  case "${choice^^}" in
    A)
      PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
      success "パスワードを自動生成しました。(後で表示します)"
      ;;
    *)
      while true; do
        read -r -s -p "🔑 パスワードを入力してください: " PASSWORD
        echo ""
        read -r -s -p "🔑 もう一度入力してください:     " PASSWORD2
        echo ""
        if [[ "$PASSWORD" == "$PASSWORD2" && -n "$PASSWORD" ]]; then
          success "パスワードを設定しました。"
          break
        else
          warn "パスワードが一致しないか空です。もう一度入力してください。"
        fi
      done
      ;;
  esac
  echo ""
}

# ============================================================
# ② Tailscale の動作確認
# ============================================================
check_prerequisites() {
  info "前提条件を確認中..."
  command -v tailscale &>/dev/null \
    || error "tailscale がインストールされていません。先に tailscale をセットアップしてください。"
  tailscale status &>/dev/null \
    || error "tailscale が起動していません。'tailscale up' を先に実行してください。"
  success "Tailscale 動作確認OK"
}

# ============================================================
# ③ 依存パッケージのインストール
# ============================================================
install_deps() {
  info "依存パッケージをインストール中..."
  apt-get update -qq
  apt-get install -y -qq curl wget openssl
  success "依存パッケージ インストール完了"
}

# ============================================================
# ④ code-server のインストール
# ============================================================
install_code_server() {
  info "最新バージョンを確認中..."
  LATEST=$(curl -fsSL https://api.github.com/repos/coder/code-server/releases/latest \
    | grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/')
  info "最新バージョン: v${LATEST}"

  DEB_URL="https://github.com/coder/code-server/releases/download/v${LATEST}/code-server_${LATEST}_amd64.deb"
  info "ダウンロード中..."
  wget -q --show-progress -O /tmp/code-server.deb "$DEB_URL"
  dpkg -i /tmp/code-server.deb
  rm /tmp/code-server.deb
  success "code-server v${LATEST} インストール完了"
}

# ============================================================
# ⑤ Japanese Language Pack のインストール
# ============================================================
install_japanese_language_pack() {
  info "Japanese Language Pack をインストール中..."

  # code-server の拡張機能ディレクトリを確保
  EXT_DIR="$REAL_HOME/.local/share/code-server/extensions"
  mkdir -p "$EXT_DIR"
  chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/.local/share/code-server"

  # 実ユーザーとして拡張機能をインストール
  sudo -u "$REAL_USER" code-server --install-extension MS-CEINTL.vscode-language-pack-ja \
    || error "Japanese Language Pack のインストールに失敗しました"

  success "Japanese Language Pack インストール完了"
}

# ============================================================
# ⑥ 設定ファイルの生成(日本語化設定込み)
# ============================================================
setup_config() {
  info "config.yaml を生成中..."
  CONFIG_DIR="$REAL_HOME/.config/code-server"
  mkdir -p "$CONFIG_DIR"
  cat > "$CONFIG_DIR/config.yaml" <<EOF
bind-addr: 127.0.0.1:${CODE_SERVER_PORT}
auth: password
password: ${PASSWORD}
cert: false
EOF
  chown -R "$REAL_USER:$REAL_USER" "$CONFIG_DIR"
  chmod 600 "$CONFIG_DIR/config.yaml"
  success "config.yaml 生成完了(127.0.0.1:${CODE_SERVER_PORT} でローカルのみ待受)"

  info "settings.json(日本語化 + 推奨設定)を配置中..."
  VSCODE_USER_DIR="$REAL_HOME/.local/share/code-server/User"
  mkdir -p "$VSCODE_USER_DIR"

  # argv.json: ロケール設定(日本語)
  cat > "$VSCODE_USER_DIR/../argv.json" <<'EOF'
{
  "locale": "ja"
}
EOF

  # locale.json: 表示言語の明示設定
  cat > "$VSCODE_USER_DIR/locale.json" <<'EOF'
{
  "locale": "ja"
}
EOF

  # settings.json: 基本的な快適設定
  cat > "$VSCODE_USER_DIR/settings.json" <<'EOF'
{
  "editor.fontSize": 14,
  "editor.tabSize": 2,
  "editor.formatOnSave": true,
  "editor.minimap.enabled": false,
  "workbench.colorTheme": "Default Dark Modern",
  "terminal.integrated.fontSize": 13,
  "files.autoSave": "afterDelay",
  "files.autoSaveDelay": 1000
}
EOF

  chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/.local/share/code-server"
  success "settings.json / argv.json / locale.json 配置完了"
}

# ============================================================
# ⑦ systemd サービスの登録・起動
# ============================================================
setup_systemd() {
  info "systemd サービスを登録中..."
  NLS_CONFIG='{"locale":"ja","osLocale":"ja","availableLanguages":{"*":"ja"}}'
  cat > /etc/systemd/system/code-server.service <<EOF
[Unit]
Description=code-server (VS Code in Browser)
After=network.target

[Service]
Type=exec
User=${REAL_USER}
WorkingDirectory=${REAL_HOME}
ExecStart=/usr/bin/code-server
Restart=always
RestartSec=5
Environment=HOME=${REAL_HOME}
Environment=VSCODE_NLS_CONFIG=${NLS_CONFIG}

[Install]
WantedBy=multi-user.target
EOF
  systemctl daemon-reload
  systemctl enable code-server
  systemctl restart code-server

  info "起動確認中..."
  sleep 4
  systemctl is-active --quiet code-server \
    && success "code-server 正常起動" \
    || { journalctl -u code-server -n 20 --no-pager; error "code-server の起動に失敗しました"; }
}

# ============================================================
# ⑧ Tailscale serve 設定(tailnet 内のみ HTTPS 公開)
# ============================================================
setup_tailscale_serve() {
  info "Tailscale serve に code-server (port ${CODE_SERVER_PORT}) を追加中..."

  tailscale serve --bg --https=${CODE_SERVER_PORT} "http://127.0.0.1:${CODE_SERVER_PORT}"
  success "Tailscale serve 設定完了(tailnet 内 HTTPS のみ)"

  TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c "
import sys, json
try:
    d = json.load(sys.stdin)
    s = d.get('Self', {})
    dns = s.get('DNSName', '').rstrip('.')
    print(dns if dns else s.get('HostName', 'unknown'))
except:
    print('unknown')
" 2>/dev/null || echo "unknown")

  echo ""
  echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${GREEN}  ✅ セットアップ完了!${NC}"
  echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo ""
  echo -e "  🌐 アクセスURL(tailnet 内のデバイスからのみアクセス可):"
  echo -e "     ${CYAN}https://${TS_HOSTNAME}:${CODE_SERVER_PORT}${NC}"
  echo ""
  echo -e "  🔑 code-server パスワード: ${YELLOW}${PASSWORD}${NC}"
  echo ""
  echo -e "  🧩 日本語化: インストール時に自動設定済み(再起動不要)"
  echo ""
  echo -e "  📋 管理コマンド:"
  echo -e "     ログ確認:       journalctl -u code-server -f"
  echo -e "     再起動:         sudo systemctl restart code-server"
  echo -e "     停止:           sudo systemctl stop code-server"
  echo -e "     serve 確認:     tailscale serve status"
  echo -e "     serve 削除:     tailscale serve --https=${CODE_SERVER_PORT} off"
  echo ""
  echo -e "  ⚠️  このパスワードを安全な場所に保管してください。"
  echo -e "     設定ファイル: ${REAL_HOME}/.config/code-server/config.yaml"
  echo ""
}

# ============================================================
# メイン
# ============================================================
main() {
  prompt_password        # ① パスワード設定(デフォルト: M)
  check_prerequisites    # ② Tailscale確認
  install_deps           # ③ 依存パッケージ
  install_code_server    # ④ code-server インストール
  install_japanese_language_pack  # ⑤ Japanese Language Pack インストール
  setup_config           # ⑥ 設定ファイル生成(日本語化込み)
  setup_systemd          # ⑦ systemd 登録・起動
  setup_tailscale_serve  # ⑧ Tailscale serve 設定
}

main "$@"

日本語化パックはインストール済みにしてありますが、もしなければExtensionsで「Japanese Language Pack for VS Code」をインストール。

Japanese Language Pack

日本語表示にする

こちらの設定が必要。上のバーで下記を入力して「日本語」を選択。

>Configure Display Language

階層をまとめない設定

設定」画面を開き、「機能」-「エクスプローラー」で、「CompactFolders」のチェックをOffにします。

Cockpit

#!/bin/bash
# ============================================================
# Cockpit を Tailscale serve 経由で HTTPS 公開するスクリプト
#   - BindTo + systemd socket で 127.0.0.1:19090 のみ待ち受け
#   - LAN からの直接アクセスはバインドアドレスで物理的に遮断
#   - TLS は tailscale serve が担当
# ============================================================
set -euo pipefail

# ============================================================
# ログ設定: /tmp/cockpit-setup.log に全出力を記録
# ============================================================
LOGFILE="/tmp/cockpit-setup.log"
exec > >(tee -a "$LOGFILE") 2>&1
echo "===== 開始: $(date '+%Y-%m-%d %H:%M:%S') =====" >> "$LOGFILE"

info()  { echo -e "\033[1;32m[INFO]\033[0m  $*"; }
warn()  { echo -e "\033[1;33m[WARN]\033[0m  $*"; }
error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }
step()  { echo -e "\033[1;36m[STEP]\033[0m  >>> $* <<<"; }

# エラー発生時に行番号とコマンドを表示して終了
trap 'echo -e "\n\033[1;31m[ABORT]\033[0m スクリプトが異常終了しました(行: ${LINENO}, コマンド: ${BASH_COMMAND})" | tee -a "$LOGFILE"; echo "ログ: $LOGFILE"' ERR

# ============================================================
# 前提チェック
# ============================================================
step "前提チェック"
command -v tailscale >/dev/null 2>&1 || error "tailscale が見つかりません。先にインストールしてください。"

info "tailscale の状態を確認しています..."

TS_INFO=$(tailscale status --json 2>/dev/null | python3 -c "
import sys, json
d = json.load(sys.stdin)
state = d.get('BackendState', '')
name  = d.get('Self', {}).get('DNSName', '').rstrip('.')
ip    = (d.get('Self', {}).get('TailscaleIPs') or [''])[0]
print(state)
print(name or ip)
" 2>/dev/null) || error "tailscale status の取得に失敗しました。"

TS_STATE=$(echo "$TS_INFO" | sed -n '1p')
TAILSCALE_HOST=$(echo "$TS_INFO" | sed -n '2p')

info "BackendState: ${TS_STATE}"
info "Tailscale ホスト: ${TAILSCALE_HOST}"

if [[ "$TS_STATE" != "Running" ]]; then
  error "tailscale が Running 状態ではありません(現在: ${TS_STATE})。'sudo tailscale up' を実行してください。"
fi
if [[ -z "$TAILSCALE_HOST" ]]; then
  error "Tailscale のホスト名/IPを取得できませんでした。"
fi

# ============================================================
# 1. Cockpit インストール
# ============================================================
step "1. Cockpit インストール"
info "Cockpit をインストールします..."
sudo apt install -y cockpit cockpit-machines

# ============================================================
# 2. cockpit.conf を配置(systemd 起動前に済ませる)
# ============================================================
step "2. cockpit.conf 配置"
info "Cockpit の設定を書き込みます..."

sudo mkdir -p /etc/cockpit
sudo tee /etc/cockpit/cockpit.conf > /dev/null << EOF
[WebService]
Origins = https://${TAILSCALE_HOST}:9090 https://localhost:9090
AllowUnencrypted = true
EOF

# ============================================================
# 3. systemd socket を 127.0.0.1:19090 に上書き
#
#    cockpit.conf の Port/BindTo より systemd socket の
#    ListenStream が優先されるため、socket 側で確実に制御する
# ============================================================
step "3. cockpit.socket オーバーライド設定"
info "cockpit.socket を 127.0.0.1:19090 に設定します..."

sudo mkdir -p /etc/systemd/system/cockpit.socket.d
sudo tee /etc/systemd/system/cockpit.socket.d/override.conf > /dev/null << EOF
[Socket]
ListenStream=
ListenStream=127.0.0.1:19090
EOF

# daemon-reload → socket を完全停止 → 再起動(設定確実反映のため stop が重要)
sudo systemctl daemon-reload
sudo systemctl stop cockpit.socket cockpit.service 2>/dev/null || true
sudo systemctl enable --now cockpit.socket

info "Cockpit を 127.0.0.1:19090 で起動しました。"

# 本当に 127.0.0.1:19090 だけで LISTEN しているか確認
LISTEN_CHECK=$(sudo ss -tlnp | grep "19090" || true)
if [[ -z "$LISTEN_CHECK" ]]; then
  warn "19090 で LISTEN していません。'sudo systemctl status cockpit.socket' を確認してください。"
elif echo "$LISTEN_CHECK" | grep -qv "127.0.0.1:19090"; then
  warn "19090 が 127.0.0.1 以外でも LISTEN しています!設定を確認してください。"
  echo "$LISTEN_CHECK"
else
  info "確認OK: 127.0.0.1:19090 のみで LISTEN しています。"
fi

# ============================================================
# 4. tailscale serve に :9090 → localhost:19090 を追加
#    既存の設定は保持する
# ============================================================
step "4. tailscale serve 設定"
info "tailscale serve の設定を確認・追加します..."

echo ""
echo "----- 既存の tailscale serve 設定 -----"
tailscale serve status 2>/dev/null || echo "(設定なし)"
echo "----------------------------------------"
echo ""

if tailscale serve status 2>/dev/null | grep -q "proxy http://localhost:19090"; then
  info ":9090 → localhost:19090 はすでに設定されています。スキップします。"
elif tailscale serve status 2>/dev/null | grep -q ":9090"; then
  warn ":9090 に別の設定があります。上書きします..."
  sudo tailscale serve --bg --https=9090 http://localhost:19090
else
  sudo tailscale serve --bg --https=9090 http://localhost:19090
  info "tailscale serve に :9090 を追加しました。"
fi

# funnel(インターネット公開)が有効なら警告
if tailscale funnel status 2>/dev/null | grep -q ":9090"; then
  warn "!!! :9090 に tailscale funnel が設定されています(インターネット公開状態)!!!"
  warn "無効化するには: sudo tailscale funnel --https=9090 off"
fi

# ============================================================
# 5. 動作確認
# ============================================================
step "5. 動作確認"
echo ""
info "最終確認..."
echo ""

echo "----- LISTEN ポート確認 -----"
sudo ss -tlnp | grep -E "19090|9090" || true

echo ""
echo "----- tailscale serve 設定 -----"
tailscale serve status

# ============================================================
# 6. libvirt デフォルトプール・ISOプールの設定
# ============================================================
step "6. libvirt プール設定"
echo ""
info "libvirt プールを設定します..."

# libvirtd の有効化
step "6a. libvirtd 起動"
sudo systemctl enable libvirtd
sudo systemctl start libvirtd
# libvirtd が完全に起動するまで少し待つ
sleep 2

# VM ストレージ用ディレクトリの準備
step "6b. /opt/vm ディレクトリ準備"
sudo mkdir -p /opt/vm
sudo chown root:libvirt /opt/vm
sudo chmod 2775 /opt/vm

# 実行ユーザーを libvirt グループへ追加
CURRENT_USER="${SUDO_USER:-$USER}"
sudo usermod -aG libvirt "$CURRENT_USER"
info "ユーザー '${CURRENT_USER}' を libvirt グループに追加しました(次回ログイン時に有効)。"

# --- デフォルトプールの再定義 ---
step "6c. default プール定義"
# virsh の既存 default プールを一旦削除して /opt/vm で再作成する
if sudo virsh pool-info default &>/dev/null; then
  warn "既存の 'default' プールを削除して再定義します..."
  sudo virsh pool-destroy default 2>/dev/null || true
  sudo virsh pool-undefine default
fi

sudo virsh pool-define-as default dir --target /opt/vm
sudo virsh pool-build default || warn "pool-build default は既存ディレクトリのためスキップ扱い(問題なし)"
sudo virsh pool-start default
sudo virsh pool-autostart default
info "デフォルトプール: /opt/vm で設定完了。"

# --- ISO プールの設定(/iso が存在する場合のみ) ---
step "6d. iso プール定義(/iso チェック)"
if [[ -d /iso ]]; then
  info "/iso ディレクトリを検出しました。ISO イメージ用プールを登録します..."

  if sudo virsh pool-info iso &>/dev/null; then
    warn "既存の 'iso' プールを削除して再定義します..."
    sudo virsh pool-destroy iso 2>/dev/null || true
    sudo virsh pool-undefine iso
  fi

  sudo virsh pool-define-as iso dir --target /iso
  sudo virsh pool-build iso || warn "pool-build iso は既存ディレクトリのためスキップ扱い(問題なし)"
  sudo virsh pool-start iso
  sudo virsh pool-autostart iso
  info "ISO プール: /iso で設定完了。"
else
  info "/iso ディレクトリが存在しないため、ISO プールの設定をスキップしました。"
  info "後から追加する場合: sudo mkdir -p /iso && sudo virsh pool-define-as iso dir --target /iso && sudo virsh pool-build iso && sudo virsh pool-start iso && sudo virsh pool-autostart iso"
fi

echo ""
echo "----- virsh プール一覧 -----"
sudo virsh pool-list --all

echo ""
echo "======================================================"
echo " セットアップ完了!"
echo "======================================================"
echo ""
echo " アクセス先(Tailnet 内のみ):"
echo "   https://${TAILSCALE_HOST}:9090/"
echo ""
echo " セキュリティ:"
echo "   - Cockpit バックエンド : 127.0.0.1:19090(LAN 到達不可)"
echo "   - TLS 終端            : tailscale serve (:9090)"
echo "   - インターネット公開  : tailscale funnel 未設定"
echo "   - UFW                 : 変更なし(既存サービス影響なし)"
echo ""
echo " libvirt プール:"
echo "   - default : /opt/vm(VM ディスクイメージ用)"
if [[ -d /iso ]]; then
  echo "   - iso     : /iso(ISO イメージ用)"
else
  echo "   - iso     : 未設定(/iso が存在しないためスキップ)"
fi
echo "======================================================"
echo ""
echo " ログファイル: $LOGFILE"
echo "======================================================"

一度再起動。

sudo reboot

virt-manager

Ubuntuデスクトップで使うならvirt-managerが高機能です。

sudo apt install -y virt-manager

VM Manager

Cockpit仮想マシンプラグインの置き換えを目指したツールで、仮想マシンプラグインでは行えない機能の追加などがやりやすくなっています。またこのあと説明するディスク拡張機能も組み込まれています。インストールはこちら

qcow2ディスクの拡張

sudo qemu-img resize /opt/vm/Win11.qcow2 +60G

qcow2ディスクの拡張をスクリプトで簡単に

cd /opt/vm
nano qcow2_resize.sh
chmod +x qcow2_resize.sh
bash qcow2_resize.sh
#!/bin/bash
# ============================================================
#  qcow2_resize.sh
#  qcow2 ファイル+スナップショットファイルを一覧・選択して拡張
# ============================================================

set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
BOLD='\033[1m'
RESET='\033[0m'

# ---- sudo プレフィックス(root でなければ sudo を使う)----
if [[ $EUID -ne 0 ]]; then
    SUDO="sudo"
else
    SUDO=""
fi

# ---- 依存確認 ---------------------------------------------
check_deps() {
    local missing=()
    for cmd in qemu-img find numfmt python3; do
        command -v "$cmd" &>/dev/null || missing+=("$cmd")
    done
    if [[ ${#missing[@]} -gt 0 ]]; then
        echo -e "${RED}エラー: 以下のコマンドが見つかりません: ${missing[*]}${RESET}"
        echo -e "  sudo apt install qemu-utils coreutils python3"
        exit 1
    fi
}

usage() {
    echo -e "${BOLD}使い方:${RESET} $0 [検索ディレクトリ]"
    exit 0
}
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && usage

SEARCH_DIR="${1:-.}"
[[ ! -d "$SEARCH_DIR" ]] && { echo -e "${RED}エラー: ディレクトリが見つかりません: $SEARCH_DIR${RESET}"; exit 1; }
SEARCH_DIR="$(realpath "$SEARCH_DIR")"

echo -e ""
echo -e "${CYAN}${BOLD}╔══════════════════════════════════════════╗${RESET}"
echo -e "${CYAN}${BOLD}║        qcow2 ディスク拡張スクリプト        ║${RESET}"
echo -e "${CYAN}${BOLD}╚══════════════════════════════════════════╝${RESET}"
echo -e "  検索対象: ${BOLD}${SEARCH_DIR}${RESET}"
if [[ -n "$SUDO" ]]; then
    echo -e "  ${YELLOW}root 以外で実行中のため qemu-img / resize に sudo を使用します${RESET}"
fi
echo -e ""

check_deps

# ---- ヘルパー: 仮想サイズ・実サイズ取得 -------------------
get_size_info() {
    local f="$1"
    local json vsize dsize vsize_h dsize_h
    # Permission denied 対策: sudo で実行、失敗しても処理継続
    json=$($SUDO qemu-img info --output=json "$f" 2>/dev/null || echo '{}')
    vsize=$(echo "$json" | python3 -c \
        "import sys,json; d=json.load(sys.stdin); print(d.get('virtual-size',0))" 2>/dev/null || echo 0)
    dsize=$(echo "$json" | python3 -c \
        "import sys,json; d=json.load(sys.stdin); print(d.get('actual-size',0))" 2>/dev/null || echo 0)
    vsize_h=$(numfmt --to=iec-i --suffix=B "$vsize" 2>/dev/null || echo "${vsize}B")
    dsize_h=$(numfmt --to=iec-i --suffix=B "$dsize" 2>/dev/null || echo "${dsize}B")
    echo "${vsize_h} ${dsize_h}"
}

# ---- ヘルパー: backing file パス取得 ----------------------
get_backing() {
    local f="$1"
    $SUDO qemu-img info --output=json "$f" 2>/dev/null \
        | python3 -c "
import sys, json
d = json.load(sys.stdin)
for key in ('full-backing-filename', 'backing-filename'):
    v = d.get(key, '')
    if v:
        print(v)
        break
else:
    print('')
" 2>/dev/null || echo ''
}

# ---- 除外する拡張子パターン --------------------------------
is_excluded() {
    local name
    name="$(basename "$1")"
    [[ "$name" =~ \.(iso|sh|img|raw|vmdk|vdi|vhd|vhdx|ova|ovf|log|conf|xml|json|txt|py|yaml|yml)$ ]] && return 0
    return 1
}

# ================================================================
#  ステップ1: .qcow2 ファイルをベースとして収集
# ================================================================
mapfile -t BASE_FILES < <(
    find "$SEARCH_DIR" -maxdepth 3 -type f -name "*.qcow2" | sort
)

if [[ ${#BASE_FILES[@]} -eq 0 ]]; then
    echo -e "${YELLOW}.qcow2 ファイルが見つかりませんでした。${RESET}"
    exit 0
fi

# ================================================================
#  ステップ2: 各ベースと同ディレクトリにある
#             "ベース名." プレフィックスのファイルをスナップとして収集
# ================================================================
declare -a ENTRIES=()
declare -a ENTRY_LABELS=()
declare -a ENTRY_TYPES=()

for base in "${BASE_FILES[@]}"; do
    base_dir="$(dirname "$base")"
    base_stem="$(basename "$base" .qcow2)"

    sizes=$(get_size_info "$base")
    vsize_h=$(echo "$sizes" | awk '{print $1}')
    dsize_h=$(echo "$sizes" | awk '{print $2}')

    ENTRIES+=("$base")
    ENTRY_TYPES+=("base")
    ENTRY_LABELS+=("$(printf '%-48s  仮想:%-9s 実:%-9s' \
        "$(basename "$base")" "$vsize_h" "$dsize_h")")

    # "ベース名." にマッチするファイルを列挙(.qcow2 本体を除く)
    while IFS= read -r candidate; do
        cname="$(basename "$candidate")"
        [[ "$cname" == "${base_stem}.qcow2" ]] && continue
        is_excluded "$candidate" && continue
        [[ ! -s "$candidate" ]] && continue

        snap_sizes=$(get_size_info "$candidate")
        sv_h=$(echo "$snap_sizes" | awk '{print $1}')
        sd_h=$(echo "$snap_sizes" | awk '{print $2}')

        ENTRIES+=("$candidate")
        ENTRY_TYPES+=("snap")
        ENTRY_LABELS+=("$(printf '  └─ %-44s  仮想:%-9s 実:%-9s' \
            "[snap] ${cname}" "$sv_h" "$sd_h")")

    done < <(find "$base_dir" -maxdepth 1 -type f -name "${base_stem}.*" | sort)
done

# ================================================================
#  ステップ3: 一覧表示
# ================================================================
echo -e "${BOLD}  番号  ファイル名                                              仮想サイズ  実サイズ${RESET}"
echo -e "  ───────────────────────────────────────────────────────────────────────────────────"

for i in "${!ENTRY_LABELS[@]}"; do
    if [[ "${ENTRY_TYPES[$i]}" == "snap" ]]; then
        color="$MAGENTA"
    else
        color="$RESET"
    fi
    printf "  ${CYAN}%3d${RESET}  ${color}%s${RESET}\n" "$((i+1))" "${ENTRY_LABELS[$i]}"
done

echo -e "  ───────────────────────────────────────────────────────────────────────────────────"
echo -e "  ${MAGENTA}紫: スナップショットファイル${RESET}  白: ベース (.qcow2)"
echo -e ""

# ================================================================
#  ステップ4: 番号選択
# ================================================================
while true; do
    read -rp "$(echo -e "  ${BOLD}拡張するファイルの番号 [1-${#ENTRIES[@]}]:${RESET} ")" sel
    if [[ "$sel" =~ ^[0-9]+$ ]] && (( sel >= 1 && sel <= ${#ENTRIES[@]} )); then
        break
    fi
    echo -e "  ${RED}無効な入力です。1〜${#ENTRIES[@]} の数値を入力してください。${RESET}"
done

IDX=$((sel-1))
TARGET_FILE="${ENTRIES[$IDX]}"
TARGET_TYPE="${ENTRY_TYPES[$IDX]}"

echo -e ""
echo -e "  選択: ${BOLD}$(basename "$TARGET_FILE")${RESET}"
echo -e "  パス: ${TARGET_FILE}"
if [[ "$TARGET_TYPE" == "snap" ]]; then
    backing=$(get_backing "$TARGET_FILE")
    echo -e "  ${MAGENTA}スナップショットファイル (backing: $(basename "${backing:-不明}"))${RESET}"
fi

# ================================================================
#  ステップ5: 拡大量の入力
# ================================================================
echo -e ""
echo -e "  ${BOLD}拡大するサイズを入力してください。${RESET}"
echo -e "  例: ${CYAN}10G${RESET} (+10 GiB)  ${CYAN}500M${RESET} (+500 MiB)  ${CYAN}2T${RESET} (+2 TiB)"

while true; do
    read -rp "$(echo -e "  ${BOLD}拡大量:${RESET} +")" size_input
    size_input="${size_input#+}"
    if [[ "$size_input" =~ ^[0-9]+(\.[0-9]+)?[MGTKmgtk]?$ ]]; then
        break
    fi
    echo -e "  ${RED}無効な形式です。例: 10G / 500M${RESET}"
done

ADD_SIZE="+${size_input}"

# ================================================================
#  ステップ6: 確認・実行
# ================================================================
echo -e ""
echo -e "  ${YELLOW}${BOLD}── 実行内容の確認 ──────────────────────────────────────${RESET}"
echo -e "  対象ファイル: ${BOLD}${TARGET_FILE}${RESET}"
echo -e "  拡大量      : ${BOLD}${ADD_SIZE}${RESET}"
echo -e "  ${YELLOW}${BOLD}────────────────────────────────────────────────────────${RESET}"
echo -e ""
read -rp "$(echo -e "  ${BOLD}実行しますか? [y/N]:${RESET} ")" confirm
[[ ! "$confirm" =~ ^[Yy]$ ]] && { echo -e "  ${YELLOW}キャンセルしました。${RESET}"; exit 0; }

before_vsize=$($SUDO qemu-img info --output=json "$TARGET_FILE" 2>/dev/null \
    | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('virtual-size',0))" 2>/dev/null || echo 0)

echo -e ""
echo -e "  ${CYAN}実行中...${RESET}"
$SUDO qemu-img resize "$TARGET_FILE" "$ADD_SIZE"

after_vsize=$($SUDO qemu-img info --output=json "$TARGET_FILE" 2>/dev/null \
    | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('virtual-size',0))" 2>/dev/null || echo 0)

before_h=$(numfmt --to=iec-i --suffix=B "$before_vsize" 2>/dev/null || echo "${before_vsize}B")
after_h=$(numfmt  --to=iec-i --suffix=B "$after_vsize"  2>/dev/null || echo "${after_vsize}B")

echo -e ""
echo -e "  ${GREEN}${BOLD}✔ 完了しました!${RESET}"
echo -e "  仮想サイズ: ${before_h} → ${BOLD}${GREEN}${after_h}${RESET}"
echo -e ""
echo -e "  ${YELLOW}ヒント: ゲスト OS 内でのパーティション拡張も忘れずに。${RESET}"
echo -e "  例(Linux): sudo growpart /dev/vda 1 && sudo resize2fs /dev/vda1"
echo -e ""

Windows 11の場合

マイクロソフトのサイトからWin11_25H2_Japanese_x64_v2.isoをダウンロードします。

mv Win11_25H2_Japanese_x64_v2.iso /opt/vm

次にvirtio-win ISOをダウンロードします。

cd /opt/vm
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso

WindowsのISOとvirtio-win.isoをマウントして、セットアップを開始します。なお、Cockpit用のVNCと、virt-viewer用のSPICEの両方を有効にしています。

nano vm-windows11-setup.sh
#!/bin/bash
VM_NAME="Windows11"
ISO_PATH="/opt/vm/Win11_25H2_Japanese_x64_v2.iso"
ISO2_PATH="/opt/vm/virtio-win.iso"
DISK_PATH="/opt/vm/Windows11.qcow2"
DISK_SIZE="64"
RAM="4096"
VCPUS="4"

qemu-img create -f qcow2 "$DISK_PATH" "${DISK_SIZE}G"

virt-install \
  --name "$VM_NAME" \
  --ram "$RAM" \
  --vcpus "$VCPUS" \
  --cpu host-passthrough \
  --os-variant win11 \
  --disk path="$DISK_PATH",format=qcow2,bus=virtio \
  --disk path="$ISO_PATH",device=cdrom,bus=sata \
  --disk path="$ISO2_PATH",device=cdrom,bus=sata \
  --network network=default,model=e1000e \
  --graphics spice,listen=127.0.0.1,gl.enable=no \
  --graphics vnc,listen=127.0.0.1 \
  --video qxl \
  --boot uefi,bootmenu.enable=yes,cdrom,hd \
  --features smm=on,kvm_hidden=on \
  --clock hypervclock_present=yes \
  --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb \
  --noautoconsole

echo "VM '$VM_NAME' を作成しました。"
bash vm-windows11-setup.sh

ゲストエージェントのインストール

virtio-win.isoが無いなら、サイトからゲストエージェントをダウンロード(現時点ではvirtio-win-guest-tools.exe)。SPICEを利用するならSPICE Guest Toolsをインストール。

ゲストエージェントをインストールしたらネットワークドライバをvirtioに変更。

パスワードを設定した場合の自動ログイン設定

コマンドプロンプトを管理者で実行してレジストリの設定を変更。

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device" /v DevicePasswordLessBuildVersion /t REG_DWORD /d 0 /f

パスワード設定画面を表示して設定。

control userpasswords2

デスクトップアイコンを表示するならこちら

Ubuntu 26.04 デスクトップ版の場合

下記で作成(–os-variantは26.04がまだ登録されていないため)。ちなみにISOブート用に保存したISOイメージを使用するならsudo mount /dev/nvme0n1p3 /isoで一時的にマウントしてISO_PATHを/iso/ubuntu-26.04-desktop-amd64.isoなどに。永続的にマウントするならこちらで設定。

cd /opt/vm
wget https://releases.ubuntu.com/26.04/ubuntu-26.04-desktop-amd64.iso
nano vm-ubuntu2604-setup.sh
#!/bin/bash
VM_NAME="Ubuntu2604"
ISO_PATH="/opt/vm/ubuntu-26.04-desktop-amd64.iso"
DISK_PATH="/opt/vm/Ubuntu2604.qcow2"
DISK_SIZE="60"
RAM="6144"
VCPUS="4"

qemu-img create -f qcow2 "$DISK_PATH" "${DISK_SIZE}G"

virt-install \
  --name "$VM_NAME" \
  --ram "$RAM" \
  --vcpus "$VCPUS" \
  --cpu host-passthrough \
  --os-variant ubuntu25.10 \
  --disk path="$DISK_PATH",format=qcow2,bus=virtio \
  --disk path="$ISO_PATH",device=cdrom,bus=sata \
  --network network=default,model=virtio \
  --graphics spice,listen=127.0.0.1,gl.enable=no \
  --graphics vnc,listen=127.0.0.1 \
  --video virtio \
  --boot uefi,bootmenu.enable=yes,cdrom,hd \
  --noautoconsole

echo "VM '$VM_NAME' を作成しました。"
bash vm-ubuntu2604-setup.sh

ゲストエージェントをインストール

spice-vdagentの確認(多分不要)

# インストール済みか確認
dpkg -l spice-vdagent
# サービスの状態確認
systemctl status spice-vdagent
# 実行中のプロセス確認
pgrep -a vdagent

インストールされていなかった場合

sudo apt install spice-vdagent

qemu-guest-agentの確認(こちらは多分必要)

# インストール済みか確認
dpkg -l qemu-guest-agent

# サービスの状態確認
systemctl status qemu-guest-agent

# 実行中のプロセス確認
pgrep -a qemu-ga

インストールされていなかった場合

sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent

Ubuntu 26.04 サーバ版の場合

cd /opt/vm
wget https://releases.ubuntu.com/26.04/ubuntu-26.04-live-server-amd64.iso
nano vm-ubuntu2604sv-setup.sh
#!/bin/bash
VM_NAME="Ubuntu2604sv"
ISO_PATH="/opt/vm/ubuntu-26.04-live-server-amd64.iso"
DISK_PATH="/opt/vm/Ubuntu2604sv.qcow2"
DISK_SIZE="60"
RAM="6144"
VCPUS="4"

qemu-img create -f qcow2 "$DISK_PATH" "${DISK_SIZE}G"

virt-install \
  --name "$VM_NAME" \
  --ram "$RAM" \
  --vcpus "$VCPUS" \
  --cpu host-passthrough \
  --os-variant ubuntu25.10 \
  --disk path="$DISK_PATH",format=qcow2,bus=virtio \
  --disk path="$ISO_PATH",device=cdrom,bus=sata \
  --network network=default,model=virtio \
  --graphics spice,listen=127.0.0.1,gl.enable=no \
  --graphics vnc,listen=127.0.0.1 \
  --video vga \
  --boot uefi,bootmenu.enable=yes,cdrom,hd \
  --noautoconsole

echo "VM '$VM_NAME' を作成しました。"
bash vm-ubuntu2604sv-setup.sh

LXD-UI

#!/bin/bash
set -e
# LXD インストール
sudo snap install lxd --channel=latest/stable
# 初期化
sudo lxd init --minimal
# HTTPS API を有効化
sudo lxc config set core.https_address :8443
# UI 有効化
sudo snap set lxd ui.enable=true
sudo systemctl reload snap.lxd.daemon
# ユーザーを lxd グループに追加
sudo usermod -aG lxd $USER
echo ""
echo "=========================================="
echo " LXD-UI セットアップ完了"
echo "=========================================="
echo " アクセス先:"
echo "   https://$(hostname):8443"
echo "=========================================="
echo ""
echo "※ グループ変更を反映するため、一度ログアウト&再ログインしてください"
sudo reboot

6個のスクリプトを一度に作成して保存するスクリプト

LXDコンテナの操作に役立つ6つのスクリプトを作りました。より新しいバージョンを作ったので、スクリプトの作成はこちらで。

スクリプトを利用してベースコンテナを作成するなら次のコマンドで。

cd /opt/script/lxd/

# ベースコンテナを作成
./minimal-lxd-base-create.sh

# 作成したコンテナ内をアップデートしてTailscaleをインストール
./first-setup-minimal-lxd-base.sh

# そのコンテナをコピーしDocker環境のコンテナを作成
./docker-lxd-base-create.sh

以後は、いずれかのコンテナをコピーして利用します。

./copy-lxd-create.sh

コピー後にコンテナに入りますが、一度出た環境でも、LXD-UIでターミナルに入って作業しても良いですし、下記でコンテナを選択して入れます。

./enter-lxd-container.sh

コピーしたコンテナに入ったら下記で認証しておきましょう。

tailscale up

exitで一度出て、スナップショットを取っておくと安心。

./snapshot-lxd.sh

LXD操作やコンテナ管理を手軽に行える「EasyLXD」

上記のような、ホストにディレクトリをマウントしてコンテナを作成したい場合などに便利な簡易Web UIも用意しました。こちらがあれば上記スクリプトがなくても手軽にコンテナ作成などが行えると思います。導入はこちら

Filebrowser

LXDコンテナに入れておくと便利。導入方法はこちら

Copyparty

Filebrowserと同じようなサービスですが、自分用で行うならこちらのほうが便利かも。導入方法はこちら

nextExplorer & OnlyOffice

こちらもFilebrowserと同じようなサービスですが、見た目が良いので他人にも使わせるならこちらが良いでしょう。OnlyOfficeと連携もすれば、使い方によってはNextcloudから置き換えられると思います。導入方法はこちら

Linkwarden

インストールはこちらの記事を参考に。
日々のバックアップはエクスポート機能を使用した方法で。
別環境に移行する時などはフルバックアップで。

Karakeep

上のLinkwardenのようなブックマーク管理サービス。こちらはサクサク検索が行えます。
インストールやバックアップ・復元はこちら

selfmark

ブックマーク管理に特化したシンプルなバージョンが必要ならこちら

Dockhand

こちらの記事を参考にインストール。

Outline

こちらを参考にインストールと定期的エクスポートを設定。

AFFiNE

Outlineのようなナレッジツール。多機能でクライアントソフトも用意されている点が魅力。エクスポート機能がない点だけ残念で、これがあればOutlineから乗り換えても良いかなという状況なんですけれど。
インストールやバックアップ・復元はこちら

Memos

OutlineやAFFiNEほど多機能なものは不要で手軽にメモするならこちら。手軽さ重視で、Tailscale serveせず、マジックDNSでアクセス出来るようにしています。

EasyNote

.mdファイルで管理するシンプルなMarkdown対応ノートアプリ。ファイル管理が楽です。インストールはこちら

Immich

インストールはこちら
普段の写真バックアップはSyncthingを使用して同期
環境移行時などのフルバックアップはこちら

Vaultwarden

こちらを参考にインストール。
バックアップ・復元スクリプトはこちら

SPW

アプリが無くなってもファイル自体を取り出しやすいシンプルなパスワードマネージャが良いならこちら

Nextcloud&OnlyOffice

こちらを参考にインストール。バックアップ・復元スクリプトもあります。

FreshRSS

こちらを参考にインストール。
バックアップや復元を行うならこちら

Syncthing

こちらを参考にインストール。

opencode

こちらを参考にインストール。

KonomiTV

Mirakurun+EDCB+KonomiTVはこちら

mirakc(Docker)+EDCB+KonomiTVはこちら

mirakc(Docker不使用)+EDCB+KonomiTVならこちら

タイトルとURLをコピーしました