ブラウザ経由で複数のVMを管理「WebVirtCloud」

以前、Webブラウザ経由でRDP接続出来る「Apache Guacamole」を紹介しましたが、仮想マシンに限定するならば「WebVirtCloud」 というツールもあります。noVNC経由で画面表示ができ、VNCだけでなくSPICE経由でも表示可能となっています。
ただ、そんなに描画が早い訳では無いので確認程度でしょうか。これならCockpitでも十分な気が。複数のマシンで動いているVMを一括管理するのに適していそうでしょうか。

VMホストに直接インストール

一応、LXDコンテナ内でも動くと思いますが、直接インストールしたほうが楽でしょう。

nano install_webvirtcloud.sh
chmod +x install_webvirtcloud.sh
sudo ./install_webvirtcloud.sh
#!/bin/bash
# ============================================================
#  WebVirtCloud インストールスクリプト
#  対象: Ubuntu 24.04 / 26.04 (LXD コンテナ・ホスト両対応)
#  使い方: sudo bash install_webvirtcloud.sh
#
#  Python 3.13+ 対応:
#    - crypt_r パッケージビルド不可 → ソースを直接パッチ
#    - import crypt_r → hashlib/hmac で代替実装を注入
# ============================================================
set -euo pipefail

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

[[ $EUID -ne 0 ]] && error "root または sudo で実行してください"

APP_PATH="/srv/webvirtcloud"
PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
info "検出した Python バージョン: ${PY_VER}"

IS_PY313_PLUS=false
python3 -c "import sys; exit(0 if sys.version_info >= (3,13) else 1)" 2>/dev/null && IS_PY313_PLUS=true

# ── 1. パッケージ更新 ─────────────────────────────────────────
info "パッケージを更新中..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get upgrade -y -qq

# ── 2. 依存パッケージインストール ────────────────────────────
info "依存パッケージをインストール中..."
apt-get install -y -qq \
    git \
    python3 python3-dev python3-pip python3-venv python3-lxml \
    python3-guestfs \
    libvirt-dev libxml2-dev libxslt1-dev \
    zlib1g-dev libffi-dev libssl-dev \
    libsasl2-dev libsasl2-modules \
    libldap2-dev \
    gcc pkg-config \
    nginx supervisor \
    curl wget

# libxcrypt-dev: 名前がディストロで異なるため試行
for PKG in libxcrypt-dev libcrypt-dev; do
    apt-cache show "$PKG" &>/dev/null && \
        apt-get install -y -qq "$PKG" && \
        info "  → ${PKG} インストール済み" && break || true
done

# ── 3. WebVirtCloud クローン ──────────────────────────────────
info "WebVirtCloud をクローン中..."
mkdir -p /srv
if [[ -d "$APP_PATH" ]]; then
    warn "$APP_PATH が存在します。バックアップして再クローンします..."
    mv "$APP_PATH" "${APP_PATH}.bak.$(date +%Y%m%d%H%M%S)"
fi
git clone --depth 1 https://github.com/retspen/webvirtcloud "$APP_PATH"
cd "$APP_PATH"

# ── 4. Python 3.13+ ソースパッチ ─────────────────────────────
# crypt_r は Python 3.13 でビルド不可かつ廃止。
# ソース内の "import crypt_r" を shim モジュールで置き換える。
if $IS_PY313_PLUS; then
    info "Python ${PY_VER}: crypt_r ソースパッチを適用中..."

    # --- shim: crypt_r.py を作成 ---
    # crypt_r の実際の使われ方 = パスワードハッシュ生成/検証
    # hashlib + hmac + secrets で同等機能を提供
    cat > "${APP_PATH}/crypt_r.py" << 'SHIM_EOF'
"""
crypt_r compatibility shim for Python 3.13+
crypt/crypt_r が削除されたため hashlib で代替実装
"""
import hashlib, hmac, os, re

SHA512_PREFIX = "$6$"
SHA256_PREFIX = "$5$"

def crypt(word: str, salt: str = None) -> str:
    """SHA-512 ベースのパスワードハッシュ (crypt_r.crypt 互換)"""
    if salt is None:
        salt = SHA512_PREFIX + _make_salt(16)

    # 既存ハッシュ値が渡された場合はそこからソルトを抽出
    m = re.match(r'(\$[0-9]\$[^$]+\$?)', salt)
    if m:
        salt_part = m.group(1)
    else:
        salt_part = SHA512_PREFIX + salt[:16]

    # SHA-512 で反復ハッシュ (簡易実装)
    raw = (word + salt_part).encode()
    digest = hashlib.sha512(raw).digest()
    for _ in range(5000):
        digest = hashlib.sha512(digest + raw).digest()
    return salt_part + "$" + digest.hex()

