antix-code

0
sudo nano code.sh
sudo bash code.sh
#!/bin/bash
# ============================================================
#  Code-Server インストールスクリプト v3.1
#  antiX / runit / Tailscale HTTPS 対応
#
#  改善点(v3.0 から):
#    - 日本語化ウォームアップ起動を追加
#      (languagepacks.json を事前生成し、手動での
#        「Configure Display Language」操作を不要に)
#
#  改善点(v2 から):
#    - root レベル runit サービスで再起動後も確実に起動
#    - 既存インストール検出 & 上書き確認
#    - サービス起動の自動リトライ
#    - ロールバック機能(失敗時クリーンアップ)
#
#  構成:
#    - code-server を 127.0.0.1:8089 で待受
#    - Tailscale serve で tailnet 内 HTTPS 公開
#    - Japanese Language Pack 自動インストール
#
#  前提:
#    - tailscale up 済み
#    - 実行: sudo bash install-codeserver3.sh
#
#  使い方:
#    sudo bash install-codeserver3.sh              # 対話モード
#    sudo bash install-codeserver3.sh -p mypass   # パスワード指定
#    sudo bash install-codeserver3.sh -a          # 自動生成
#    sudo bash install-codeserver3.sh -y          # 確認なし
#    sudo bash install-codeserver3.sh -p mypass -P 9090 -u vscode
# ============================================================

set -euo pipefail

# --- 定数 ---
VERSION="3.1.0"
SV_DIR="/etc/sv/code-server"
SVC_LINK="/etc/service/code-server"
WRAPPER="/usr/local/bin/code-server-wrapper"
CODE_SERVER_BIN="/usr/bin/code-server"
LOG_FILE="/var/log/codeserver-install.log"
WARMUP_PORT=39999

# --- カラー ---
GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'
RED='\033[0;31m'; DIM='\033[2m'; 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}  $*" >&2; exit 1; }
log()     { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }

# --- オプション解析 ---
PASSWORD=""
CODE_SERVER_PORT=8089
REAL_USER=""
AUTO_PASS=false
YES_MODE=false

usage() {
  cat <<EOF
Usage: sudo bash $0 [OPTIONS]

Options:
  -p, --password PASS   code-server のパスワードを指定
  -P, --port PORT       待受ポート(デフォルト: 8089)
  -u, --user USER       対象ユーザー(デフォルト: 実行ユーザー)
  -a, --auto-password   パスワードを自動生成
  -y, --yes             確認プロンプトをスキップ
  -h, --help            ヘルプ表示

Examples:
  sudo bash $0                          # 対話モード
  sudo bash $0 -a                       # パスワード自動生成
  sudo bash $0 -p mypassword            # パスワード指定
  sudo bash $0 -p pass -P 9090 -u bob   # 全パラメータ指定
  sudo bash $0 -a -y                    # 自動生成・確認なし
EOF
  exit 0
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -p|--password)   PASSWORD="$2"; shift 2 ;;
    -P|--port)       CODE_SERVER_PORT="$2"; shift 2 ;;
    -u|--user)       REAL_USER="$2"; shift 2 ;;
    -a|--auto-password) AUTO_PASS=true; shift ;;
    -y|--yes)        YES_MODE=true; shift ;;
    -h|--help)       usage ;;
    *) error "不明なオプション: $1(--help で確認)" ;;
  esac
done

# --- 前処理 ---
[[ $EUID -ne 0 ]] && error "root権限で実行してください: sudo bash $0"

REAL_USER="${REAL_USER:-${SUDO_USER:-${USER:-$(id -un)}}}"
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)

# ログファイル初期化
: > "$LOG_FILE"
log "Installation started (v${VERSION})"

# ============================================================
# ロールバック: 失敗時にクリーンアップ
# ============================================================
cleanup_on_error() {
  local exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    warn "インストール中にエラーが発生しました(終了コード: ${exit_code})"
    log "ERROR: Installation failed with exit code ${exit_code}"

    # 部分的に作成されたサービスを停止
    sv stop code-server 2>/dev/null || true
    rm -f "$SVC_LINK" 2>/dev/null || true

    error "インストールに失敗しました。ログ: ${LOG_FILE}"
  fi
}
trap cleanup_on_error EXIT

# ============================================================
# 0. 既存インストールの確認
# ============================================================
check_existing() {
  if command -v code-server &>/dev/null; then
    EXISTING_VER=$(code-server --version 2>/dev/null | head -1 || echo "unknown")
    info "code-server が既にインストールされています: ${EXISTING_VER}"

    if sv status code-server 2>/dev/null | grep -q "^run:"; then
      info "現在稼働中です。"
    fi

    if [[ "$YES_MODE" == false ]]; then
      echo -e "  ${YELLOW}[R]${NC} 再インストール(上書き)"
      echo -e "  ${YELLOW}[K]${NC} キャンセル"
      echo ""
      read -r -p "選択 [R/K] (Enterでキャンセル): " choice
      case "${choice^^}" in
        R) info "再インストールを実行します..." ;;
        *) info "キャンセルしました。"; exit 0 ;;
      esac
    fi

    # 既存サービスを停止
    sv stop code-server 2>/dev/null || true
    rm -f "$SVC_LINK" 2>/dev/null || true
    log "Existing installation detected, reinstalling"
  fi
}

