Ubuntu26.04直前、セットアップ後の流れ確認

Ubuntu26.04の正式リリースが目前に迫っているので、ここで一度セットアップの流れを確認してみました。
4月13日版のISOを利用しVM上で動作確認しています。

Tailscale

# アップデート
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

以降はSSHで。

SSH接続時にエラーの場合

下記の表示でエラーが出た場合。何度も接続先をやり直しているとローカルにある情報と違うための警告が表示されることがあります。
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! 
この場合、ローカルにある情報を削除して接続すれば繋がるはず。

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

Ubuntu Desktopをサーバにする場合

設定画面で「リモートデスクトップ」-「デスクトップ共有」を有効化。キーリングは空のまま保存。

自動画面ロックを無効化

# 自動画面ロックを無効化
gsettings set org.gnome.desktop.screensaver lock-enabled false

# 自動スリープ(画面オフ)も無効にする場合
gsettings set org.gnome.desktop.session idle-delay 0

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

デスクトップ共有の場合、ロックされてもアクセス出来るように。
まず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
sudo reboot

Taildrop

送信用

sudo tailscale set --operator=$USER && sudo mkdir -p /opt/script && sudo tee /opt/script/taildrop-send.sh << 'EOF'
#!/usr/bin/env bash
# taildrop-send.sh — Taildropでファイルを送信するスクリプト

set -euo pipefail

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

