docker-webtop/create-devcontainer-config.sh
Tatsuya Ishikawa d0852e2500
Refactor SSL config and user image build process
Updated numbering for SSL configuration and modified volume entries for WSLg. Enhanced user image building process and improved README formatting.
2026-01-30 16:26:54 +09:00

601 lines
17 KiB
Bash
Executable File

#!/bin/bash
# Create VS Code .devcontainer configuration
# This script creates a devcontainer.json that works with the webtop KDE desktop container
set -e
echo "========================================"
echo "VS Code Dev Container Configuration"
echo "========================================"
echo "This script will create a .devcontainer configuration"
echo "for using this container with VS Code."
echo ""
# Check if .devcontainer already exists
if [ -d ".devcontainer" ]; then
echo "⚠️ .devcontainer directory already exists."
read -p "Overwrite existing configuration? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
rm -rf .devcontainer
fi
# Default values
GPU_VENDOR="none"
GPU_ALL="false"
GPU_NUMS=""
UBUNTU_VERSION="24.04"
RESOLUTION="1920x1080"
DPI="96"
SSL_DIR=""
HOST_ARCH_RAW=$(uname -m)
case "${HOST_ARCH_RAW}" in
x86_64|amd64) DETECTED_ARCH="amd64" ;;
aarch64|arm64) DETECTED_ARCH="arm64" ;;
*) DETECTED_ARCH="${HOST_ARCH_RAW}" ;;
esac
TARGET_ARCH="${DETECTED_ARCH}"
# Interactive configuration
echo "========================================"
echo "Configuration Questions"
echo "========================================"
echo ""
# GPU configuration
echo "1. GPU Configuration"
echo "-------------------"
echo "Select GPU type:"
echo " 1) No GPU (software rendering)"
echo " 2) NVIDIA GPU"
echo " 3) NVIDIA WSL2"
echo " 4) Intel GPU"
echo " 5) AMD GPU"
read -p "Select [1-5] (default: 1): " gpu_choice
case "${gpu_choice}" in
2)
GPU_VENDOR="nvidia"
echo ""
echo "NVIDIA GPU selected."
read -p "Use all NVIDIA GPUs? (Y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
read -p "Enter GPU device numbers (comma-separated, e.g., 0,1): " GPU_NUMS
GPU_ALL="false"
else
GPU_ALL="true"
GPU_NUMS=""
fi
;;
3)
GPU_VENDOR="nvidia-wsl"
GPU_ALL="true"
echo "NVIDIA WSL2 selected."
;;
4)
GPU_VENDOR="intel"
echo "Intel GPU selected."
;;
5)
GPU_VENDOR="amd"
echo "AMD GPU selected."
;;
*)
GPU_VENDOR="none"
echo "No GPU selected (software rendering)."
;;
esac
echo ""
# Ubuntu version
echo "2. Ubuntu Version"
echo "----------------"
read -p "Ubuntu version (22.04 or 24.04, default: 24.04): " UBUNTU_VERSION
UBUNTU_VERSION="${UBUNTU_VERSION:-24.04}"
echo ""
# Architecture
echo "3. Architecture"
echo "---------------"
read -p "Target architecture (amd64 or arm64, default: ${DETECTED_ARCH}): " TARGET_ARCH_INPUT
TARGET_ARCH_INPUT="${TARGET_ARCH_INPUT:-${DETECTED_ARCH}}"
case "${TARGET_ARCH_INPUT}" in
amd64|x86_64) TARGET_ARCH="amd64" ;;
arm64|aarch64) TARGET_ARCH="arm64" ;;
*)
echo "Unsupported architecture: ${TARGET_ARCH_INPUT}" >&2
exit 1
;;
esac
echo ""
# Display settings
echo "4. Display Settings"
echo "-------------------"
read -p "Display resolution (default: 1920x1080): " RESOLUTION
RESOLUTION="${RESOLUTION:-1920x1080}"
read -p "DPI (default: 96): " DPI
DPI="${DPI:-96}"
echo ""
# Language/Timezone settings
echo "5. Language/Timezone Settings"
echo "-----------------------------"
echo "Select language (affects timezone):"
echo " ja) Japanese (Asia/Tokyo)"
echo " en) English (UTC)"
read -p "Select language [ja/en] (default: en): " lang_choice
case "${lang_choice}" in
ja|JA|jp|JP)
TIMEZONE="Asia/Tokyo"
echo "Japanese selected. Timezone: Asia/Tokyo"
;;
*)
TIMEZONE="UTC"
echo "English selected. Timezone: UTC"
;;
esac
echo ""
# SSL directory (optional)
echo "6. SSL Configuration (Optional)"
echo "-------------------------------"
read -p "SSL directory path (leave empty to skip): " SSL_DIR
echo ""
# Default SSL dir fallback (same as start-container.sh)
if [ -z "${SSL_DIR}" ]; then
DEFAULT_SSL_DIR="$(pwd)/ssl"
if [ -d "${DEFAULT_SSL_DIR}" ]; then
SSL_DIR="${DEFAULT_SSL_DIR}"
echo "Using SSL dir: ${SSL_DIR}"
fi
fi
CURRENT_USER=$(whoami)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COMPOSE_ENV_SCRIPT="${SCRIPT_DIR}/compose-env.sh"
if [ ! -x "${COMPOSE_ENV_SCRIPT}" ]; then
echo "Error: ${COMPOSE_ENV_SCRIPT} not found. Run this script from the repository root." >&2
exit 1
fi
# Create .devcontainer directory
mkdir -p .devcontainer
# Build compose-env arguments
COMPOSE_ARGS=(--gpu "${GPU_VENDOR}" --ubuntu "${UBUNTU_VERSION}" --resolution "${RESOLUTION}" --dpi "${DPI}" --arch "${TARGET_ARCH}" --timezone "${TIMEZONE}")
if [ "${GPU_VENDOR}" = "nvidia" ]; then
if [ "${GPU_ALL}" = "true" ]; then
COMPOSE_ARGS+=(--all)
else
COMPOSE_ARGS+=(--num "${GPU_NUMS}")
fi
fi
if [ -n "${SSL_DIR}" ]; then
COMPOSE_ARGS+=(--ssl "${SSL_DIR}")
fi
# Generate environment variables
ENV_FILE=".devcontainer/.env"
"${COMPOSE_ENV_SCRIPT}" "${COMPOSE_ARGS[@]}" --env-file "${ENV_FILE}"
# Load generated environment values
set -a
# shellcheck disable=SC1090
source "${ENV_FILE}"
set +a
DEVCONTAINER_CONTAINER_NAME="${CONTAINER_NAME}"
{
echo ""
echo "# Dev Container specific"
echo "DEVCONTAINER_CONTAINER_NAME=${DEVCONTAINER_CONTAINER_NAME}"
} >> "${ENV_FILE}"
export DEVCONTAINER_CONTAINER_NAME
WORKSPACE_FOLDER="/home/${CURRENT_USER}/host_home"
GPU_DEVICES=""
case "${GPU_VENDOR}" in
intel)
if [ -d "/dev/dri" ]; then
GPU_DEVICES="/dev/dri:/dev/dri:rwm"
fi
;;
amd)
if [ -d "/dev/dri" ]; then
GPU_DEVICES="/dev/dri:/dev/dri:rwm"
fi
if [ -e "/dev/kfd" ]; then
GPU_DEVICES="${GPU_DEVICES:+${GPU_DEVICES},}/dev/kfd:/dev/kfd:rwm"
fi
;;
nvidia)
if [ -d "/dev/dri" ]; then
GPU_DEVICES="/dev/dri:/dev/dri:rwm"
fi
;;
nvidia-wsl)
if [ -e "/dev/dxg" ]; then
GPU_DEVICES="/dev/dxg:/dev/dxg:rwm"
fi
;;
esac
# Build forward port list
FORWARD_PORTS=("${HOST_PORT_SSL}" "${HOST_PORT_HTTP}" "${HOST_PORT_TURN}")
FORWARD_PORTS_JSON=""
for PORT in "${FORWARD_PORTS[@]}"; do
if [ -n "${FORWARD_PORTS_JSON}" ]; then
FORWARD_PORTS_JSON="${FORWARD_PORTS_JSON},
"
fi
FORWARD_PORTS_JSON="${FORWARD_PORTS_JSON} ${PORT}"
done
PORT_ATTRIBUTES_JSON=" \"${HOST_PORT_SSL}\": {
\"label\": \"HTTPS Web UI\",
\"onAutoForward\": \"notify\"
},
\"${HOST_PORT_HTTP}\": {
\"label\": \"HTTP Web UI\",
\"onAutoForward\": \"silent\"
},
\"${HOST_PORT_TURN}\": {
\"label\": \"TURN Server\",
\"onAutoForward\": \"silent\"
}"
# devcontainer.json
cat > .devcontainer/devcontainer.json << EOF
{
"name": "KDE Desktop (${GPU_VENDOR})",
"dockerComposeFile": [
"docker-compose.base.yml",
"docker-compose.override.yml"
],
"service": "webtop",
"workspaceFolder": "${WORKSPACE_FOLDER}",
"runServices": ["webtop"],
"overrideCommand": false,
"shutdownAction": "none",
"initializeCommand": "cd \${localWorkspaceFolder:-${PWD}} && if [ -f .devcontainer/.env ]; then CN=\$(sed -n 's/^CONTAINER_NAME=//p' .devcontainer/.env | head -n1); fi; if [ -n \"\$CN\" ]; then docker rm -f \"\$CN\" >/dev/null 2>&1 || true; fi",
"forwardPorts": [
${FORWARD_PORTS_JSON}
],
"portsAttributes": {
${PORT_ATTRIBUTES_JSON}
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode-remote.remote-containers",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cmake-tools",
"ms-vscode.makefile-tools",
"redhat.vscode-yaml",
"redhat.vscode-xml",
"ms-vscode.hexeditor",
"ms-python.python",
"ms-python.vscode-pylance",
"vscode-icons-team.vscode-icons",
"donjayamanne.git-extension-pack"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
}
}
},
"remoteUser": "${CURRENT_USER}",
"containerUser": "root",
"updateRemoteUserUID": false,
"remoteEnv": {
"USER": "${CURRENT_USER}",
"HOME": "/home/${CURRENT_USER}"
},
EOF
# Add GPU hostRequirements if applicable
if [ "${GPU_VENDOR}" = "nvidia" ] || [ "${GPU_VENDOR}" = "nvidia-wsl" ]; then
cat >> .devcontainer/devcontainer.json << 'EOF'
"hostRequirements": {
"gpu": "optional"
},
EOF
fi
cat >> .devcontainer/devcontainer.json << EOF
"postCreateCommand": "echo '===== Dev Container Ready =====' && echo 'Desktop access' && echo ' HTTPS: https://localhost:${HOST_PORT_SSL}' && echo ' HTTP : http://localhost:${HOST_PORT_HTTP}' && echo 'If HTTPS fails, confirm your SSL certs or use HTTP.' && echo '==============================='"
}
EOF
# docker-compose base (match start-container.sh)
cat > .devcontainer/docker-compose.base.yml << EOF
services:
webtop:
image: \${USER_IMAGE}
container_name: \${CONTAINER_NAME}
hostname: \${CONTAINER_HOSTNAME}
shm_size: \${SHM_SIZE:-4g}
privileged: true
security_opt:
- seccomp:unconfined
environment:
- HOSTNAME=\${CONTAINER_HOSTNAME}
- HOST_HOSTNAME=\${CONTAINER_HOSTNAME}
- SHELL=/bin/bash
- DISPLAY=:1
- DPI=\${DPI}
- SCALE_FACTOR=\${SCALE_FACTOR}
- FORCE_DEVICE_SCALE_FACTOR=\${FORCE_DEVICE_SCALE_FACTOR}
- CHROMIUM_FLAGS=\${CHROMIUM_FLAGS}
- DISPLAY_WIDTH=\${WIDTH}
- DISPLAY_HEIGHT=\${HEIGHT}
- CUSTOM_RESOLUTION=\${RESOLUTION}
- USER_UID=\${USER_UID}
- USER_GID=\${USER_GID}
- USER_NAME=\${USER_NAME}
- PUID=\${HOST_UID}
- PGID=\${HOST_GID}
- SELKIES_ENCODER=\${SELKIES_ENCODER}
- GPU_VENDOR=\${GPU_VENDOR}
- ENABLE_NVIDIA=\${ENABLE_NVIDIA}
- LIBVA_DRIVER_NAME=\${LIBVA_DRIVER_NAME}
- WSL_ENVIRONMENT=\${WSL_ENVIRONMENT}
- DISABLE_ZINK=\${DISABLE_ZINK}
- XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR}
- LD_LIBRARY_PATH=\${LD_LIBRARY_PATH}
- SELKIES_TURN_HOST=\${SELKIES_TURN_HOST}
- SELKIES_TURN_PORT=\${SELKIES_TURN_PORT}
- SELKIES_TURN_USERNAME=\${SELKIES_TURN_USERNAME}
- SELKIES_TURN_PASSWORD=\${SELKIES_TURN_PASSWORD}
- SELKIES_TURN_PROTOCOL=\${SELKIES_TURN_PROTOCOL}
- TURN_RANDOM_PASSWORD=\${TURN_RANDOM_PASSWORD}
- TURN_EXTERNAL_IP=\${TURN_EXTERNAL_IP}
volumes:
- \${HOME}:\${HOST_HOME_MOUNT}:rw
ports:
- \${HOST_PORT_HTTP}:3000
- \${HOST_PORT_SSL}:3001
- \${HOST_PORT_TURN}:3478/tcp
- \${HOST_PORT_TURN}:3478/udp
restart: unless-stopped
EOF
# docker-compose override for devcontainer
cat > .devcontainer/docker-compose.override.yml << EOF
services:
webtop:
network_mode: bridge
EOF
DEVICE_ENTRIES=()
VOLUME_ENTRIES=()
GROUPS_TO_ADD=()
# Add host group mappings (match start-container.sh)
VIDEO_GID=$(getent group video 2>/dev/null | cut -d: -f3 || true)
RENDER_GID=$(getent group render 2>/dev/null | cut -d: -f3 || true)
if [ -n "${VIDEO_GID}" ]; then
GROUPS_TO_ADD+=("${VIDEO_GID}")
fi
if [ -n "${RENDER_GID}" ]; then
GROUPS_TO_ADD+=("${RENDER_GID}")
fi
if [ "${#GROUPS_TO_ADD[@]}" -gt 0 ]; then
{
echo " group_add:"
for GID in "${GROUPS_TO_ADD[@]}"; do
echo " - \"${GID}\""
done
} >> .devcontainer/docker-compose.override.yml
fi
if [ "${GPU_VENDOR}" = "nvidia" ] || [ "${GPU_VENDOR}" = "nvidia-wsl" ]; then
if [ "${GPU_VENDOR}" = "nvidia-wsl" ] || [ "${GPU_ALL}" = "true" ]; then
echo " gpus: all" >> .devcontainer/docker-compose.override.yml
elif [ -n "${GPU_NUMS}" ]; then
echo " gpus: \"device=${GPU_NUMS}\"" >> .devcontainer/docker-compose.override.yml
fi
fi
if [ "${GPU_VENDOR}" = "nvidia-wsl" ]; then
# Add WSL-specific devices if they exist
if [ -e "/dev/dxg" ]; then
DEVICE_ENTRIES+=("/dev/dxg:/dev/dxg:rwm")
fi
# Add WSL-specific volumes
if [ -d "/usr/lib/wsl/lib" ]; then
VOLUME_ENTRIES+=("/usr/lib/wsl/lib:/usr/lib/wsl/lib:ro")
fi
if [ -d "/mnt/wslg" ]; then
VOLUME_ENTRIES+=("/mnt/wslg:/mnt/wslg:rw")
VOLUME_ENTRIES+=("/mnt/wslg/.X11-unix:/tmp/.X11-unix:rw")
VOLUME_ENTRIES+=("/usr/lib/wsl/drivers:/usr/lib/wsl/drivers:ro")
fi
fi
if [ -n "${GPU_DEVICES}" ]; then
IFS=',' read -r -a GPU_DEVICE_LIST <<< "${GPU_DEVICES}"
for DEVICE in "${GPU_DEVICE_LIST[@]}"; do
DEVICE_ENTRIES+=("${DEVICE}")
done
fi
DEVICE_ENTRIES+=("/dev/bus/usb:/dev/bus/usb:rwm")
# Add SSL mount when available (match start-container.sh)
if [ -n "${SSL_DIR}" ] && [ -f "${SSL_DIR}/cert.pem" ] && [ -f "${SSL_DIR}/cert.key" ]; then
VOLUME_ENTRIES+=("\${SSL_DIR}:/config/ssl:ro")
fi
# Add /mnt mount on non-mac hosts (Docker Desktop for Mac does not share /mnt by default)
if [ "$(uname -s)" != "Darwin" ] && [ -d "/mnt" ]; then
VOLUME_ENTRIES+=("/mnt:\${HOST_MNT_MOUNT}:rw")
fi
if [ "${#DEVICE_ENTRIES[@]}" -gt 0 ]; then
{
echo " devices:"
for DEVICE in "${DEVICE_ENTRIES[@]}"; do
echo " - ${DEVICE}"
done
} >> .devcontainer/docker-compose.override.yml
fi
if [ "${#VOLUME_ENTRIES[@]}" -gt 0 ]; then
{
echo " volumes:"
for VOLUME in "${VOLUME_ENTRIES[@]}"; do
echo " - ${VOLUME}"
done
} >> .devcontainer/docker-compose.override.yml
fi
# Copy .env to workspace root for docker-compose
cp "${ENV_FILE}" .env
# README
cat > .devcontainer/README.md << EOF
# VS Code Dev Container Configuration
The files in this directory are generated by \`./create-devcontainer-config.sh\`. It writes the same environment variables as \`start-container.sh\` into \`.devcontainer/.env\` and the repository root \`.env\`.
## Generated settings
- **GPU**: ${GPU_VENDOR}
EOF
if [ "${GPU_VENDOR}" = "nvidia" ]; then
if [ "${GPU_ALL}" = "true" ]; then
cat >> .devcontainer/README.md << 'EOF'
- **NVIDIA GPUs**: all
EOF
else
cat >> .devcontainer/README.md << EOF
- **NVIDIA GPUs**: ${GPU_NUMS}
EOF
fi
fi
cat >> .devcontainer/README.md << EOF
- **Ubuntu Version**: ${UBUNTU_VERSION}
- **Resolution**: ${RESOLUTION}
- **DPI**: ${DPI}
- **Timezone**: ${TIMEZONE}
## Access URLs
- **HTTPS**: https://localhost:${HOST_PORT_SSL}
- **HTTP**: http://localhost:${HOST_PORT_HTTP}
- **TURN Port**: ${HOST_PORT_TURN}
## How to use in VS Code
1. Install the Dev Containers extension
2. Open the workspace and run \`F1\` → \`Dev Containers: Reopen in Container\`
3. VS Code reads \`.env\` and starts \`docker compose\`
## How to use in VS Code
1. Install the Dev Containers extension
2. Open the workspace and run \`F1\` → \`Dev Containers: Reopen in Container\`
3. VS Code reads \`.env\` and starts \`docker compose\`
EOF
# Copy .env to workspace root for docker-compose
cp "${ENV_FILE}" .env
echo ""
echo "========================================"
echo "Configuration Complete!"
echo "========================================"
echo ""
echo "Created files:"
echo " - .devcontainer/devcontainer.json"
echo " - .devcontainer/docker-compose.base.yml"
echo " - .devcontainer/docker-compose.override.yml"
echo " - .devcontainer/.env"
echo " - .devcontainer/README.md"
echo " - .env (for docker-compose)"
echo ""
echo "Configuration summary:"
echo " - GPU: ${GPU_VENDOR}"
if [ "${GPU_VENDOR}" = "nvidia" ]; then
if [ "${GPU_ALL}" = "true" ]; then
echo " NVIDIA GPUs: all"
else
echo " NVIDIA GPUs: ${GPU_NUMS}"
fi
fi
echo " - Ubuntu: ${UBUNTU_VERSION}"
echo " - Resolution: ${RESOLUTION}"
echo " - DPI: ${DPI}"
echo " - Timezone: ${TIMEZONE}"
echo " - HTTPS Port: ${HOST_PORT_SSL}"
echo " - HTTP Port: ${HOST_PORT_HTTP}"
echo " - TURN Port: ${HOST_PORT_TURN}"
echo ""
echo "========================================"
# Check if the user image exists
echo "Checking for user image: ${USER_IMAGE}..."
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^${USER_IMAGE}$"; then
echo ""
echo "⚠️ User image not found: ${USER_IMAGE}"
echo "Building user image automatically..."
echo ""
# Prepare build-user-image.sh arguments
BUILD_ARGS=(--ubuntu "${UBUNTU_VERSION}" --arch "${TARGET_ARCH}")
# Determine language argument
case "${TIMEZONE}" in
Asia/Tokyo)
BUILD_ARGS+=(--language "ja")
;;
*)
BUILD_ARGS+=(--language "en")
;;
esac
# Execute build-user-image.sh
BUILD_SCRIPT="${SCRIPT_DIR}/build-user-image.sh"
if [ ! -x "${BUILD_SCRIPT}" ]; then
echo "Error: ${BUILD_SCRIPT} not found or not executable." >&2
echo "Please run: ./build-user-image.sh ${BUILD_ARGS[*]}" >&2
exit 1
fi
echo "Executing: ${BUILD_SCRIPT} ${BUILD_ARGS[*]}"
if "${BUILD_SCRIPT}" "${BUILD_ARGS[@]}"; then
echo ""
echo "✅ User image built successfully!"
echo ""
else
echo ""
echo "❌ Failed to build user image." >&2
echo "Please manually run: ./build-user-image.sh ${BUILD_ARGS[*]}" >&2
exit 1
fi
else
echo "✅ User image found: ${USER_IMAGE}"
fi
echo ""
echo "========================================"
echo "Ready to use Dev Container!"
echo "========================================"
echo ""
echo "To start the devcontainer from VS Code:"
echo " 1) Open this workspace in VS Code."
echo " 2) Press F1 to open the Command Palette."
echo " 3) Type and run: Dev Containers: Reopen in Container"
echo " (or select 'Dev Containers: Reopen in Container')"
echo ""
echo "Tip: You can also click the green >< icon in the lower-left corner and choose 'Reopen in Container'."