Ubuntu 26.04の正式版が公開されたことで、LXDコンテナでもUbuntu 26.04が使えるようになりました。既存のスクリプトだとエラーが出たりするので、ここで一旦整理します。インストール直後を想定しています。
アップデート・Tailscale・SSH
まずはアップデート。
sudo apt update
sudo apt upgrade -y
Tailscaleをインストール。
sudo apt install curl
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
SSHサーバをインストール。以降は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
一度再起動。このあとLXDをインストールするならその時に。
sudo reboot
Cockpit
VMを扱うならインストール。
#!/bin/bash
set -e
# --- Cockpit セットアップ ---
sudo apt install -y nano cockpit
# 仮想マシンプラグイン
sudo apt install -y cockpit-machines
# ホスト名を取得
HOSTNAME=$(hostname)
# Cockpit設定
sudo mkdir -p /etc/cockpit
sudo tee /etc/cockpit/cockpit.conf << EOF
[WebService]
Origins = https://${HOSTNAME}:9090 https://localhost:9090
EOF
sudo systemctl enable --now cockpit.socket
sudo systemctl restart cockpit
echo ""
echo "======================================"
echo " セットアップ完了!"
echo "======================================"
echo " アクセス先: https://${HOSTNAME}:9090/"
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
あとはvmフォルダに移動して、ISOイメージをダウンロードするなど。
cd /opt/vm
Windows 11の場合
マイクロソフトのサイトからダウンロードしたWin11_25H2_Japanese_x64_v2.isoが/opt/vmにある想定。
# 依存パッケージのインストール
sudo apt update
sudo apt install -y libhivex-bin libwin-hivex-perl wimtools genisoimage \
golang-go git make
# ビルドディレクトリの作成とクローン
sudo mkdir -p /opt/distrobuilder
sudo chown $USER /opt/distrobuilder
cd /opt/distrobuilder
git clone https://github.com/lxc/distrobuilder
cd distrobuilder
make
# バイナリをコピー(go installで~/go/binに生成される)
sudo cp ~/go/bin/distrobuilder /usr/local/bin/distrobuilder
# 確認
distrobuilder --version
# virtio-win ISOをダウンロード
cd /opt/vm
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso
# ISOファイル名を確認してから実行
ls -lh /opt/vm/*.iso
# virtioドライバ統合済みISOを再パッケージ
# ※ファイル名はlsの結果に合わせて変更すること
WIN_ISO=$(ls /opt/vm/Win11_*.iso | head -1)
echo "使用するISO: $WIN_ISO"
sudo distrobuilder repack-windows \
"$WIN_ISO" \
/opt/vm/Win11-virtio.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
virt-managerもインストールするなら
sudo apt install -y virt-manager
Ubuntu 26.04 デスクトップ版の場合
wget https://releases.ubuntu.com/26.04/ubuntu-26.04-desktop-amd64.iso
下記で作成(–os-variantは26.04がまだ登録されていないため)
#!/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 \
--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 26.04 サーバ版の場合
wget https://releases.ubuntu.com/26.04/ubuntu-26.04-live-server-amd64.iso
#!/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 \
--cdrom "$ISO_PATH" \
--network network=default,model=virtio \
--graphics vnc,listen=127.0.0.1 \
--video virtio \
--boot uefi \
--noautoconsole
echo "VM '$VM_NAME' を作成しました。"
ゲストエージェントをインストール
sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent
# エージェントが実行中か確認
systemctl status qemu-guest-agent
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
ベースコンテナを作成し今後はLXD内で作業
とにかくホストを汚さない設定。一番ベースのLXDコンテナを作成。今後は全てこのコンテナ内で作業。このコンテナが仮想マシンの想定。バックアップもやり直しも手軽。
#!/bin/bash
# コンテナ名を入力
read -p "コンテナ名を入力してください: " CONTAINER_NAME
if [ -z "$CONTAINER_NAME" ]; then
echo "エラー: コンテナ名が空です"
exit 1
fi
# コンテナ作成
echo "コンテナ '$CONTAINER_NAME' を作成中..."
lxc launch ubuntu:26.04 "$CONTAINER_NAME"
# コンテナ内コンテナ用の設定(特権なし・入れ子のみ)
echo "入れ子コンテナ設定を適用中..."
lxc config set "$CONTAINER_NAME" security.nesting true
echo "入れ子コンテナ設定完了"
# コンテナが起動するまで待機
echo "コンテナの起動を待機中..."
sleep 5
# パッケージアップデート
echo "パッケージをアップデート中..."
lxc exec "$CONTAINER_NAME" -- bash -c "apt-get update && apt-get upgrade -y"
# Tailscaleインストール
echo "Tailscaleをインストール中..."
lxc exec "$CONTAINER_NAME" -- bash -c "curl -fsSL https://tailscale.com/install.sh | sh"
echo "Tailscaleインストール完了(tailscale up は手動で実行してください)"
echo "完了: $CONTAINER_NAME"
lxc list "$CONTAINER_NAME"
LXD-UIでターミナルに入って作業。
tailscale up
一度スナップショットを取っておくと便利。
shutdown now
以降は、このコンテナ内で全て実行。失敗したらコンテナを消してやり直せばOK。
opencode web
作成したコンテナに直接インストール。不要なら飛ばしてOK。
#!/usr/bin/env bash
# =============================================================================
# opencode web + Tailscale Serve セットアップスクリプト
# LXDコンテナ内で実行。Tailscaleネットワーク経由でHTTPSアクセスを提供します。
#
# 使い方:
# bash setup-opencode-tailscale.sh
# ※ sudo は不要。スクリプト内で必要な箇所だけ sudo を使います。
# =============================================================================
set -euo pipefail
# ── 設定 ─────────────────────────────────────────────────────────────────────
OPENCODE_PORT=4096
OPENCODE_USERNAME="opencode"
OPENCODE_PASSWORD=""
WORK_DIR="${OPENCODE_WORK_DIR:-$HOME/workspace}"
SERVICE_FILE="/etc/systemd/system/opencode-web.service"
RUN_USER="${USER:-$(id -un)}"
RUN_HOME="$HOME"
# ── カラー出力 ────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; 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}[ERROR]${NC} $*" >&2; exit 1; }
# ── ① 最初にパスワードを入力させる ───────────────────────────────────────────
prompt_password() {
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ opencode web + Tailscale Serve セットアップ ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════╝${NC}"
echo ""
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
OPENCODE_PASSWORD="$OPENCODE_SERVER_PASSWORD"
info "OPENCODE_SERVER_PASSWORD 環境変数からパスワードを取得しました。"
return
fi
echo -e "${YELLOW}[WARN]${NC} Tailscaleネットワーク越しに公開するため、パスワード設定を推奨します。"
echo ""
while true; do
read -r -s -p "🔑 opencode web のパスワードを入力 (空Enterでスキップ): " OPENCODE_PASSWORD
echo ""
if [[ -z "$OPENCODE_PASSWORD" ]]; then
warn "パスワードなしで続行します。"
break
fi
read -r -s -p "🔑 もう一度入力してください: " OPENCODE_PASSWORD2
echo ""
if [[ "$OPENCODE_PASSWORD" == "$OPENCODE_PASSWORD2" ]]; then
success "パスワードを設定しました。"
break
else
warn "パスワードが一致しません。もう一度入力してください。"
fi
done
echo ""
}
# ── 前提チェック ──────────────────────────────────────────────────────────────
check_prerequisites() {
info "前提条件を確認しています..."
if ! command -v tailscale &>/dev/null; then
error "tailscale が見つかりません。先に Tailscale をインストール・ログインしてください。"
fi
if ! tailscale status &>/dev/null; then
error "Tailscale が接続されていません。'tailscale up' を実行してください。"
fi
success "Tailscale: 接続済み"
}
# ── opencode インストール ─────────────────────────────────────────────────────
install_opencode() {
# 公式スクリプトは ~/.opencode/bin/ にインストールするのでPATHを先に通す
export PATH="$HOME/.opencode/bin:$HOME/.local/bin:$HOME/bin:$PATH"
if command -v opencode &>/dev/null; then
CURRENT_VER=$(opencode --version 2>/dev/null || echo "unknown")
success "opencode は既にインストール済みです ($CURRENT_VER)。スキップします。"
return
fi
info "opencode をインストールしています..."
if command -v npm &>/dev/null; then
npm install -g opencode-ai@latest
success "opencode を npm でインストールしました。"
return
fi
if command -v curl &>/dev/null; then
curl -fsSL https://opencode.ai/install | bash
# インストール後にPATHを再読み込み
export PATH="$HOME/.opencode/bin:$HOME/.local/bin:$HOME/bin:$PATH"
success "opencode を公式スクリプトでインストールしました。"
return
fi
error "npm も curl も見つかりません。Node.js または curl をインストールしてください。"
}
# ── ワークディレクトリ・設定ファイル準備 ─────────────────────────────────────
setup_workdir() {
info "ワークディレクトリを準備しています: $WORK_DIR"
mkdir -p "$WORK_DIR"
OPENCODE_CONFIG_DIR="$RUN_HOME/.config/opencode"
mkdir -p "$OPENCODE_CONFIG_DIR"
cat > "$OPENCODE_CONFIG_DIR/opencode.json" <<EOF
{
"server": {
"port": ${OPENCODE_PORT},
"hostname": "127.0.0.1"
}
}
EOF
success "opencode 設定ファイル: $OPENCODE_CONFIG_DIR/opencode.json"
}
# ── systemd サービス登録 ──────────────────────────────────────────────────────
setup_systemd_service() {
info "systemd サービスを設定しています..."
# opencode バイナリのフルパスを解決
# 公式スクリプトは ~/.opencode/bin/ にインストールするため優先的に探す
OPENCODE_BIN=""
for candidate in \
"$RUN_HOME/.opencode/bin/opencode" \
"$RUN_HOME/.local/bin/opencode" \
"$RUN_HOME/bin/opencode" \
"/usr/local/bin/opencode"
do
if [[ -x "$candidate" ]]; then
OPENCODE_BIN="$candidate"
break
fi
done
# forループで見つからなければ PATH から探す
if [[ -z "$OPENCODE_BIN" ]]; then
OPENCODE_BIN=$(command -v opencode 2>/dev/null || true)
fi
if [[ -z "$OPENCODE_BIN" ]]; then
error "opencode バイナリが見つかりません。インストールを確認してください。"
fi
info "opencode バイナリ: $OPENCODE_BIN"
# パスワードが設定されている場合のみ Environment 行を追加
if [[ -n "$OPENCODE_PASSWORD" ]]; then
ENV_PASSWORD_LINE="Environment=\"OPENCODE_SERVER_PASSWORD=${OPENCODE_PASSWORD}\""
else
ENV_PASSWORD_LINE="# OPENCODE_SERVER_PASSWORD not set"
fi
sudo tee "$SERVICE_FILE" > /dev/null <<EOF
[Unit]
Description=OpenCode Web Server
After=network.target tailscaled.service
Wants=tailscaled.service
[Service]
Type=simple
User=${RUN_USER}
WorkingDirectory=${WORK_DIR}
Environment="HOME=${RUN_HOME}"
Environment="PATH=${RUN_HOME}/.opencode/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${RUN_HOME}/.local/bin:${RUN_HOME}/bin"
Environment="OPENCODE_SERVER_USERNAME=${OPENCODE_USERNAME}"
${ENV_PASSWORD_LINE}
ExecStart=${OPENCODE_BIN} web --port ${OPENCODE_PORT} --hostname 127.0.0.1
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=opencode-web
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable opencode-web.service
sudo systemctl restart opencode-web.service
sleep 4
if sudo systemctl is-active --quiet opencode-web.service; then
success "opencode-web サービスが起動しました。"
else
warn "サービスの起動に問題があります。直近のログ:"
echo ""
sudo journalctl -u opencode-web -n 20 --no-pager || true
echo ""
warn "詳細確認: journalctl -u opencode-web -n 50 --no-pager"
fi
}
# ── Tailscale serve 設定 ──────────────────────────────────────────────────────
setup_tailscale_serve() {
info "Tailscale serve を設定しています (HTTPS → localhost:${OPENCODE_PORT})..."
# 既存設定をリセット(冪等性確保)
sudo tailscale serve reset 2>/dev/null || true
# 新構文: tailscale serve --bg http://127.0.0.1:<PORT>
sudo tailscale serve --bg "http://127.0.0.1:${OPENCODE_PORT}"
success "Tailscale serve を設定しました。"
# Tailscale ホスト名を取得
TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
self = d.get('Self', {})
dns = self.get('DNSName', '').rstrip('.')
print(dns if dns else self.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 (Tailscaleネットワーク内から):"
echo -e " ${CYAN}https://${TS_HOSTNAME}${NC}"
echo ""
if [[ -n "$OPENCODE_PASSWORD" ]]; then
echo -e " 🔐 認証情報:"
echo -e " ユーザー名: ${OPENCODE_USERNAME}"
echo -e " パスワード: (入力した値)"
else
echo -e " ⚠️ 認証なし(パスワード未設定)"
fi
echo ""
echo -e " 📋 管理コマンド:"
echo -e " ログ確認: journalctl -u opencode-web -f"
echo -e " 再起動: sudo systemctl restart opencode-web"
echo -e " 停止: sudo systemctl stop opencode-web"
echo -e " serve 確認: tailscale serve status"
echo ""
}
# ── メイン ────────────────────────────────────────────────────────────────────
main() {
prompt_password # ① まずパスワードを聞く(sudo前)
check_prerequisites # ② Tailscale確認
install_opencode # ③ opencode インストール
setup_workdir # ④ 設定ファイル生成
setup_systemd_service # ⑤ systemd サービス登録(sudo)
setup_tailscale_serve # ⑥ Tailscale serve 設定(sudo)
}
main "$@"
code-server
こちらも同じくLXDコンテナ内に直接インストール。
#!/bin/bash
# ============================================================
# Code-Server インストール(LXDコンテナ / Tailscale HTTPS版)
# 前提:
# - Ubuntu 26.04 LXDコンテナ内で実行
# - tailscale up 済み(tailscale serve でHTTPS化)
# - opencode web (port 4096) が既存で動作中
# - 実行: sudo bash install-codeserver.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=""
# ============================================================
# ① パスワードを最初に聞く
# ============================================================
prompt_password() {
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Code-Server + Tailscale Serve セットアップ ║${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
M)
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
;;
*)
PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
success "パスワードを自動生成しました。(後で表示します)"
;;
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} インストール完了"
}
# ============================================================
# ⑤ 設定ファイルの生成
# ============================================================
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 "argv.json(日本語化)を配置中..."
VSCODE_DIR="$REAL_HOME/.local/share/code-server/User"
mkdir -p "$VSCODE_DIR"
cat > "$VSCODE_DIR/argv.json" <<'EOF'
{
"locale": "ja"
}
EOF
chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/.local/share/code-server"
success "argv.json 配置完了"
}
# ============================================================
# ⑥ systemd サービスの登録・起動
# ============================================================
setup_systemd() {
info "systemd サービスを登録中..."
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}
[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 の起動に失敗しました"; }
}
# ============================================================
# ⑦ opencode の PATH を通す
# ============================================================
setup_path() {
info "opencode のバイナリパスを確認中..."
# 候補パスを優先順に検索
OPENCODE_BIN=""
for candidate in \
"/usr/local/bin/opencode" \
"$REAL_HOME/.local/bin/opencode" \
"$REAL_HOME/bin/opencode" \
"$REAL_HOME/.opencode/bin/opencode"; do
if [[ -x "$candidate" ]]; then
OPENCODE_BIN="$candidate"
break
fi
done
if [[ -z "$OPENCODE_BIN" ]]; then
warn "opencode バイナリが見つかりませんでした。PATH 設定をスキップします。"
warn "手動で確認: find /usr /home -name opencode -type f 2>/dev/null"
return
fi
OPENCODE_DIR=$(dirname "$OPENCODE_BIN")
info "opencode を発見: $OPENCODE_BIN"
# /usr/local/bin なら既に PATH が通っているはずなのでスキップ
if [[ "$OPENCODE_DIR" == "/usr/local/bin" ]]; then
success "opencode は /usr/local/bin にあるため PATH 設定不要です。"
return
fi
# ~/.bashrc と ~/.profile の両方に export を追記(重複しないよう確認)
PATH_LINE="export PATH=\"${OPENCODE_DIR}:\$PATH\""
for rc in "$REAL_HOME/.bashrc" "$REAL_HOME/.profile"; do
if [[ -f "$rc" ]] && grep -qF "$OPENCODE_DIR" "$rc"; then
info "$rc には既に設定済みです。スキップ。"
else
echo "" >> "$rc"
echo "# opencode" >> "$rc"
echo "$PATH_LINE" >> "$rc"
chown "$REAL_USER:$REAL_USER" "$rc"
success "$rc に PATH を追記しました。"
fi
done
# code-server の systemd 環境変数にも追加(ターミナル起動時に即反映)
if ! grep -q "OPENCODE" /etc/systemd/system/code-server.service; then
sed -i "/^ExecStart=/i Environment=PATH=${OPENCODE_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
/etc/systemd/system/code-server.service
systemctl daemon-reload
systemctl restart code-server
success "code-server の systemd に PATH を追加し再起動しました。"
fi
success "opencode PATH 設定完了 → ${OPENCODE_DIR}"
}
# ============================================================
# ⑧ Tailscale serve 設定(opencode の設定を維持しつつ追加)
# ============================================================
setup_tailscale_serve() {
info "Tailscale serve に code-server (port ${CODE_SERVER_PORT}) を追加中..."
# --https=8089 で code-server 専用ポートを公開
# opencode web の port 4096(デフォルト443)は既存設定のまま残る
tailscale serve --bg --https=${CODE_SERVER_PORT} "http://127.0.0.1:${CODE_SERVER_PORT}"
success "Tailscale serve に code-server を追加しました。"
# Tailscale ホスト名取得
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 (Tailscaleネットワーク内から):"
echo -e " code-server : ${CYAN}https://${TS_HOSTNAME}:${CODE_SERVER_PORT}${NC}"
echo -e " opencode web: ${CYAN}https://${TS_HOSTNAME}${NC} ← 既存(変更なし)"
echo ""
echo -e " 🔑 code-server パスワード: ${YELLOW}${PASSWORD}${NC}"
echo ""
echo -e " 🧩 日本語化手順:"
echo -e " 1. Extensions で 'Japanese Language Pack' をインストール"
echo -e " 2. Ctrl+Shift+P → 'Configure Display Language' → 日本語を選択"
echo ""
echo -e " 🤖 opencode の使い方:"
echo -e " code-server の統合ターミナルで opencode を実行してください"
echo -e " Ctrl+Esc → opencode をターミナルで開く"
echo -e " Ctrl+Shift+Esc → 新規 opencode セッションを開始"
echo -e " Alt+Ctrl+K → @ファイル参照を挿入"
echo ""
echo -e " 📋 管理コマンド:"
echo -e " ログ確認: journalctl -u code-server -f"
echo -e " 再起動: sudo systemctl restart code-server"
echo -e " serve 確認: tailscale serve status"
echo ""
}
# ============================================================
# メイン
# ============================================================
main() {
prompt_password # ① パスワードを最初に聞く
check_prerequisites # ② Tailscale確認
install_deps # ③ 依存パッケージ
install_code_server # ④ code-server インストール
setup_config # ⑤ 設定ファイル生成
setup_systemd # ⑥ systemd 登録・起動
setup_path # ⑦ opencode PATH設定
setup_tailscale_serve # ⑧ Tailscale serve 設定
}
main "$@"
以降は、このcode-server上で作業。
Filebrowser
LXDコンテナ内のファイルへアクセスしやすいように。コンテナ内なので、ルートをマウントする設定にしています。コンテナ内に直接インストールします。
#!/bin/bash
set -e
# ── 1. バイナリをインストール ──────────────────────────────
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
# → /usr/local/bin/filebrowser に配置される
# ── 2. DB・設定ディレクトリを作成 ────────────────────────
mkdir -p /var/lib/filebrowser
# ── 3. 設定ファイルを作成 ──────────────────────────────────
cat > /etc/filebrowser.json <<'EOF'
{
"port": 8080,
"baseURL": "",
"address": "127.0.0.1",
"log": "stdout",
"database": "/var/lib/filebrowser/filebrowser.db",
"root": "/"
}
EOF
# ── 4. DB 初期化(ランダムパスワード生成)──────────────────
ADMIN_PASS=$(openssl rand -base64 12)
filebrowser config init --config /etc/filebrowser.json
filebrowser users add admin "${ADMIN_PASS}" --perm.admin \
--config /etc/filebrowser.json
# ── 5. systemd サービス登録 ───────────────────────────────
cat > /etc/systemd/system/filebrowser.service <<'EOF'
[Unit]
Description=File Browser
After=network.target
[Service]
ExecStart=/usr/local/bin/filebrowser --config /etc/filebrowser.json
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now filebrowser
# ── 6. Tailscale Serve(未設定の場合のみ)─────────────────
if ! tailscale serve status 2>/dev/null | grep -q ":8080"; then
tailscale serve --bg --https=8080 http://127.0.0.1:8080
sleep 2
fi
TAILSCALE_URL=$(tailscale serve status 2>/dev/null | grep -oP 'https://[^\s]+(?=.*8080)' | head -1)
# ポート付きURLを抽出(例: https://hostname.tailnet.ts.net:8080)
if [ -z "$TAILSCALE_URL" ]; then
TAILSCALE_URL=$(tailscale serve status 2>/dev/null | grep "8080" | grep -oP 'https://\S+')
fi
echo "========================================="
echo " File Browser セットアップ完了"
echo "========================================="
echo ""
echo "【認証情報】"
echo " ユーザー名 : admin"
echo " パスワード : ${ADMIN_PASS}"
echo ""
echo "【アクセスURL】"
echo " Tailscale : ${TAILSCALE_URL:-'URL取得失敗 → tailscale serve status で確認'}"
echo " ローカル : http://127.0.0.1:8080"
echo ""
echo "【アクセス制限】"
echo " LAN からのアクセス : 不可 (127.0.0.1バインド)"
echo " Tailscale経由 : 可 (tailnet only)"
echo ""
echo "【サービス状態】"
systemctl status filebrowser --no-pager
echo "========================================="
パスワードはここでしか確認出来ないので、メモしておきましょう。
ログインしたら、プロフィール設定の項目で日本語メニューに変更出来ます。またこのページでパスワードを変更出来ます。パスワードは12文字以上となっています。
マウント場所の変更はnano /etc/filebrowser.jsonで変更しsystemctl restart filebrowserでサービス再起動。
LXDコンテナ内にLXD-UIインストール
ここで再度LXDコンテナをインストール
#!/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個のスクリプトを一度に作成して保存するスクリプト
コピーボタンでまとめてコピーして、ターミナルに貼り付けて実行するだけです。
実行後、以下の6ファイルが /opt/script/lxd/ に作成され、実行権限も付与されます。
生成される6つのスクリプトの内容:
| ファイル名 | 内容 |
|---|---|
minimal-lxd-base-create.sh | コンテナ作成・マウントディレクトリチェック付き |
first-setup-minimal-lxd-base.sh | apt update/upgrade・curl・Tailscaleインストール後シャットダウン |
docker-lxd-base-create.sh | lxd-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:26.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


