macOSとLinux devcontainer間のシームレスなクリップボード共有

現代の開発環境において、コンテナ化は一貫性のある独立した開発環境を作るための定番ソリューションとなっています。しかし、devcontainerを使用する際、ホストシステムとコンテナ間でのシームレスなクリップボード共有が欠如していることがよくある問題です。特に、ホストとしてmacOSを使用し、Linuxベースのdevcontainerを使用する場合、この問題は顕著になります。

この記事では、macOSとLinuxdevcontainer間でクリップボード共有を可能にする堅牢なソリューションを探ります。このセットアップにより、devcontainerからテキストをコピーし、macOS上のアプリケーションに直接ペーストすることができるようになり、ワークフローがよりスムーズで効率的になります。

## 課題

devcontainerで作業する際、クリップボードはホストシステムから分離されています。つまり、コンテナ内でテキストをコピーしても（`xclip`や`xsel`などのコマンドを使用）、macOSのクリップボードでは利用できません。この分離は、devcontainerとMac上の他のアプリケーション間でコードやテキストのスニペットを頻繁に転送する必要がある場合、ワークフローを著しく遅くする可能性があります。

## 解決策

私たちの解決策は、macOS上にクリップボードリレーを作成し、devcontainer内にクリップボードコマンドを傍受するカスタムスクリプトを作成することです。そして、SSH逆ポートフォワーディングを使用して、2つの環境間に安全なトンネルを作成します。

以下がコンポーネントの概要です：

1. `socat`を使用したmacOS上のクリップボードリレースクリプト。
2. `xclip`と`xsel`を置き換えるdevcontainer内のカスタムスクリプト。
3. 2つを接続するためのSSH逆ポートフォワーディング。

それでは、ステップバイステップで見ていきましょう。

### ステップ1：macOSクリップボードリレーのセットアップ

まず、macOS上で着信クリップボードデータをリッスンし、macOSクリップボードに転送するスクリプトを作成する必要があります。以下がそのスクリプトです：

```bash
#!/bin/bash

# Install socat if not already installed
if ! command -v socat &> /dev/null; then
    echo "socat is not installed. Installing..."
    brew install socat
fi

# Function to start the clipboard relay
start_clipboard_relay() {
    echo "Starting clipboard relay..."
    socat tcp-listen:8121,fork,bind=127.0.0.1,reuseaddr EXEC:'pbcopy',nofork &
    echo $! > /tmp/clipboard_relay.pid
}

# Check if the relay is already running
if [ -f /tmp/clipboard_relay.pid ]; then
    if ps -p $(cat /tmp/clipboard_relay.pid) > /dev/null; then
        echo "Clipboard relay is already running."
    else
        start_clipboard_relay
    fi
else
    start_clipboard_relay
fi

# Keep the script running
while true; do
    sleep 60
done
```

このスクリプトを`macos_clipboard_relay.sh`として保存し、`chmod +x macos_clipboard_relay.sh`で実行可能にします。このスクリプトは、macOS上に`socat`リスナーをセットアップし、着信データを`pbcopy`に転送して、効果的にmacOSクリップボードに配置します。

### ステップ2：devcontainerクリップボード転送のセットアップ

次に、devcontainer内に`xclip`と`xsel`コマンドを傍受し、クリップボードの内容をmacOSに転送するスクリプトを作成する必要があります。以下がそのスクリプトです：

```bash
#!/bin/bash

# Install netcat if not already installed
sudo apt-get update && sudo apt-get install -y netcat

# Create the updated clipboard forwarding script
cat << EOF > /usr/local/bin/clipboard_forward.sh
#!/bin/bash
echo "Debug: clipboard_forward.sh called with args: \$@" >> /tmp/clipboard_debug.log

# Function to forward to netcat
forward_to_netcat() {
    echo "Debug: Forwarding input to netcat" >> /tmp/clipboard_debug.log
    tee /tmp/clipboard_content.log | nc -w 1 localhost 8121
    echo "Debug: netcat exit code: \$?" >> /tmp/clipboard_debug.log
}

# Check if we're being asked to output clipboard content
if [[ "\$*" == *"-o"* ]] || [[ "\$*" == *"--output"* ]]; then
    echo "Debug: Output requested, but not implemented" >> /tmp/clipboard_debug.log
    exit 0
fi

# If -selection clipboard is present, read from stdin
if [[ "\$*" == *"-selection clipboard"* ]]; then
    forward_to_netcat
    exit 0
fi

# For other cases, check for input flags
for arg in "\$@"
do
  case "\$arg" in
    -i|--input|-in)
    forward_to_netcat
    exit 0
    ;;
  esac
done

# If no matching args found, still forward (this catches the case with no args)
forward_to_netcat

echo "Debug: End of script reached" >> /tmp/clipboard_debug.log
EOF

# Make the script executable
chmod +x /usr/local/bin/clipboard_forward.sh

# Create symlinks for xsel and xclip (if they don't exist)
ln -sf /usr/local/bin/clipboard_forward.sh /usr/local/bin/xsel
ln -sf /usr/local/bin/clipboard_forward.sh /usr/local/bin/xclip

# Add to PATH if necessary
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc

# Source .bashrc to update PATH in current session
source ~/.bashrc

echo "Clipboard forwarding set up using netcat with timeout"
```

このスクリプトをdevcontainer内に保存し、実行可能にして実行します。これにより、クリップボードの内容をmacOSホストに転送する偽の`xsel`と`xclip`コマンドがセットアップされます。

### ステップ3：すべてを接続する

devcontainerをmacOSに接続するには、SSH逆ポートフォワーディングを使用する必要があります。devcontainerに接続する際に、以下のコマンドを使用します：

```bash
ssh -R 8121:localhost:8121 user@your-devcontainer-host
```

これにより、devcontainerのポート8121への接続がmacOSのポート8121に転送されます。

.ssh/configで設定するには次のようにします。

```config
Host devcontainer
  HostName devcontainer
  User root
  Port 22
  RemoteForward 8121 localhost:8121
  ServerAliveInterval 30
  ServerAliveCountMax 3
  ExitOnForwardFailure yes
```

## 使用方法

1. macOS上で`macos_clipboard_relay.sh`スクリプトを起動します。
2. SSH逆ポートフォワーディングを使用してdevcontainerに接続します。
3. devcontainer内で、クリップボード転送メカニズムをインストールするセットアップスクリプトを実行します。
4. これで、devcontainer内で`xclip`や`xsel`を使用するたびに、内容がmacOSクリップボードに転送されます。

例えば、devcontainer内で以下のように実行できます：

```bash
echo "devcontainerからこんにちは" | xclip -selection clipboard
```

このテキストがmacOSクリップボードからペースト可能になります。

## 結論

このセットアップにより、macOSホストとLinuxdevcontainer間でシームレスなクリップボード共有を楽しむことができます。このソリューションは、独立した開発環境と効率的なワークフローの必要性の間のギャップを埋め、devcontainerとmacOSアプリケーション間で簡単にコピー＆ペーストできるようにします。

devcontainerに接続する前にmacOSリレースクリプトを起動し、接続時には常に逆ポートフォワーディング付きのSSHコマンドを使用することを忘れないでください。ハッピーコーディング！