【完全版】sudo なしでヘッドレスサーバの codex login を成功させる実戦ガイド

に公開

はじめに

本記事では、sudo権限なしの環境で、ヘッドレスなリモートサーバ上での codex login を高確率で成功させるための実戦的なガイドを提供します。

SSH ローカルフォワードを用いたトンネリング、ポート競合時の回避策(ローカル側ポート変更・URL書き換え・デバイスコード方式など)を網羅的に解説します。


0. TL;DR(最短手順)

前提条件

  • ローカルマシンにブラウザがある
  • リモートホスト名は golgi~/.ssh/config で設定済み)
  • codex はリモートで実行

最短3ステップ

ステップ1: ローカルでSSHトンネルを作成

ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:1455:127.0.0.1:1455 golgi

ステップ2: リモートで codex を起動

ssh golgi
codex login

ステップ3: 表示されたURLをローカルブラウザで開く

端末に表示された長いURLをローカルのブラウザで開きます。


1. 仕組み:なぜSSHトンネルが必要なのか

OAuth認証フローの課題

  1. codex login は一時的なHTTPサーバを リモート側の 127.0.0.1:1455 に立ち上げる
  2. ブラウザはローカルにあるため、そのままではコールバックがローカルに落ちてしまう
  3. OAuth認証のコールバックがリモートサーバに届かず、認証が完了しない

SSHローカルフォワードによる解決

SSH の -L オプションでトンネルを作成することで、以下の通信経路を確立します:

ローカルブラウザ → ローカル:1455 → [SSHトンネル] → リモート:1455 → codex

これにより、ローカルブラウザからのコールバックがリモートの codex プロセスに正しく届きます。


2. 用語の整理

本記事での用語の定義を明確にしておきます。

用語 説明
ローカル あなたのPC(ブラウザがある側)
リモート サーバ(codex login を実行する側)
トンネル SSH ローカルフォワード(-L オプション)

3. 標準手順(再現性重視)

3.1 前提確認

以下を事前に確認してください:

  • ローカルから ssh golgi でログインできる
  • ローカルマシンにブラウザがある
  • sudo権限は不要(一般ユーザー権限のみで完結)

3.2 実行手順

(1) ローカルでSSHトンネルを作成

ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:1455:127.0.0.1:1455 golgi

オプションの説明:

  • -f: バックグラウンドで実行
  • -N: リモートコマンドを実行しない(トンネル専用)
  • -o ExitOnForwardFailure=yes: ポートフォワードに失敗したら即座に終了

(2) ローカルでLISTEN状態を確認

Linux:

ss -ltn 'sport = :1455' 2>/dev/null || true

macOS:

lsof -iTCP:1455 -sTCP:LISTEN -n -P || true

(3) リモートで codex を実行

ssh golgi
codex login

端末に表示されたURLをローカルのブラウザで開きます。

(4) 終了後、トンネルをクローズ

pkill -f 'ssh -N -L .*1455:127\.0\.0\.1:1455' || true

4. エラーの切り分け(一次診断)

ローカルで ssh -L が失敗する場合

エラーメッセージ:

bind [127.0.0.1]:1455: Address already in use

原因: ローカルのポート1455が既に使用されている

確認方法: 5.1節 を参照


リモートで codex login が失敗する場合

エラーメッセージ:

Port 127.0.0.1:1455 is already in use

原因: リモートのポート1455が占有されている(過去の codex プロセスや他ユーザーのプロセス)

確認方法: 5.2節 を参照


ブラウザ認証後も完了しない場合

症状: ブラウザで認証したのに codex が反応しない

原因:

  • トンネルが張られていない
  • URLが localhost のままでIPv6に流れている
  • ローカル側で別ポートを使用しているのにURL未調整

対処: 5.3節 および 5.4節 を参照


5. トラブルシューティング

5.1 ローカル側のポート競合

症状

ssh -L 実行時に以下のエラーが表示される:

bind [127.0.0.1]:1455: Address already in use

確認コマンド

Linux:

ss -ltn 'sport = :1455' 2>/dev/null || true

macOS:

lsof -iTCP:1455 -sTCP:LISTEN -n -P 2>/dev/null || true

プロセス確認:

pgrep -af 'ssh -N -L .*1455:127\.0\.0\.1:1455|codex' || true

対処法A: 既存プロセスの終了

自分のSSH/codexプロセスを終了させます:

pkill -f 'ssh -N -L .*1455:127\.0\.0\.1:1455' || true
pkill -f '^codex .*login' || true

対処法B: ローカルで別ポートを使用

ローカル側のみ別ポート(例: 21455)を使用します:

LPORT=21455
ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:$LPORT:127.0.0.1:1455 golgi

LISTEN確認:

ss -ltn "sport = :$LPORT" 2>/dev/null || true

対処法C: ポート自動割当て

ローカルポートを自動割当て(:0)にする方法:

ssh -vfN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:0:127.0.0.1:1455 golgi 2> /tmp/ssh-tunnel.log

LPORT=$(grep -oE 'Local forwarding listening on 127\.0\.0\.1 port [0-9]+' \
  /tmp/ssh-tunnel.log | awk '{print $NF}')

