opencode webで手軽にローカルAI環境を構築

以前OpenWorkを紹介しましたが、コード生成であればopencodeで十分です。そのopencodeを手軽にWebから利用できる、opencode webを一発で導入出来るスクリプトです。

LXDコンテナで実行


LXDコンテナ内での実行を想定しており、Tailscale ServeでHTTPS化しています。tailscaleが有効なLXDコンテナ内で、下記スクリプトを貼り付けるだけです。

#!/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 "$@"

モデルを指定

画面下でモデルを変更出来ます。

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