info()    { echo -e "${CYAN}${BOLD}→${RESET} $*"; }
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
warn()    { echo -e "${YELLOW}${BOLD}!${RESET} $*"; }
error()   { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
sep()     { echo -e "${DIM}$(printf '─%.0s' {1..52})${RESET}"; }

check_dependencies() {
    if ! command -v tailscale &>/dev/null; then
        error "tailscale コマンドが見つかりません。"
        echo "  インストール: https://tailscale.com/download/linux"
        exit 1
    fi
    if ! tailscale status &>/dev/null; then
        error "Tailscale が起動していません。"
        echo "  起動: sudo systemctl start tailscaled && sudo tailscale up"
        exit 1
    fi
}

select_file() {
    echo ""
    echo -e "${BOLD}📁 現在のフォルダのファイル一覧${RESET}  ${DIM}$(pwd)${RESET}"
    sep

    mapfile -t FILES < <(find . -maxdepth 1 -type f ! -name '.*' | sed 's|^\./||' | sort)

    if [[ ${#FILES[@]} -eq 0 ]]; then
        error "ファイルが見つかりません: $(pwd)"
        exit 1
    fi

    local i=1
    for f in "${FILES[@]}"; do
        local size
        size=$(du -sh "$f" 2>/dev/null | cut -f1)
        printf "  ${CYAN}%3d${RESET}  %-42s ${DIM}%s${RESET}\n" "$i" "$f" "$size"
        ((i++))
    done
    sep
    echo ""

    while true; do
        read -rp "$(echo -e "送信するファイルの番号を入力 ${DIM}[1-${#FILES[@]}]${RESET}: ")" file_choice
        if [[ "$file_choice" =~ ^[0-9]+$ ]] && \
           (( file_choice >= 1 && file_choice <= ${#FILES[@]} )); then
            SELECTED_FILE="${FILES[$((file_choice - 1))]}"
            success "選択したファイル: ${BOLD}${SELECTED_FILE}${RESET}"
            break
        else
            warn "無効な番号です。1〜${#FILES[@]} の範囲で入力してください。"
        fi
    done
}

select_target() {
    echo ""
    echo -e "${BOLD}💻 Tailnetのデバイス一覧${RESET}"
    sep

    local self_ip
    self_ip=$(tailscale ip -4 2>/dev/null || echo "SELF")

    mapfile -t STATUS_LINES < <(
        tailscale status 2>/dev/null \
        | awk 'NF>=2 && $1!~/^#/ && !/^$/' \
        | grep -v "^${self_ip}" || true
    )

    declare -a PEER_NAMES=()
    declare -a PEER_IPS=()
    declare -a PEER_STATUS=()

    for line in "${STATUS_LINES[@]}"; do
        local ip hostname rest
        ip=$(echo "$line" | awk '{print $1}')
        hostname=$(echo "$line" | awk '{print $2}')
        rest=$(echo "$line" | cut -d' ' -f3-)

        local flag
        if echo "$rest" | grep -qwi 'offline'; then
            flag="オフライン"
        else
            flag="オンライン"
        fi

        PEER_NAMES+=("$hostname")
        PEER_IPS+=("$ip")
        PEER_STATUS+=("$flag")
    done

    if [[ ${#PEER_NAMES[@]} -eq 0 ]]; then
        error "接続可能なデバイスが見つかりません。"
        exit 1
    fi

    local i=1
    for idx in "${!PEER_NAMES[@]}"; do
        local color="${DIM}"
        [[ "${PEER_STATUS[$idx]}" == "オンライン" ]] && color="${GREEN}"
        printf "  ${CYAN}%3d${RESET}  %-30s ${DIM}%-20s${RESET} ${color}%s${RESET}\n" \
            "$i" "${PEER_NAMES[$idx]}" "${PEER_IPS[$idx]}" "${PEER_STATUS[$idx]}"
        ((i++))
    done
    sep
    echo ""

    while true; do
        read -rp "$(echo -e "送信先の番号を入力 ${DIM}[1-${#PEER_NAMES[@]}]${RESET}: ")" target_choice
        if [[ "$target_choice" =~ ^[0-9]+$ ]] && \
           (( target_choice >= 1 && target_choice <= ${#PEER_NAMES[@]} )); then
            SELECTED_TARGET="${PEER_NAMES[$((target_choice - 1))]}"
            SELECTED_IP="${PEER_IPS[$((target_choice - 1))]}"
            success "送信先: ${BOLD}${SELECTED_TARGET}${RESET} (${SELECTED_IP})"
            break
        else
            warn "無効な番号です。1〜${#PEER_NAMES[@]} の範囲で入力してください。"
        fi
    done
}

send_file() {
    echo ""
    sep
    echo -e "  ${BOLD}送信内容の確認${RESET}"
    sep
    printf "  %-10s ${BOLD}%s${RESET}\n" "ファイル:" "${SELECTED_FILE}"
    printf "  %-10s ${BOLD}%s${RESET} ${DIM}(%s)${RESET}\n" "送信先:" "${SELECTED_TARGET}" "${SELECTED_IP}"
    sep
    echo ""

    read -rp "$(echo -e "送信しますか? ${DIM}[y/N]${RESET}: ")" confirm
    [[ "$confirm" =~ ^[yY] ]] || { echo ""; warn "キャンセルしました。"; exit 0; }

    echo ""
    info "送信中..."

    if tailscale file cp "$SELECTED_FILE" "${SELECTED_TARGET}:" 2>/tmp/_taildrop_err; then
        echo ""
        success "送信完了: ${BOLD}${SELECTED_FILE}${RESET} → ${BOLD}${SELECTED_TARGET}${RESET}"
    else
        local errmsg
        errmsg=$(cat /tmp/_taildrop_err 2>/dev/null || echo "")
        if echo "$errmsg" | grep -qi "access denied\|permission denied"; then
            warn "権限エラー検出 → sudo で再試行します..."
            if sudo tailscale file cp "$SELECTED_FILE" "${SELECTED_TARGET}:"; then
                echo ""
                success "送信完了: ${BOLD}${SELECTED_FILE}${RESET} → ${BOLD}${SELECTED_TARGET}${RESET}"
            else
                echo ""
                error "送信に失敗しました(sudo でも失敗)。"
                echo "$errmsg"
                exit 1
            fi
        else
            echo ""
            error "送信に失敗しました。"
            echo "$errmsg"
            exit 1
        fi
    fi
    echo ""
}

main() {
    echo ""
    echo -e "  ${BOLD}${CYAN}Taildrop ファイル送信ツール${RESET}"
    echo ""
    check_dependencies
    select_file
    select_target
    send_file
}

main "$@"
EOF
sudo chmod +x /opt/script/taildrop-send.sh && echo "セットアップ完了"

あとは、送信したいファイルがあるフォルダで下記を実施します。

bash /opt/script/taildrop-send.sh

受信用

受け取ったデータを/opt/lxd-data/taildropに保存することで、どのコンテナからも参照出来ます。サービス化で再起動しても自動実行されます。

TARGET_DIR="/opt/lxd-data/taildrop"

# ディレクトリが存在しない場合は作成
sudo mkdir -p "$TARGET_DIR"

# サービスファイル作成(sudo tee を使う)
sudo tee /etc/systemd/system/taildrop.service > /dev/null <<EOF
[Unit]
Description=Tailscale File Receiver (Taildrop)
After=network.target tailscaled.service
Requires=tailscaled.service

[Service]
Type=simple
ExecStart=tailscale file get --loop --conflict=rename $TARGET_DIR
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

# 有効化・起動
sudo systemctl daemon-reload
sudo systemctl enable taildrop
sudo systemctl start taildrop

# 確認(Ctrl+Cで終了)
sudo systemctl status taildrop

LXD

LXD-UI

まずは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

# マジックDNS名を取得してアクセス先を表示
echo ""
echo "=========================================="
echo " LXD-UI セットアップ完了"
echo "=========================================="

# ホスト自身のマジックDNS名を取得(Multipass環境の場合 .local、通常はhostname)
HOSTNAME=$(hostname)
MAGIC_DNS="${HOSTNAME}"

echo " アクセス先:"
echo "   https://${MAGIC_DNS}:8443"
echo "=========================================="
echo ""
echo "※ グループ変更を反映するため、一度ログアウト&再ログインしてください"
sudo reboot

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

コピーボタンでまとめてコピーして、ターミナルに貼り付けて実行するだけです。
実行後、以下の6ファイルが /opt/script/lxd/ に作成され、実行権限も付与されます。

生成される6つのスクリプトの内容:

ファイル名内容
minimal-lxd-base-create.shコンテナ作成・マウントディレクトリチェック付き
first-setup-minimal-lxd-base.shapt update/upgrade・curl・Tailscaleインストール後シャットダウン
docker-lxd-base-create.shlxd-base-minimalをコピーしてDocker環境構築・シャットダウン
copy-lxd-create.sh既存コンテナをコピーして新規作成
snapshot-lxd.shスナップショット作成
enter-lxd-container.shコンテナ選択して入る
#!/bin/bash
set -euo pipefail

sudo mkdir -p /opt/script/lxd

cat > /tmp/minimal-lxd-base-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

CONTAINER="lxd-base-minimal"
MOUNT_PATH="/opt/lxd-data"

echo "=== lxd-base-minimal コンテナを作成 ==="
lxc launch ubuntu:24.04 "$CONTAINER"

echo "=== $MOUNT_PATH が存在しない場合は作成 ==="
if [ ! -d "$MOUNT_PATH" ]; then
    sudo mkdir -p "$MOUNT_PATH"
    if [ ! -d "$MOUNT_PATH" ]; then
        echo "エラー: $MOUNT_PATH の作成に失敗しました"
        exit 1
    fi
    echo "    $MOUNT_PATH を作成しました"
else
    echo "    $MOUNT_PATH は既に存在します"
fi

echo "=== ホストの $MOUNT_PATH をコンテナに同じパスでマウント ==="
lxc config device add "$CONTAINER" opt-lxd-data disk source="$MOUNT_PATH" path="$MOUNT_PATH"

echo "=== ID マッピング設定を適用 ==="
lxc config set "$CONTAINER" raw.idmap "both 1000 1000"

echo "=== コンテナを再起動します ==="
lxc restart "$CONTAINER"

echo "=== 完了しました ==="
EOF

cat > /tmp/first-setup-minimal-lxd-base.sh << 'EOF'
#!/bin/bash
set -euo pipefail

CONTAINER="lxd-base-minimal"

echo "==> コンテナ '${CONTAINER}' に接続してセットアップを開始します..."

lxc exec "${CONTAINER}" -- bash -euo pipefail << 'INNER'
echo "==> apt update"
sudo apt update

echo "==> apt upgrade"
sudo apt upgrade -y

echo "==> curl インストール"
sudo apt install -y curl

echo "==> Tailscale インストール"
curl -fsSL https://tailscale.com/install.sh | sh

echo "==> セットアップ完了"
INNER

echo "==> コンテナをシャットダウンします..."
lxc stop "${CONTAINER}"

echo "==> 完了"
EOF

cat > /tmp/docker-lxd-base-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

SRC="lxd-base-minimal"
NEW="lxd-base-docker"
MOUNT_PATH="/opt/lxd-data"

echo "=== コンテナをコピー: $SRC → $NEW ==="
lxc copy "$SRC" "$NEW"

echo "=== コンテナを停止します(設定適用のため) ==="
lxc stop "$NEW" 2>/dev/null || true

echo "=== raw.idmap を設定 ==="
lxc config set "$NEW" raw.idmap "both 1000 1000"

echo "=== Nesting を Allow に設定 ==="
lxc config set "$NEW" security.nesting true

echo "=== コンテナを起動 ==="
lxc start "$NEW"

echo "=== マウントディレクトリの権限設定(ホスト側) ==="
sudo chown 1000:1000 "$MOUNT_PATH"
sudo chmod 775 "$MOUNT_PATH"

echo "=== 設定反映のため再起動 ==="
lxc restart "$NEW"

echo "=== ネットワーク疎通を待機中 ==="
for i in $(seq 1 30); do
    if lxc exec "$NEW" -- curl -fsSL --max-time 3 https://get.docker.com -o /dev/null 2>/dev/null; then
        echo "    ネットワーク疎通確認 (${i}秒)"
        break
    fi
    echo "    待機中... (${i}/30秒)"
    sleep 1
    if [ "$i" -eq 30 ]; then
        echo "エラー: ネットワークが30秒以内に疎通しませんでした" >&2
        exit 1
    fi
done

echo "=== コンテナ内でセットアップを実行 ==="
lxc exec "$NEW" -- bash -euo pipefail << 'INNER'

echo "--- Docker インストール ---"
curl -fsSL https://get.docker.com | sh

echo "--- /opt/docker ディレクトリのセットアップ ---"
mkdir -p /opt/docker

if getent group docker > /dev/null 2>&1; then
    chown -R "${USER:-root}:docker" /opt/docker
    chmod -R 775 /opt/docker
    chmod -R g+s /opt/docker
    if [ "${USER:-root}" != "root" ]; then
        usermod -aG docker "$USER"
        echo "グループ変更を反映するには、newgrp docker を実行してください(再起動不要)"
    else
        echo "rootユーザーのため usermod はスキップしました"
    fi
else
    chown -R "${USER:-root}:${USER:-root}" /opt/docker
    chmod -R 755 /opt/docker
    echo "docker グループが存在しないため、オーナー権限のみ設定しました"
fi

echo "/opt/docker のセットアップ完了"
INNER

echo "=== コンテナをシャットダウン ==="
lxc stop "$NEW"

echo "=== 完了: $NEW ==="
EOF

cat > /tmp/copy-lxd-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

containers=($(lxc list -c n --format csv))

echo "=== コピー元のコンテナを選択してください ==="
i=1
for c in "${containers[@]}"; do
    echo "$i) $c"
    ((i++))
done

read -p "番号を入力: " index

if ! [[ "$index" =~ ^[0-9]+$ ]]; then
    echo "エラー: 数字を入力してください" >&2
    exit 1
fi

index=$((index - 1))

if [ "$index" -lt 0 ] || [ "$index" -ge "${#containers[@]}" ]; then
    echo "エラー: 不正な番号です" >&2
    exit 1
fi

SRC="${containers[$index]}"
echo "選択されたコピー元: $SRC"

read -p "新しいコンテナ名を入力: " NEW

if [ -z "$NEW" ]; then
    echo "エラー: コンテナ名が空です" >&2
    exit 1
fi

echo "=== コピー開始: $SRC → $NEW ==="
lxc copy "$SRC" "$NEW"

echo "=== 一旦コンテナを停止します(ID マップ適用のため) ==="
lxc stop "$NEW" 2>/dev/null || true

echo "=== raw.idmap を設定します ==="
lxc config set "$NEW" raw.idmap "both 1000 1000"

echo "=== コンテナを起動します ==="
lxc start "$NEW"

echo "=== 設定反映のため再起動 ==="
lxc restart "$NEW"

echo "=== コンテナに入ります: $NEW ==="
lxc exec "$NEW" -- bash
EOF

cat > /tmp/snapshot-lxd.sh << 'EOF'
#!/bin/bash
set -euo pipefail

echo "=== LXD コンテナ一覧 ==="

# コンテナ名一覧を配列に格納
containers=($(lxc list -c n --format csv))

# 一覧表示
i=1
for c in "${containers[@]}"; do
    echo "$i) $c"
    ((i++))
done

# 番号選択
read -p "番号を入力: " index

# 数字チェック
if ! [[ "$index" =~ ^[0-9]+$ ]]; then
    echo "エラー: 数字を入力してください" >&2
    exit 1
fi

index=$((index - 1))

# 範囲チェック
if [ "$index" -lt 0 ] || [ "$index" -ge "${#containers[@]}" ]; then
    echo "エラー: 不正な番号です" >&2
    exit 1
fi

TARGET="${containers[$index]}"
echo "選択されたコンテナ: $TARGET"

# コメント入力
read -p "スナップショットのコメントを入力(任意・空可): " COMMENT

# スペースをハイフンに変換
COMMENT="${COMMENT// /-}"

# スナップショット名(コメントがあれば付加)
SNAP="snap-$(date +%Y%m%d-%H%M%S)"
if [ -n "$COMMENT" ]; then
    SNAP="${SNAP}-${COMMENT}"
fi

echo "=== コンテナ停止: $TARGET ==="
lxc stop "$TARGET" 2>/dev/null || true

echo "=== スナップショット作成: $TARGET/$SNAP ==="
lxc snapshot "$TARGET" "$SNAP"

echo "=== コンテナ起動: $TARGET ==="
lxc start "$TARGET"

echo "=== 完了しました ==="
echo "作成されたスナップショット: $TARGET/$SNAP"
EOF

cat > /tmp/enter-lxd-container.sh << 'EOF'
#!/bin/bash
set -euo pipefail

echo "=== LXD コンテナ一覧 ==="

containers=($(lxc list -c n --format csv))

i=1
for c in "${containers[@]}"; do
    echo "$i) $c"
    ((i++))
done

read -p "番号を入力: " index

if ! [[ "$index" =~ ^[0-9]+$ ]]; then
    echo "エラー: 数字を入力してください" >&2
    exit 1
fi

index=$((index - 1))

if [ "$index" -lt 0 ] || [ "$index" -ge "${#containers[@]}" ]; then
    echo "エラー: 不正な番号です" >&2
    exit 1
fi

TARGET="${containers[$index]}"
echo "選択されたコンテナ: $TARGET"

STATUS=$(lxc info "$TARGET" | awk '/Status:/ {print tolower($2)}')

if [ "$STATUS" != "running" ]; then
    echo "=== コンテナが停止中です。起動します ==="
    lxc start "$TARGET"
else
    echo "=== コンテナは既に起動中です ==="
fi

echo "=== コンテナに入ります: $TARGET ==="
lxc exec "$TARGET" -- bash
EOF

sudo mv /tmp/minimal-lxd-base-create.sh /opt/script/lxd/
sudo mv /tmp/first-setup-minimal-lxd-base.sh /opt/script/lxd/
sudo mv /tmp/docker-lxd-base-create.sh /opt/script/lxd/
sudo mv /tmp/copy-lxd-create.sh /opt/script/lxd/
sudo mv /tmp/snapshot-lxd.sh /opt/script/lxd/
sudo mv /tmp/enter-lxd-container.sh /opt/script/lxd/

sudo chmod +x /opt/script/lxd/*.sh
sudo chown user:user /opt/script/lxd/*.sh

echo "=== 完了 ==="
ls -la /opt/script/lxd/

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

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

Cockpit

VMが必要ならCockpitが便利なのでホストにインストール。Tailscale Serveで、Chrome利用時でもパスワードが保存されるようにしています。

#!/bin/bash
set -e

# --- Cockpit + Tailscale セットアップ ---
sudo apt install -y nano cockpit

# 仮想マシンプラグイン
sudo apt install -y cockpit-machines

# --- Tailscale Serve 設定 ---

sudo tailscale serve --bg http://localhost:9090

# Tailscaleのフルドメイン名を自動取得
TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json, sys
data = json.load(sys.stdin)
self = data.get('Self', {})
dns = self.get('DNSName', '').rstrip('.')
print(dns)
")

# CockpitのCSPにTailscaleドメインを許可(HTTPSアクセス対応)
sudo mkdir -p /etc/cockpit
sudo tee /etc/cockpit/cockpit.conf << EOF
[WebService]
AllowUnencrypted=true
Origins = https://${TAILSCALE_DOMAIN}
EOF

sudo systemctl restart cockpit

echo ""
echo "======================================"
echo " セットアップ完了!"
echo "======================================"
echo " アクセス先: https://${TAILSCALE_DOMAIN}"
echo "======================================"

デフォルトの場所(/var/lib/libvirt/images)よりもアクセスしやすいように変更。
Cockpitにアクセスし、仮想マシンページを開いたあとに下記を実行。

# ディレクトリ作成
sudo mkdir -p /opt/vm

# グループをlibvirtに統一
sudo chown root:libvirt /opt/vm

# setgidビット付与
sudo chmod 2775 /opt/vm

# 自分をlibvirtグループに追加
sudo usermod -aG libvirt $USER

# 再起動後も自動起動されるよう登録
sudo systemctl enable libvirtd

一度再起動。

sudo reboot

再ログイン後に実行。

# デフォルトプールとして登録
virsh pool-define-as default dir --target /opt/vm
virsh pool-build default
virsh pool-start default
virsh pool-autostart default

ISOをコピー

mv ~/iso/*.iso /opt/vm

Windows 11の場合

マイクロソフトのサイトからダウンロードしたWin11_25H2_Japanese_x64_v2.iso/opt/vmにある想定。

## Windows 11 ISOの再パッケージ化
# 依存パッケージのインストール
sudo apt update
sudo apt install -y libhivex-bin libwin-hivex-perl wimtools genisoimage \
    golang-go git make

## distrobuilderのビルド
# ソースをクローン&ビルド
git clone https://github.com/lxc/distrobuilder
cd distrobuilder
make

# バイナリをコピー
sudo cp ~/go/bin/distrobuilder /usr/local/bin/distrobuilder

# 確認
distrobuilder --version

# virtioドライバ統合のためISOを再パッケージ
cd /opt/vm

# virtio-win ISOをダウンロード
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso

# virtioドライバ統合済みISOを再パッケージ
sudo distrobuilder repack-windows Win11_25H2_Japanese_x64_v2.iso Win11-v.iso \
    --drivers=/opt/vm/virtio-win.iso

virtioドライバ統合ISOを使用してセットアップ

#!/bin/bash
VM_NAME="Win11"
ISO_PATH="/opt/vm/Win11-v.iso"
DISK_PATH="/opt/vm/Win11.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 \
  --cdrom "$ISO_PATH" \
  --network network=default,model=virtio \
  --graphics vnc,listen=127.0.0.1 \
  --video vga \
  --boot uefi,cdrom,hd \
  --features smm=on \
  --clock hypervclock_present=yes \
  --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb \
  --noautoconsole

echo "VM '$VM_NAME' を作成しました。"

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

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

コマンドプロンプトを管理者で実行してレジストリに登録。

# レジストリに登録
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device" /v DevicePasswordLessBuildVersion /t REG_DWORD /d 0 /f

# パスワードを指定
control userpasswords2

qcow2ディスクの拡張

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

Ubuntu 26.04の場合

ISOイメージダウンロード

cd /opt/vm

デスクトップ版の場合:(現時点はまだベータ版なので下記URL)

wget https://cdimage.ubuntu.com/daily-live/current/resolute-desktop-amd64.iso

サーバ版の場合:(現時点はまだベータ版なので下記URL)

wget https://cdimage.ubuntu.com/ubuntu-server/daily-live/current/resolute-live-server-amd64.iso

下記で作成(–os-variantは26.04がまだ登録されていないため)

#!/bin/bash
VM_NAME="Ubuntu2604"
ISO_PATH="/opt/vm/resolute-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 \
  --cdrom "$ISO_PATH" \
  --network network=default,model=virtio \
  --graphics vnc,listen=127.0.0.1 \
  --video virtio \
  --boot uefi \
  --noautoconsole

echo "VM '$VM_NAME' を作成しました。"

Ubuntu Serverの場合。

#!/bin/bash
VM_NAME="Ubuntu2604sv"
ISO_PATH="/opt/vm/resolute-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 \
  --cdrom "$ISO_PATH" \
  --network network=default,model=virtio \
  --graphics vnc,listen=127.0.0.1 \
  --video virtio \
  --boot uefi \
  --noautoconsole

echo "VM '$VM_NAME' を作成しました。"

ゲストがUbuntuの場合は次のコマンドでゲストエージェントをインストール。

sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent
# エージェントが実行中か確認
systemctl status qemu-guest-agent

ファイラー Navigator プラグイン

sudo apt install -y git
git clone https://github.com/45Drives/cockpit-navigator.git
cd cockpit-navigator
git checkout v0.5.8
sudo apt install -y make
sudo make install
cd ..
rm -rf cockpit-navigator

Samba を Cockpit で管理

sudo apt install -y samba
wget https://github.com/45Drives/cockpit-file-sharing/releases/download/v4.5.3-4/cockpit-file-sharing_4.5.3-4jammy_all.deb
sudo apt install -y ./cockpit-file-sharing_4.5.3-4jammy_all.deb
rm cockpit-file-sharing_4.5.3-4jammy_all.deb

virt-managerもインストール

sudo apt install -y virt-manager

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