# ============================================================
# 1. パスワード設定
# ============================================================
prompt_password() {
  if [[ -n "$PASSWORD" ]]; then
    success "コマンドライン引数でパスワードを指定しました"
    return
  fi

  if [[ "$AUTO_PASS" == true ]]; then
    PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
    success "パスワードを自動生成しました"
    return
  fi

  echo ""
  echo -e "${CYAN}╔══════════════════════════════════════════════════════╗${NC}"
  echo -e "${CYAN}║   Code-Server + Tailscale Serve セットアップ         ║${NC}"
  echo -e "${CYAN}║   v${VERSION}  antiX / runit / tailnet 内 HTTPS 公開    ║${NC}"
  echo -e "${CYAN}║   日本語化セットアップ済み                            ║${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
    A)
      PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
      success "パスワードを自動生成しました。(後で表示します)"
      ;;
    *)
      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
      ;;
  esac
  echo ""
}

# ============================================================
# 2. 前提条件チェック
# ============================================================
check_prerequisites() {
  info "前提条件を確認中..."
  command -v tailscale &>/dev/null \
    || error "tailscale がインストールされていません。先に tailscale をセットアップしてください。"
  tailscale status &>/dev/null \
    || error "tailscale が起動していません。'tailscale up' を先に実行してください。"
  command -v apt-get &>/dev/null \
    || error "apt-get が見つかりません。Debian/Ubuntu 系のシステムが必要です。"
  success "前提条件 OK"
}

# ============================================================
# 3. 依存パッケージ
# ============================================================
install_deps() {
  info "依存パッケージをインストール中..."
  apt-get update -qq
  apt-get install -y -qq curl wget openssl
  success "依存パッケージ完了"
}

# ============================================================
# 4. 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 || apt-get -f install -y
  rm -f /tmp/code-server.deb
  success "code-server v${LATEST} インストール完了"
  log "Installed code-server v${LATEST}"
}

# ============================================================
# 5. Japanese Language Pack
# ============================================================
install_japanese_language_pack() {
  info "Japanese Language Pack をインストール中..."
  EXT_DIR="$REAL_HOME/.local/share/code-server/extensions"
  mkdir -p "$EXT_DIR"
  chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/.local/share/code-server"

  sudo -u "$REAL_USER" code-server --install-extension MS-CEINTL.vscode-language-pack-ja \
    || warn "Japanese Language Pack のインストールに失敗しました(後で手動インストール可能)"

  success "Japanese Language Pack 完了"
}

# ============================================================
# 6. 設定ファイル生成
# ============================================================
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 "settings.json(日本語化 + 推奨設定)を配置中..."
  VSCODE_USER_DIR="$REAL_HOME/.local/share/code-server/User"
  mkdir -p "$VSCODE_USER_DIR"

  cat > "$REAL_HOME/.local/share/code-server/argv.json" <<'EOF'
{
  "locale": "ja"
}
EOF

  cat > "$VSCODE_USER_DIR/locale.json" <<'EOF'
{
  "locale": "ja"
}
EOF

  cat > "$VSCODE_USER_DIR/settings.json" <<'EOF'
{
  "editor.fontSize": 14,
  "editor.tabSize": 2,
  "editor.formatOnSave": true,
  "editor.minimap.enabled": false,
  "workbench.colorTheme": "Default Dark Modern",
  "terminal.integrated.fontSize": 13,
  "files.autoSave": "afterDelay",
  "files.autoSaveDelay": 1000
}
EOF

  chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/.local/share/code-server"
  success "settings.json / argv.json / locale.json 配置完了"
}

# ============================================================
# 6.5. 日本語化ウォームアップ起動(languagepacks.json 生成)
# ============================================================
#  code-server は argv.json に "locale": "ja" を書いても、
#  languagepacks.json(言語パックの索引ファイル)が無いと
#  日本語UIとして認識されない。このファイルは通常
#  「Configure Display Language」をUIから実行したときだけ
#  自動生成され、--install-extension のようなCLI経由の
#  インストールでは生成されないことがある(既知の挙動)。
#
#  対策として、本番の runit サービス起動より前に
#  一度だけ code-server をバックグラウンドで起動して
#  終了させ、languagepacks.json を事前生成しておく。
# ============================================================
warmup_japanese_locale() {
  info "日本語化キャッシュを生成中(ウォームアップ起動)..."
  local lp_file="$REAL_HOME/.local/share/code-server/languagepacks.json"
  local warm_log="/tmp/code-server-warmup.log"

  if [[ -f "$lp_file" ]]; then
    success "languagepacks.json は既に存在します(スキップ)"
    return 0
  fi

  sudo -u "$REAL_USER" env HOME="$REAL_HOME" \
    VSCODE_NLS_CONFIG='{"locale":"ja","osLocale":"ja","availableLanguages":{"*":"ja"}}' \
    "$CODE_SERVER_BIN" --auth none --bind-addr "127.0.0.1:${WARMUP_PORT}" \
    > "$warm_log" 2>&1 &
  local warm_pid=$!

  local waited=0
  while [[ $waited -lt 15 ]]; do
    if [[ -f "$lp_file" ]]; then
      break
    fi
    sleep 1
    waited=$((waited + 1))
  done

  kill "$warm_pid" 2>/dev/null || true
  wait "$warm_pid" 2>/dev/null || true

  if [[ -f "$lp_file" ]]; then
    success "日本語化ウォームアップ完了(languagepacks.json 生成確認)"
    log "Warmup succeeded: languagepacks.json created"
  else
    warn "languagepacks.json が生成されませんでした"
    warn "初回アクセス時に手動で言語設定が必要になる場合があります(ログ: ${warm_log})"
    log "Warmup failed: languagepacks.json not created, see ${warm_log}"
  fi
}

