Ubuntu 26.04 LTS環境での初期セットアップ

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.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: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
タイトルとURLをコピーしました