def _make_salt(n: int = 16) -> str:
    chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
    return "".join(chars[b % len(chars)] for b in os.urandom(n))

# モジュールレベルで crypt 関数を属性として公開
METHOD_SHA512 = SHA512_PREFIX
METHOD_SHA256 = SHA256_PREFIX
SHIM_EOF

    info "  → ${APP_PATH}/crypt_r.py (shim) を作成しました"

    # --- ソース内の import を grep して確認 ---
    info "  crypt_r を使用しているファイル:"
    grep -rl "crypt_r" "${APP_PATH}" --include="*.py" | grep -v "__pycache__" | grep -v "crypt_r.py" || true
fi

# ── 5. venv 作成 ─────────────────────────────────────────────
info "Python 仮想環境を作成中..."
python3 -m venv --system-site-packages venv
source venv/bin/activate

# ── 6. requirements.txt パッチ ────────────────────────────────
info "requirements.txt をパッチ中..."
if $IS_PY313_PLUS; then
    # crypt-r はビルド不可のため除外
    sed -i '/crypt[-_]r/Id' conf/requirements.txt
    # lxml は apt 版を使用するため pip ビルド除外
    sed -i '/^lxml[^-]/Id' conf/requirements.txt
    info "  → crypt-r, lxml を除外しました"
fi

info "requirements.txt (パッチ後):"
grep -v "^#" conf/requirements.txt | grep -v "^$" || true

# ── 7. pip インストール ───────────────────────────────────────
info "pip を最新化中..."
pip install --upgrade pip -q

