ZIP配布からGitHub運用へ──トンネル越しにスクリプト管理を自動化した話

# 背景と課題

私の所属するチームでは、業務効率化のために私が作成したNode.jsスクリプトをメンバー間で共有していました。当初は、更新ごとにZIPファイルをフォルダにアップロードして配布するという原始的な方法を採用していました。しかし、次第に以下のような課題が浮上しました。

- **更新のたびにZIP作成とアップロードが必要**で、運用コストが高い
- **メンバーが最新版を導入するまでに時間差**があり、バージョンずれが発生
- **上書き対象の手動選択がミスを誘発**
- **バグ発生時に、どのバージョンが原因か特定しづらい**

# 解決策：GitHubによるスクリプト管理

私はこの問題を解決するため、**会社のGitHubリポジトリでスクリプトを一元管理**する仕組みを提案・実装しました。

#### しかし、壁があった…

会社のGitHubにアクセスするには、**Pomerium経由の社内トンネル接続**が必須で、CLIからのGit操作やスクリプトの自動化が困難でした。

# ①Pomeriumトンネルの自動生成

まず最初に取り組んだのが、Pomeriumバイナリを使ったトンネル作成の自動化です。

```typescript:CommandLine.ts
public initPomerium(): Promise<void> {
  const pomeriumPath = environment.CURRENT_OS === "win32"
    ? paths.POMERIUM_PATH_WIN
    : paths.POMERIUM_PATH_MAC;

  return new Promise<void>((resolve, reject) => {
    const pomeriumProcess = spawn(pomeriumPath, ["tcp", pomeriumHost, "--listen", `:${pomeriumPort}`]);

    pomeriumProcess.stderr.on("data", (chunk) => {
      const message = chunk.toString();
      if (message.includes("listening on")) {
        Logger.success(`Pomerium is listening on localhost:${pomeriumPort}`);
        resolve();
      } else {
        reject(new Error(`Failed to listen on ${pomeriumPort}`));
      }
    });
  });
}
```

この処理により、**OSを問わずPomeriumを自動で起動し、トンネルを確立**できるようにしました。

# ②Gitのプロキシ設定とCredential自動登録

Pomeriumトンネルが立った後は、Gitがトンネルを通してGitHubにアクセスできるよう、設定を自動化しました。

```typescript:CommandLine.ts
await exec("git config http.proxy 127.0.0.1:8080");
await exec("git config --global user.name xxx");
await exec("git config --global user.email xxx@example.com");

await exec([
  "git config",
  "--global",
  `credential.${hosts.GITHUB_URL}.provider`,
  "github"
].join(" "));
```

Gitリモート設定も自動で行い、**HTTPプロキシ越しでもシームレスにgit cloneできる**仕組みにしました。

# ③forwardLocalPortでトンネル確立をリファクタリング

さらに、**Pomerium経由でGitHubへアクセスするためのローカルポートフォワーディング**を自動化する`forwardLocalPort`メソッドも作成しました。

```typescript:CommandLine.ts
public forwardLocalPort(): Promise<void> {
  const pomeriumPath = environment.CURRENT_OS === "win32"
    ? paths.POMERIUM_PATH_WIN
    : paths.POMERIUM_PATH_MAC;
  let message = "";
  
  return new Promise<void>((resolve) => {
    const tunnel = spawn("ssh", [
      "-o", `ProxyCommand=${pomeriumPath} tcp --listen - %h:%p`,
      "-o", "MACs=hmac-sha2-256",
      "-o", `User=${credentials.SSH_USERNAME_STG}`,
      "-o", "StrictHostKeyChecking=no",
      "-L", "8080:localhost:80",
      hosts.REMOTE_DOMAIN_STG,
    ]);

    tunnel.stdout.on("data", (data) => {
      Logger.info(`stdout: ${data}`);
    });

    tunnel.stderr.on("data", (data) => {
      message += data.toString();
      if (message.includes("connection established")) {
        Logger.success(`Tunnel is listening on localhost:8080`);
        resolve();
      }
    });

    tunnel.on("close", (code) => {
      Logger.info(`SSH process exited with code ${code}`);
    });
  });
}
```

#### ここがポイント

- `forwardLocalPort` は **sshコマンド＋PomeriumのProxyCommand** を使って、**トンネル確立をコード上で完結**させています
- このメソッドを `clone` や `setup`、`pull` など**全てのGit操作前に呼び出す**ことで、**一切手作業なしでGitHubアクセスできるようになりました**

# ④npm run installだけで初期化一発

メンバーが簡単にセットアップできるよう、以下をまとめた初期化スクリプトも整備しました。

```typescript:CommandLine.ts
public async setup(): Promise<void> {
  await this.forwardLocalPort();
  await exec("git init");
  await exec("git config http.proxy 127.0.0.1:8080");
  await exec("git remote add origin http://<user>:<token>@repo.url");
  await exec("git fetch origin");
  await exec("git reset --hard origin/main");
  await exec("npm install");
}
```

結果、**「npm run install」コマンド一発で、誰でも数秒で環境構築できる世界**が実現しました。

# ⑤semantic-releaseでバージョン管理も自動化

コミットしてプッシュするだけで、semantic-releaseがリリース管理もしてくれる仕組みを導入しました。

```typescript:CommandLine.ts
public runSemantic(): void {
  execSync("npx semantic-release", { stdio: "inherit" });
}
```

これにより、**バージョンアップとリリースノート作成も自動化**でき、さらに運用負担を削減しました。

# 成果まとめ

- スクリプトの**配布・更新コストがゼロ**に
- **バージョン管理が明確化**し、トラブルシュートが容易に
- **非エンジニアでも簡単に導入できる**仕組みを実現
- **インフラ制約を乗り越えて社内GitHub連携を完全自動化**

単なるスクリプト共有の改善にとどまらず、**チーム全体の開発体験（DX）を向上させる仕組み作り**につなげることができた経験でした。