echo "Local port = $LPORT"

その後、コールバックURLの :1455:$LPORT に書き換えて開きます。


5.2 リモート側のポート競合

症状

codex login 実行時に以下のエラーが表示される:

Port 127.0.0.1:1455 is already in use

確認コマンド(リモートで実行)

# ポート使用状況
ss -ltn 'sport = :1455' || true

# 自分のcodex/sshプロセス
pgrep -af 'codex|ssh.* -R .*1455' || true

# 詳細表示
ps -u "$USER" -f | egrep 'codex|ssh.*-R|ssh.*-L' | grep -v egrep || true

対処法A: 自分のプロセスを終了

pkill -f '^codex .*login' || true
pkill -f 'ssh.* -R .*1455' || true

対処法B: codex のポート変更機能を利用

他ユーザーがポートを使用している場合、kill できません(sudo不要縛りの制約)。

codex がコールバックポートの変更に対応している場合:

# まず確認
codex help login

# 例(実際のオプションはヘルプを確認)
codex login --port 31455

この場合、トンネルも新しいポートに合わせます:

RPORT=31455  # リモート側の新ポート
ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:1455:127.0.0.1:$RPORT golgi

対処法C: デバイスコード方式(推奨)

codex がデバイスコード方式をサポートしている場合、これが最も安定します(ポートを使用しません)。

手順:

  1. リモートでコマンド実行
  2. 端末にURLとコードが表示される
  3. ローカルのブラウザでURLを開き、コードを入力・承認
  4. リモートの codex が完了表示
# 例(実際のコマンドは codex help login で確認)
codex login --device

5.3 IPv6による接続失敗

症状

ブラウザで認証したのに codex が完了しない、または接続失敗する。

原因

URLの localhost が環境によってIPv6(::1)に解決され、トンネルがIPv4(127.0.0.1)を待ち受けているため。

対処

認証URLの localhost127.0.0.1 に置換して開きます。

ローカルで別ポートを使用している場合は、:1455:LPORT に書き換えます。


5.4 URL書き換えの具体例

シナリオ

  • ローカルポートを LPORT=21455 でトンネル作成済み
  • 認証後のリダイレクト先が以下のURL:
http://localhost:1455/auth/callback?code=ABC123&state=XYZ789

対処

以下のように書き換えて開きます:

http://127.0.0.1:21455/auth/callback?code=ABC123&state=XYZ789

ワンライナーでの変換

手作業が面倒な場合は以下のように変換できます:

URL='http://localhost:1455/auth/callback?code=ABC123&state=XYZ789'
LPORT=21455
echo "$URL" | sed -E "s#localhost(:1455)?#127.0.0.1:${LPORT}#"

5.5 多段SSH(JumpHost経由)

~/.ssh/config での設定

Host golgi
  HostName 192.168.1.100
  ProxyJump bastion
  User myuser

トンネル作成コマンド

ProxyJump を設定済みの場合、-J オプションは不要です:

ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:1455:127.0.0.1:1455 golgi

明示的に指定する場合:

ssh -fN -o ExitOnForwardFailure=yes \
  -J bastion \
  -L 127.0.0.1:1455:127.0.0.1:1455 golgi

5.6 バックグラウンドプロセスの罠

注意点

tmux/screen や autossh などで、背景に残ったトンネルが無自覚に生きていることがあります。

確認と終了

# トンネルプロセスを終了
pkill -f 'ssh -N -L .*1455:127\.0\.0\.1:1455' || true

# 親プロセスの確認(autossh等で自動復活していないか)
ps -o pid,ppid,cmd --forest -p <PID>

6. 代替フロー(高確率な別手段)

6.1 デバイスコード方式(推奨)

特徴

  • ポートを一切使用しない
  • 最も安定した方式
  • codex がサポートしている必要あり

手順

# 対応しているか確認
codex help login

# デバイスコード方式で実行(例)
codex login --device
  1. リモートでコマンド実行 → 端末にURLとコードが表示
  2. ローカルのブラウザでURLを開き、コードを入力・承認
  3. リモートの codex が完了表示

6.2 トンネルなしの非常手段

手順

1. リモートで codex を起動

codex login

2. ローカルブラウザで認証

表示されたURLをローカルブラウザで開く

3. コールバックURLをコピー

ブラウザが最終的に開こうとするURL:

http://localhost:1455/auth/callback?code=...&state=...

を丸ごとコピー

4. リモートで curl を実行

curl -iL "http://127.0.0.1:1455/auth/callback?code=...&state=..."

6.3 固定別ポート運用

社内端末でポート1455が恒常的に埋まっている場合、固定で別ポートを使用する運用も有効です。

設定例

# ローカル側を固定で21455にする
LPORT=21455
ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:$LPORT:127.0.0.1:1455 golgi

URL変換スクリプト

# ブラウザで表示された失敗URLをコピー → 端末で変換
URL='http://localhost:1455/auth/callback?...'
LPORT=21455
echo "$URL" | sed -E "s#localhost(:1455)?#127.0.0.1:${LPORT}#"