info "Python パッケージをインストール中... (数分かかります)"
pip install -r conf/requirements.txt || {
    warn "一括インストール失敗。個別にリトライします..."
    while IFS= read -r pkg || [[ -n "$pkg" ]]; do
        [[ -z "$pkg" || "$pkg" =~ ^# ]] && continue
        pip install "$pkg" -q && info "  OK: $pkg" || warn "  SKIP: $pkg"
    done < conf/requirements.txt
}

# ── 8. settings.py & SECRET_KEY ──────────────────────────────
info "settings.py を設定中..."
cp webvirtcloud/settings.py.template webvirtcloud/settings.py

SECRET_KEY=$(python3 -c "
import random, string
chars = string.ascii_letters + string.digits + '!@#\$%^&*(-_=+)'
print(''.join(random.SystemRandom().choice(chars) for _ in range(50)))
")
[[ -z "$SECRET_KEY" ]] && error "SECRET_KEY の生成に失敗しました"

sed -i "s|SECRET_KEY = ''|SECRET_KEY = '${SECRET_KEY}'|" webvirtcloud/settings.py
sed -i 's|SECRET_KEY = ""|SECRET_KEY = "'"${SECRET_KEY}"'"|' webvirtcloud/settings.py

if ! grep -qE "SECRET_KEY = '.{10,}'" webvirtcloud/settings.py; then
    python3 -c "
import re, pathlib
p = pathlib.Path('webvirtcloud/settings.py')
c = p.read_text()
c = re.sub(r\"SECRET_KEY\s*=\s*['\\\"].*?['\\\"]\", \"SECRET_KEY = '${SECRET_KEY}'\", c)
p.write_text(c)
print('SECRET_KEY を Python で書き込みました')
"
fi
grep "SECRET_KEY" webvirtcloud/settings.py | head -1

# CSRF_TRUSTED_ORIGINS
HOST_IP=$(hostname -I | awk '{print $1}')
FQDN=$(hostname -f 2>/dev/null || hostname)
TRUSTED="['http://${FQDN}', 'http://${HOST_IP}', 'http://localhost', 'http://127.0.0.1']"
if grep -q "^CSRF_TRUSTED_ORIGINS" webvirtcloud/settings.py; then
    sed -i "s|^CSRF_TRUSTED_ORIGINS = .*|CSRF_TRUSTED_ORIGINS = ${TRUSTED}|" webvirtcloud/settings.py
else
    echo "CSRF_TRUSTED_ORIGINS = ${TRUSTED}" >> webvirtcloud/settings.py
fi

# ── 9. DB migrate & collectstatic ────────────────────────────
info "データベースを初期化中..."
python3 manage.py migrate --noinput

info "静的ファイルを収集中..."
python3 manage.py collectstatic --noinput

deactivate

# ── 10. パーミッション ────────────────────────────────────────
chown -R www-data:www-data "$APP_PATH"

# ── 11. Nginx 設定 ────────────────────────────────────────────
info "Nginx を設定中..."
cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/webvirtcloud.conf
rm -f /etc/nginx/sites-enabled/default
sed -i "s|server_name .*;|server_name ${FQDN} ${HOST_IP} localhost;|" \
    /etc/nginx/conf.d/webvirtcloud.conf

# ── 12. Supervisor 設定 ───────────────────────────────────────
info "Supervisor を設定中..."
cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d/webvirtcloud.conf
SUPERVISOR_CONF="/etc/supervisor/supervisord.conf"
if ! grep -q "\[inet_http_server\]" "$SUPERVISOR_CONF" 2>/dev/null; then
    printf '\n[inet_http_server]\nport = 127.0.0.1:9001\n' >> "$SUPERVISOR_CONF"
fi

# ── 13. サービス起動 ──────────────────────────────────────────
info "サービスを起動中..."
if command -v systemctl &>/dev/null && systemctl list-units &>/dev/null 2>&1; then
    systemctl enable nginx supervisor 2>/dev/null || true
    systemctl restart nginx
    systemctl restart supervisor
    sleep 2
    supervisorctl reread  || true
    supervisorctl update  || true
    supervisorctl start webvirtcloud:* 2>/dev/null || true
    supervisorctl start novncd         2>/dev/null || true
else
    warn "systemd 不可 → 直接起動します"
    nginx -t && nginx &
    supervisord -c /etc/supervisor/supervisord.conf &
    sleep 3
    supervisorctl -c /etc/supervisor/supervisord.conf reread || true
    supervisorctl -c /etc/supervisor/supervisord.conf update || true
fi

# ── 14. 完了 ──────────────────────────────────────────────────
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN}  WebVirtCloud インストール完了!${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo -e "  アクセスURL  : ${YELLOW}http://${HOST_IP}/${NC}"
echo -e "  ユーザー名   : ${YELLOW}admin${NC}"
echo -e "  パスワード   : ${YELLOW}admin${NC}  (ログイン後すぐ変更してください)"
echo ""
echo -e "  noVNC ポート : ${YELLOW}6080${NC}"
echo ""
echo -e "  ローカルVM管理の追加設定:"
echo -e "    sudo -u www-data ssh-keygen -t rsa -N '' -f /var/lib/www-data/.ssh/id_rsa"
echo -e "    cat /var/lib/www-data/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys"
echo -e "    WebUI: Computes → Add → SSH → 127.0.0.1"
echo ""

ローカルVMが見えない場合は権限を追加します。

# www-data を libvirt・kvm グループに追加
sudo usermod -aG libvirt www-data
sudo usermod -aG kvm www-data

# supervisor を再起動してグループ変更を反映
sudo systemctl restart supervisor

# 反映確認
groups www-data

# 再テスト(これが通ればOK)
sudo -u www-data virsh -c qemu:///system list

Webから管理

Cockpitの仮想マシンよりは細かな設定ができます。ストレージの拡大などがGUIで手軽に出来るのは便利ですね。

一応、Docker版もあるようです。

アンインストール

# 1. サービス停止
sudo supervisorctl stop all
sudo systemctl stop supervisor nginx
sudo systemctl disable supervisor nginx

# 2. WebVirtCloud 本体削除
sudo rm -rf /srv/webvirtcloud

# 3. Nginx・Supervisor 設定削除
sudo rm -f /etc/nginx/conf.d/webvirtcloud.conf
sudo rm -f /etc/supervisor/conf.d/webvirtcloud.conf

# 4. Nginx デフォルトサイト復元(必要なら)
sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

# 5. インストールしたパッケージを削除(任意)
sudo apt-get remove -y nginx supervisor
sudo apt-get autoremove -y

# 6. Nginx・Supervisor を残す場合はリロードだけ
sudo systemctl restart nginx
sudo systemctl restart supervisor

注意点:

  • nginxsupervisor を他の用途でも使っている場合はステップ5はスキップしてください
  • WebVirtCloudのDBは /srv/webvirtcloud/db.sqlite3 にあるので、VMの設定情報も一緒に消えます(VM自体は消えません)
  • /var/lib/www-data/.ssh/ に鍵を作った場合は sudo rm -rf /var/lib/www-data も追加してください
タイトルとURLをコピーしました