# ============================================================
# 7. runit サービス(root レベル)
# ============================================================
setup_runit() {
  info "runit サービスを登録中(root レベル)..."
  NLS_CONFIG='{"locale":"ja","osLocale":"ja","availableLanguages":{"*":"ja"}}'

  cat > "$WRAPPER" <<WEOF
#!/bin/bash
export HOME="${REAL_HOME}"
export VSCODE_NLS_CONFIG='${NLS_CONFIG}'
exec ${CODE_SERVER_BIN} "\$@"
WEOF
  chmod +x "$WRAPPER"

  mkdir -p "$SV_DIR"
  cat > "$SV_DIR/run" <<'EOF'
#!/bin/bash
exec 2>&1
exec /usr/local/bin/code-server-wrapper
EOF
  chmod +x "$SV_DIR/run"

  ln -sfn "$SV_DIR" "$SVC_LINK"

  # 既存の user レベルサービスを削除
  rm -rf "$REAL_HOME/.runit/service/code-server" "$REAL_HOME/.runit/usersv/code-server" 2>/dev/null || true

  # 起動確認 with リトライ
  info "サービス起動確認中..."
  local retry=0
  local max_retry=5
  while [[ $retry -lt $max_retry ]]; do
    sleep 2
    if sv status code-server 2>/dev/null | grep -q "^run:"; then
      success "code-server 正常起動(root レベル runit)"
      log "runit service started successfully"
      return 0
    fi
    retry=$((retry + 1))
    if [[ $retry -lt $max_retry ]]; then
      warn "起動確認リトライ (${retry}/${max_retry})..."
      sv start code-server 2>/dev/null || true
    fi
  done

  warn "自動起動確認に失敗しました。手動で起動してください: sv start code-server"
  warn "ログ確認: sv status code-server -v"
}

# ============================================================
# 8. Tailscale serve 設定
# ============================================================
setup_tailscale_serve() {
  info "Tailscale serve に code-server (port ${CODE_SERVER_PORT}) を追加中..."

  # 既存の serve 設定があれば削除してから再設定
  tailscale serve --https="${CODE_SERVER_PORT}" off 2>/dev/null || true
  tailscale serve --bg --https=${CODE_SERVER_PORT} "http://127.0.0.1:${CODE_SERVER_PORT}"
  success "Tailscale serve 設定完了"

  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}  [OK] セットアップ完了! (v${VERSION})${NC}"
  echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo ""
  echo -e "  アクセスURL(tailnet 内のデバイスからのみアクセス可):"
  echo -e "     ${CYAN}https://${TS_HOSTNAME}:${CODE_SERVER_PORT}${NC}"
  echo ""
  echo -e "  code-server パスワード: ${YELLOW}${PASSWORD}${NC}"
  echo ""
  echo -e "  日本語化: インストール時に自動設定済み(再起動不要)"
  echo ""
  echo -e "  管理コマンド:"
  echo -e "     再起動:         sv restart code-server"
  echo -e "     停止:           sv stop code-server"
  echo -e "     ステータス:     sv status code-server"
  echo -e "     ログ:           tail -f /var/log/code-server/current"
  echo -e "     serve 確認:     tailscale serve status"
  echo -e "     serve 削除:     tailscale serve --https=${CODE_SERVER_PORT} off"
  echo ""
  echo -e "  [!] このパスワードを安全な場所に保管してください。"
  echo -e "     設定ファイル: ${REAL_HOME}/.config/code-server/config.yaml"
  echo ""

  log "Installation completed successfully"
}

# ============================================================
# メイン
# ============================================================
main() {
  check_existing           # 既存インストール確認
  prompt_password          # ① パスワード設定
  check_prerequisites      # ② Tailscale確認
  install_deps             # ③ 依存パッケージ
  install_code_server      # ④ code-server インストール
  install_japanese_language_pack  # ⑤ 日本語パック
  setup_config             # ⑥ 設定ファイル
  warmup_japanese_locale   # ⑥.5 日本語化ウォームアップ(新規)
  setup_runit              # ⑦ runit 登録(root レベル)
  setup_tailscale_serve    # ⑧ Tailscale serve 設定
}

main "$@"
タイトルとURLをコピーしました