変換後のURLをブラウザで開きます。


7. 疎通確認とデバッグ

ローカルでの確認

Linux:

ss -ltn 'sport = :1455' 2>/dev/null || true

macOS:

lsof -iTCP:1455 -sTCP:LISTEN -n -P 2>/dev/null || true

リモートでの確認

ss -ltn 'sport = :1455' || true

トンネル越し到達確認

ローカルから実行:

# リモートにcodexのHTTPサーバが立っていれば 200/302/404 等が返る
curl -i http://127.0.0.1:1455/ || true

ローカルで別ポートを使用している場合:

curl -i http://127.0.0.1:$LPORT/ || true

プロセス確認

自分のプロセスのみ確認:

pgrep -u "$USER" -af 'codex|ssh.*-L|ssh.*-R' || true

自分のプロセスのみ終了:

pkill -u "$USER" -f '^codex .*login' || true
pkill -u "$USER" -f 'ssh -N -L .*1455:127\.0\.0\.1:1455' || true

8. ワンライナー集

一撃ログイン(標準)

ターミナルA(ローカル):

ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:1455:127.0.0.1:1455 golgi

ターミナルB(リモート):

ssh golgi && codex login

ローカル別ポート版

LPORT=21455
ssh -fN -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:$LPORT:127.0.0.1:1455 golgi

# 以後、ブラウザの最終URL :1455 → :$LPORT に置換して開く

残骸掃除(ローカル)

( ss -ltn 'sport = :1455' 2>/dev/null || true ); \
( lsof -iTCP:1455 -sTCP:LISTEN -n -P 2>/dev/null || true ); \
( pkill -f 'ssh -N -L .*1455:127\.0\.0\.1:1455' || true ); \
( pkill -f '^codex .*login' || true )

残骸掃除(リモート、自分のプロセスのみ)

pkill -u "$USER" -f '^codex .*login' || true
pkill -u "$USER" -f 'ssh.* -R .*1455' || true

9. セキュリティ上の注意事項

バインドアドレスの制限

# ✅ 良い例(127.0.0.1に限定)
ssh -L 127.0.0.1:1455:127.0.0.1:1455 golgi

# ❌ 悪い例(0.0.0.0に晒される)
ssh -L 1455:127.0.0.1:1455 golgi

認可情報の取り扱い

  • URLに含まれる codestate パラメータは第三者と共有しない
  • 平文でログファイルや共有チャットに残さない
  • ブラウザの履歴に残ることに注意

トンネルのクリーンアップ

作業後は必ずトンネルを閉じます:

pkill -f 'ssh -N -L .*1455:127\.0\.0\.1:1455' || true

使い回しを避けるため、成功直後に不要なトンネルは落とすことを推奨します。


10. エラーメッセージ早見表

エラーメッセージ 発生場所 原因 対処法
bind [127.0.0.1]:1455: Address already in use ローカル ローカルの1455が占有されている 5.1節参照
Port 127.0.0.1:1455 is already in use リモート リモートの1455が占有されている 5.2節参照
認証後も完了しない(無反応) ブラウザ コールバックがリモートに届いていない 5.3節5.4節参照

11. まとめ:sudo なしで安定運用するコツ

基本原理

ローカル→リモートのSSHローカルフォワードでOAuthコールバックを橋渡しする。

推奨アプローチ

  1. まずは標準ポート(1455)を使用

    • 最もシンプルで成功率が高い
  2. 失敗したらローカル別ポートに切り替え

    • ローカル側のみポート番号を変更
    • コールバックURLのポート番号を書き換えて開く
  3. リモート側が埋まっている場合

    • 自分のプロセスのみ掃除
    • それでもダメなら codex のポート変更機能を使用
    • デバイスコード方式が利用可能ならそれを使う

sudo なし環境の制約

  • 自分のプロセスしか kill できない
  • 他ユーザーのプロセスがポートを占有している場合は調整が必要
  • ランダム高番ポート活用とURL書き換え運用が高確度

補足事項

codex のバージョン依存

codex がポート変更やデバイスコードをサポートしているかはバージョンによります。

必ず以下を確認してください:

codex help login
# または公式ドキュメントを参照

URL書き換え運用の互換性

本記事で紹介したURL書き換え運用は、多くのCLI/OAuth実装で問題なく動作しますが、プロダクト固有の検証が厳しい場合は挙動が異なることがあります。

その際はデバイスコード方式が最も堅牢です。


おわりに

本記事では、sudo権限なしでヘッドレスサーバ上の codex login を成功させるための包括的なガイドを提供しました。

基本的なSSHトンネリングから、ポート競合時の回避策、デバッグ方法まで網羅していますので、環境に応じて適切な方法を選択してください。

不明点があれば、お使いの環境(OS、sshのバージョン、codexのバージョン)を添えてコメントいただければ幸いです。


参考情報

  • SSH ポートフォワーディングについては man ssh を参照
  • OAuth 2.0 の仕様は RFC 6749 を参照
  • Device Authorization Grant(デバイスコードフロー)は RFC 8628 を参照

Discussion