diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 0000000..9dcac7b --- /dev/null +++ b/README.ja.md @@ -0,0 +1,59 @@ +

dots-tailscale

+ +

English · 한국어 · 简体中文 · 日本語

+ +Dots のクラウドコンピューターで、ユーザー権限の OpenSSH とともに Tailscale を実行します。インストールファイル、ノードの状態、SSH の ID 情報は `/workspace` に保存し、実行時のソケット、PID、ログは `/tmp` に作成し直します。 + +`/workspace` が環境の入れ替え後も保持される環境を対象としています。新しい環境では、その都度 start コマンドを実行する必要があります。このプロジェクトは独立したもので、OpenAI や Tailscale の公式ツールではありません。 + +## デモ + +![Dots ターミナルでインストール状況と Tailscale バージョン 1.102.4 を確認する画面](docs/assets/dots-tailscale-ssh-verification.png) + +Dots デスクトップのターミナルで撮影された、メンテナー提供のスクリーンショットです。既存のインストール状況とステータス、および Tailscale 1.102.4 のバージョンが表示されています。提供された画像をそのまま使用しており、新規インストールや VM の入れ替えを示すものではありません。 + +```bash +bash /workspace/.dots-tailscale/start.sh check +/workspace/.dots-tailscale/tailscale version +``` + +## クイックスタート + +Linux x86_64、Git、Python 3、OpenSSH、およびローカルポート 22 をバインドする権限が必要です。依存関係と SSH の設定については、[使用ガイド](docs/usage.ja.md)を参照してください。 + +確認済みの Dots SSH 環境では GitHub への HTTPS 直接接続に失敗したため、clone コマンドでは Dots のプロキシを明示的に指定しています。診断方法と設定の適用範囲は、[ダウンロード接続とプロキシ設定](docs/usage.ja.md#download-proxy)を参照してください。 + +```bash +cd /workspace +HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \ + git clone --branch v0.3.1 --depth 1 https://github.com/abruption/dots-tailscale.git +cd dots-tailscale +bash tailscale-setup.sh check +bash tailscale-setup.sh install +bash /workspace/.dots-tailscale/start.sh login +``` + +初回ログインでは承認用 URL が表示されます。クライアントの公開鍵をインストール済み SSH の `authorized_keys` ファイルに追加し、接続する前にクラウドコンピューターの GUI ターミナルでホスト鍵のフィンガープリントを確認してください。環境が入れ替わった後は、`bash /workspace/.dots-tailscale/start.sh` で既存のインストールを起動します。スクリプトは自動起動を登録しません。 + +## v0.3.1: 診断とプライベート HTTP 接続 + +これらのヘルパーを使うには、[v0.3.1](https://github.com/abruption/dots-tailscale/releases/tag/v0.3.1) のソースファイル 4 つをすべてインストールしてください。 + +```bash +/workspace/.dots-tailscale/dots-ts doctor +/workspace/.dots-tailscale/dots-ts cli status +/workspace/.dots-tailscale/dots-ts proxy show +``` + +`doctor` は状態を変更せず、ローカルの診断情報を読み取ります。`cli` はインストール済みの Tailscale バイナリとソケットを選択します。任意で使える loopback SOCKS5/HTTP プロキシは既定で無効です。有効にするには、デーモンを再起動する手順を別途確認してください。設定、アクセス方針、検証範囲については、[使用ガイド](docs/usage.ja.md#development-helpers)と[プライベート HTTP API の利用例](docs/private-http-api.ja.md)を参照してください。これらのプロキシは ChatGPT コネクター、MCP クライアント、Dots ブラウザーには自動設定されません。 + +## 同梱内容 + +| パス | 用途 | +|---|---| +| `tailscale-setup.sh`, `shim.py` | Tailscale の確認・インストール・起動、ローカル OpenSSH の設定、Tailscale の明示的な認証 | +| `dots-ts`, `dots_proxy.py` | CLI、診断、およびプロセスごとの任意の外向きプロキシ設定 | +| `home-persistence-probe.py` | HOME と `/workspace` の継続性を示すマーカーを記録 | +| `tests/` | オフライン回帰テスト | + +[英語の使用ガイド](docs/usage.md)、[韓国語の使用ガイド](docs/usage.ko.md)、[中国語(簡体字)の使用ガイド](docs/usage.zh-CN.md)、[日本語の使用ガイド](docs/usage.ja.md)、[MIT ライセンス](LICENSE)を参照してください。 diff --git a/README.ko.md b/README.ko.md index e056b19..3a32fd0 100644 --- a/README.ko.md +++ b/README.ko.md @@ -1,8 +1,8 @@

dots-tailscale

-

English · 한국어

+

English · 한국어 · 简体中文 · 日本語

-Dots 클라우드 컴퓨터에서 사용자 권한으로 Tailscale과 OpenSSH를 실행합니다. 설치 파일·장치 state·SSH identity는 `/workspace`, 실행 socket·PID·로그는 `/tmp`에 둡니다. +Dots 클라우드 컴퓨터에서 일반 사용자 권한으로 Tailscale과 OpenSSH를 실행합니다. 설치 파일, Tailscale 상태, SSH 키는 `/workspace`에 보관하고 실행 중 필요한 소켓·PID·로그는 `/tmp`에 둡니다. `/workspace`가 환경 교체 후 보존되는 환경을 대상으로 합니다. 새 환경에서는 start 명령을 실행해야 합니다. OpenAI 또는 Tailscale 공식 도구가 아닌 독립 프로젝트입니다. @@ -10,7 +10,7 @@ Dots 클라우드 컴퓨터에서 사용자 권한으로 Tailscale과 OpenSSH를 ![Dots 터미널에서 설치 상태와 Tailscale 버전 1.102.4를 확인하는 스크린샷](docs/assets/dots-tailscale-ssh-verification.png) -관리자가 제공한 Dots 데스크톱 터미널의 스크린샷입니다. 기존 설치 파일과 상태, Tailscale 1.102.4 버전을 확인하는 장면입니다. 제공된 이미지를 그대로 사용했으며, 새로 설치하는 과정은 포함하지 않습니다. +관리자가 제공한 Dots 데스크톱 터미널 화면입니다. 기존 설치 파일과 상태, Tailscale 1.102.4 버전을 확인한 모습이며, 새로 설치하거나 VM을 교체하는 장면은 아닙니다. ```bash bash /workspace/.dots-tailscale/start.sh check @@ -33,7 +33,7 @@ bash tailscale-setup.sh install bash /workspace/.dots-tailscale/start.sh login ``` -최초 로그인 URL을 승인한 뒤 SSH 공개키를 설치 경로의 `authorized_keys`에 등록하세요. 호스트 키 지문은 클라우드 컴퓨터의 GUI 터미널에서 확인한 후 SSH로 접속합니다. 환경이 교체되면 `bash /workspace/.dots-tailscale/start.sh`로 기존 설치를 수동 기동하세요. +최초 로그인 URL을 승인한 뒤 SSH 공개키를 설치 경로의 `authorized_keys`에 등록하세요. 호스트 키 지문은 클라우드 컴퓨터의 GUI 터미널에서 확인한 후 SSH로 접속합니다. 환경이 교체되면 `bash /workspace/.dots-tailscale/start.sh`로 기존 설치를 직접 실행해야 합니다. 스크립트는 자동 시작을 등록하지 않습니다. ## v0.3.1: 진단과 비공개 HTTP 접속 @@ -47,13 +47,13 @@ bash /workspace/.dots-tailscale/start.sh login `doctor`는 상태를 바꾸지 않고 로컬 진단 정보를 읽습니다. `cli`는 설치된 Tailscale 실행 파일과 소켓을 선택합니다. 선택 기능인 로컬 SOCKS5·HTTP 프록시는 기본적으로 꺼져 있으며, 활성화하려면 데몬 재시작을 별도로 검토해야 합니다. 설정과 접근 정책, 검증 범위는 [사용 가이드](docs/usage.ko.md#development-helpers)와 [비공개 HTTP API 연결 예시](docs/private-http-api.ko.md)를 참고하세요. ChatGPT 커넥터·MCP 클라이언트·Dots 브라우저에 자동으로 적용되지는 않습니다. -## 파일 +## 구성 요소 | 경로 | 역할 | |---|---| -| `tailscale-setup.sh`, `shim.py` | Tailscale·OpenSSH 점검, 설치, 기동 및 명시적 인증 | +| `tailscale-setup.sh`, `shim.py` | Tailscale을 점검·설치·기동하고 로컬 OpenSSH를 설정하며 Tailscale 인증을 명시적으로 수행 | | `dots-ts`, `dots_proxy.py` | CLI·진단과 프로세스별 외부 연결 프록시 설정 | -| `home-persistence-probe.py` | HOME과 `/workspace` 식별자 관찰 | +| `home-persistence-probe.py` | HOME과 `/workspace` 지속성 마커를 기록하고 변화 확인 | | `tests/` | 오프라인 회귀 테스트 | -[English guide](docs/usage.md) · [MIT License](LICENSE) +[English guide](docs/usage.md) · [한국어 사용 가이드](docs/usage.ko.md) · [简体中文使用指南](docs/usage.zh-CN.md) · [日本語の使い方](docs/usage.ja.md) · [MIT License](LICENSE) diff --git a/README.md b/README.md index 387d0a9..96fba7d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

dots-tailscale

-

English · 한국어

+

English · 한국어 · 简体中文 · 日本語

Run Tailscale with user-level OpenSSH in a Dots cloud computer. Installation files, node state, and SSH identity live in `/workspace`; runtime sockets, PIDs, and logs are recreated in `/tmp`. @@ -10,7 +10,7 @@ This setup targets environments that preserve `/workspace`. Processes still need ![Dots terminal screenshot showing installation checks and Tailscale version 1.102.4](docs/assets/dots-tailscale-ssh-verification.png) -A screenshot provided by the maintainer from the Dots desktop terminal. It shows existing installation checks and status, plus Tailscale version 1.102.4. The supplied image is used unchanged. No new installation is performed in this screenshot. +This screenshot, provided by the maintainer, shows the existing installation checks and status, plus Tailscale version 1.102.4, in the Dots desktop terminal. The image is used unchanged; it does not show a new installation or a VM replacement. ```bash bash /workspace/.dots-tailscale/start.sh check @@ -51,9 +51,9 @@ Install all four source files from [v0.3.1](https://github.com/abruption/dots-ta | Path | Purpose | |---|---| -| `tailscale-setup.sh`, `shim.py` | Check, install, start, and explicitly authenticate Tailscale with local SSH | +| `tailscale-setup.sh`, `shim.py` | Check, install, and start Tailscale; configure local OpenSSH; authenticate Tailscale explicitly | | `dots-ts`, `dots_proxy.py` | CLI, diagnostics, and optional per-process outbound proxy configuration | | `home-persistence-probe.py` | Record HOME and `/workspace` continuity markers | | `tests/` | Offline regression tests | -See the [English usage guide](docs/usage.md), [Korean usage guide](docs/usage.ko.md), and [MIT License](LICENSE). +See the [English usage guide](docs/usage.md), [Korean usage guide](docs/usage.ko.md), [Simplified Chinese usage guide](docs/usage.zh-CN.md), [Japanese usage guide](docs/usage.ja.md), and [MIT License](LICENSE). diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..1719da9 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,59 @@ +

dots-tailscale

+ +

English · 한국어 · 简体中文 · 日本語

+ +在 Dots 云电脑上以普通用户权限运行 Tailscale 和 OpenSSH。安装文件、节点状态和 SSH 身份保存在 `/workspace`;运行时套接字、PID 和日志会在 `/tmp` 中重新创建。 + +本方案适用于会保留 `/workspace` 的环境。每次进入新环境后,仍需运行启动命令。本项目独立开发,并非 OpenAI 或 Tailscale 的官方工具。 + +## 演示 + +![Dots 终端中的安装检查以及 Tailscale 版本 1.102.4 截图](docs/assets/dots-tailscale-ssh-verification.png) + +这是维护者提供的 Dots 桌面终端截图,显示了已有安装的检查结果和状态,以及 Tailscale 版本 1.102.4。截图使用了原始图片,并未展示新安装或 VM 替换。 + +```bash +bash /workspace/.dots-tailscale/start.sh check +/workspace/.dots-tailscale/tailscale version +``` + +## 快速开始 + +需要 Linux x86_64、Git、Python 3、OpenSSH,以及绑定本地 22 端口的权限。完整依赖和 SSH 设置请参阅[使用指南](docs/usage.zh-CN.md)。 + +克隆命令明确使用 Dots 代理,因为在经过测试的 Dots SSH 环境中,直接通过 HTTPS 连接 GitHub 失败了。有关诊断方法和这些设置的适用范围,请参阅[下载连接与代理设置](docs/usage.zh-CN.md#download-proxy)。 + +```bash +cd /workspace +HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \ + git clone --branch v0.3.1 --depth 1 https://github.com/abruption/dots-tailscale.git +cd dots-tailscale +bash tailscale-setup.sh check +bash tailscale-setup.sh install +bash /workspace/.dots-tailscale/start.sh login +``` + +首次登录时会显示一个授权链接。将客户端公钥添加到已安装 SSH 的 `authorized_keys` 文件中,并在连接前通过云电脑的 GUI 终端核对主机密钥指纹。环境替换后,使用 `bash /workspace/.dots-tailscale/start.sh` 启动现有安装。脚本不会注册自动启动项。 + +## v0.3.1:诊断与私有 HTTP 访问 + +要使用这些辅助工具,请从 [v0.3.1](https://github.com/abruption/dots-tailscale/releases/tag/v0.3.1) 一并安装全部四个源文件。 + +```bash +/workspace/.dots-tailscale/dots-ts doctor +/workspace/.dots-tailscale/dots-ts cli status +/workspace/.dots-tailscale/dots-ts proxy show +``` + +`doctor` 读取本地诊断信息,不会更改状态。`cli` 会选择已安装的 Tailscale 二进制文件和套接字。可选的回环 SOCKS5/HTTP 代理默认关闭;启用前需要单独审查并重启守护进程。配置、访问策略和验证范围请参阅[使用指南](docs/usage.zh-CN.md#development-helpers)和[私有 HTTP API 配置示例](docs/private-http-api.zh-CN.md)。这些代理不会自动配置 ChatGPT 连接器、MCP 客户端或 Dots 浏览器。 + +## 项目内容 + +| 路径 | 用途 | +|---|---| +| `tailscale-setup.sh`, `shim.py` | 检查、安装和启动 Tailscale;配置本地 OpenSSH;Tailscale 身份验证需明确执行 | +| `dots-ts`, `dots_proxy.py` | CLI、诊断,以及可选的按进程配置出站代理 | +| `home-persistence-probe.py` | 记录 HOME 和 `/workspace` 的连续性标记 | +| `tests/` | 离线回归测试 | + +另请参阅[英文使用指南](docs/usage.md)、[韩文使用指南](docs/usage.ko.md)、[简体中文使用指南](docs/usage.zh-CN.md)、[日语使用指南](docs/usage.ja.md)和 [MIT 许可证](LICENSE)。 diff --git a/docs/private-http-api.ja.md b/docs/private-http-api.ja.md new file mode 100644 index 0000000..8fef325 --- /dev/null +++ b/docs/private-http-api.ja.md @@ -0,0 +1,156 @@ +# Dots からプライベート HTTP API にアクセスする + +[English](private-http-api.md) · [한국어](private-http-api.ko.md) · [简体中文](private-http-api.zh-CN.md) · 日本語 · [使用ガイド](usage.ja.md#development-helpers) + +この手順では、同じ tailnet 内にある別のデバイス上の HTTPS API を、任意のローカル Tailscale プロキシ経由で Dots ターミナルのプロセスから呼び出します。この手順に必要なヘルパーは v0.3.0 で導入されました。v0.3.0 以降のヘルパーファイル一式をインストールしてください。v0.2.0 以前には含まれていません。 + +## 接続経路と前提条件 + +```text +Dots curl process -> 127.0.0.1:8080 HTTP CONNECT proxy + -> Tailscale -> private API device:8443 -> HTTPS API +``` + +任意で使える SOCKS5 の接続先は `socks5h://127.0.0.1:1080` です。`h` により、接続先の名前解決がプロキシ経由になります。HTTP CONNECT も接続先ホスト名をプロキシに渡します。ポート `18089` のデーモン shim を代わりに使わないでください。これは Tailscale 自体の Dots プラットフォームプロキシ経路に使われるもので、アプリケーションのリクエスト用ではありません。 + +次の項目をそれぞれ準備します。 + +- 対象の tailnet で認証済みの Dots Tailscale ノード。v0.3.0 以降のヘルパーファイル一式をインストールしておきます。 +- tailnet 経由で到達できるプライベート API デバイス。意図したプライベート/Tailscale インターフェイスとポートで HTTPS リスナーが待ち受けている必要があります。API デバイスの `127.0.0.1` だけで待ち受けるリスナーには Dots から接続できません。手軽だからといってパブリックインターフェイスに公開しないでください。 +- 選択したプロキシ経由で名前解決でき、API 証明書とも一致する接続先名。信頼済みの公開 CA を使うか、プライベート CA ファイルをクライアントに明示的に渡してください。IP アドレスを使う場合も、証明書の ID が一致する必要があります。 +- 必要な送信元、接続先、TCP ポートだけを許可する tailnet 権限に加え、サーバー側の bearer 認証と API 認可。Tailscale に接続できても、API 認証の代わりにはなりません。 +- 認証が必要な読み取り専用 `/health` エンドポイント。`{"status":"ok"}` のような、小さく非公開情報を含まないレスポンスだけを意図的に返すものを用意してください。サンプルのエンドポイントはサーバー側の同等機能に置き換え、データをエクスポートしたり変更したりするエンドポイントは使わないでください。 + +## ネットワークアクセスを制限する + +次の有効な JSON は、Tailscale ポリシーで有効な JSON5 でもあります。送信元タグ、接続先タグ、許可する TCP ポートを1つ指定する例です。名前と管理者の所有権はポリシーに合わせて置き換えるプレースホルダーです。ポリシーファイル全体を置き換えず、関連する項目を既存のポリシーに統合してください。 + +```json +{ + "tagOwners": { + "tag:dots-client": ["autogroup:admin"], + "tag:private-api": ["autogroup:admin"] + }, + "grants": [ + { + "src": ["tag:dots-client"], + "dst": ["tag:private-api"], + "ip": ["tcp:8443"] + } + ] +} +``` + +権限を持つ tailnet 管理者が、送信元タグを対象の Dots デバイスに、接続先タグを API デバイスに割り当てる必要があります。これらのスクリプトはタグを割り当てず、ポリシーも編集しません。grants は追加方式です。この範囲を絞った項目を加えても、既存のより広い grants や ACL ルールは取り消されません。アクセスが制限されたと判断する前に、既存のルール、タグの所有権、API ホストのファイアウォールルールを確認してください。実際のノード ID を使って、tailnet ポリシーエディターの検証とポリシーテストを行ってください。公式の [grants ドキュメント](https://tailscale.com/kb/1324/grants)も参照してください。 + +## ローカルリスナーを意図的に有効にする + +まず[設定と適用の手順](usage.ja.md#development-helpers)を読んでください。プロキシリスナーは既定で無効です。設定だけでは希望する状態が保存されるだけで、稼働中のデーモンは再起動しません。 + +```bash +/workspace/.dots-tailscale/dots-ts proxy show +/workspace/.dots-tailscale/dots-ts proxy configure --socks-port 1080 --http-port 8080 +``` + +ポートは別々にしてください。この実装では、最初は SOCKS5 と HTTP に同じポートを指定できません。希望する設定は `/workspace/.dots-tailscale/outbound-proxy.json` に保存されます。ランタイムのリスナーは環境の入れ替え後に作り直す必要があります。設定の適用には、SSH を切断する可能性のある、別途実行する保護付きデーモン再起動が必要です。`dots-ts proxy apply` の手順に進む前に、独立した Dots GUI ターミナルとロールバック手順を確保してください。適用後は外部から厳格な鍵確認を使った SSH 接続を再確認してください。 + +これらはローカルプロセス用の loopback リスナーであり、他のマシン向けの認証済みゲートウェイではありません。リスナーにアクセスできるプロセスなら、接続を試みられます。プロキシを使うのが自分のスクリプトだけでも、API 認証と範囲を絞った tailnet 権限を維持してください。 + +## 認証付きリクエストを1回、時間とサイズを制限して実行する + +**curl 8.4.0 以降が必要です。** このバージョンから `--max-filesize` は、サイズが不明なレスポンス(chunked レスポンスなど)も制限します。この例はトークンを求めたり API リクエストを送ったりする前に curl のバージョンを調べ、バージョンを取得できない場合や古い場合は停止します。 + +HTTP リスナーを適用した後、Dots ターミナルで次を実行してください。URL は HTTPS サービスのホスト名とポートに置き換えます。この例では Python 標準ライブラリを使い、表示せずに bearer token を読み取り、そのヘッダーを標準入力から curl に渡します。トークンはコマンドライン引数、URL、シェル履歴、環境変数、ファイルには入りません。リクエストが終わるまでプロセスメモリに保持されます。HTTP トレースを有効にしたり、トークンを表示したりしないでください。 + +```bash +python3 - <<'PY' +import getpass +import json +import os +import re +import subprocess + +url = "https://api.example.internal:8443/health" +proxy = "http://127.0.0.1:8080" +ca_file = "" # Optional trusted private CA file path; blank uses system trust. +child_env = os.environ.copy() +for name in ("NO_PROXY", "no_proxy", "CURL_CA_BUNDLE", "SSL_CERT_FILE", "SSL_CERT_DIR"): + child_env.pop(name, None) +try: + version_result = subprocess.run( + ["curl", "--disable", "--version"], + text=True, capture_output=True, timeout=5, env=child_env, + ) +except (OSError, subprocess.TimeoutExpired, UnicodeError): + raise SystemExit("Could not verify curl version; curl 8.4.0 or newer is required") +version_match = re.match(r"curl ([0-9]{1,5})\.([0-9]{1,5})\.([0-9]{1,5})(?:\s|$)", version_result.stdout) +if (version_result.returncode != 0 or version_match is None + or tuple(map(int, version_match.groups())) < (8, 4, 0)): + raise SystemExit("curl 8.4.0 or newer is required for the response size limit") +token = getpass.getpass("API bearer token: ") +if not token or any(c in token for c in "\r\n"): + raise SystemExit("Invalid token input") +command = [ + "curl", "--disable", "--silent", "--show-error", "--proto", "=https", + "--proxy", proxy, "--noproxy", "", + "--connect-timeout", "5", "--max-time", "20", "--max-filesize", "4096", + "--header", "@-", "--write-out", "\n%{http_code}", url, +] +if ca_file: + command.extend(["--cacert", ca_file]) +try: + result = subprocess.run( + command, input="Authorization: Bearer " + token + "\n", + text=True, capture_output=True, timeout=25, env=child_env, + ) +except subprocess.TimeoutExpired: + raise SystemExit("Request timed out") +except (OSError, UnicodeError): + raise SystemExit("Could not execute curl or decode its response") +finally: + token = None +if result.returncode: + raise SystemExit("curl failed (exit %d); check network, DNS and TLS separately" % result.returncode) +body, separator, status = result.stdout.rpartition("\n") +if not separator: + raise SystemExit("Missing HTTP status") +print("HTTP", status) +if status != "200": + raise SystemExit("Unexpected HTTP status; response body omitted") +try: + healthy = json.loads(body) == {"status": "ok"} +except ValueError: + healthy = False +if not healthy: + raise SystemExit("Unexpected health response; body omitted") +print("Expected read-only health response received") +PY +``` + +リクエストはリダイレクトを追いません。資格情報を別の場所に転送せず、正規のエンドポイントを確認してください。HTTPS 証明書とホスト名の検証は有効のままです。レスポンスは 4 KiB までに制限され、ステータスと期待した形式かどうかだけが出力されます。サンプルサーバーのレスポンス仕様は意図的に非公開情報を含まないものです。非公開レコードを含むレスポンスにこの確認を使わないでください。 + +代わりに SOCKS5 を使うには、そのリスナーを有効にした後で `proxy` だけを `socks5h://127.0.0.1:1080` に変更します。`--noproxy ''` により、curl のバイパスリストが選択したプロキシを迂回するのを防ぎます。`NO_PROXY`/`no_proxy` と、継承した CA 関連の設定 (`CURL_CA_BUNDLE`、`SSL_CERT_FILE`、`SSL_CERT_DIR`) を消すのは、curl の子プロセスだけです。シェルや他のアプリケーションには影響しません。プライベート CA を使う場合は、確認済みの `ca_file` を `--cacert` で明示的に指定してください。システムのプロキシ、ブラウザー設定、MCP 設定、ChatGPT プラグイン/コネクター設定は変更しません。別のクライアントから使う場合は、そのクライアントがこのプロキシに明示的に対応し、設定できる必要があります。任意のインテグレーションから自動でこの API に接続できるわけではありません。 + +## 接続段階ごとの確認と拒否ケース + +`dots-ts doctor` はローカルの各層を報告します。任意の `doctor --target api.example.internal:8443` は時間を制限した TCP probe であり、認証付き HTTPS の確認ではありません。TCP 接続に成功しても、証明書が有効であることやトークンが認可されることは確認できません。 + +| 確認項目 | 期待する結果と解釈 | +| --- | --- | +| ローカルインストール、ソケット、デーモン、プロキシ設定 | `doctor` と `proxy show` で保存済み設定と実行状態を区別し、各層の結果を確認 | +| 許可された送信元から対象ホスト/ポートへの接続 | TCP 接続が成功し、認証付きの読み取り専用ヘルスチェックが HTTP 200 と合意済みの機密情報を含まないレスポンスを返す | +| API トークンの欠落または意図的に無効な値 | サーバーがリクエストを拒否。通常は HTTP 401、一部のサービスでは 403。機密情報を含む本文は返さない | +| 許可ポリシーの範囲外にあるデバイスまたは接続先ポート | API の応答前に接続が拒否またはタイムアウトする。ポリシーテストと、より広い許可がないことを確認 | +| 信頼されていない CA またはホスト名の不一致 | TLS 検証に失敗する。検証を無効にせず、証明書または信頼設定を修正 | +| プロキシ経由で接続先名を解決できない | DNS/プロキシエラー。名前解決を tailnet 権限や API 認証とは分けて調査 | +| 設定を無効にして適用済み | ローカルリスナーは利用不可。`disable` だけでは希望状態を記録するだけで、稼働中のリスナーが停止した証明にはならない | + +HTTP 401 は HTTP サーバーから応答があったことを意味し、ネットワークポリシーによる拒否ではありません。タイムアウトだけでは、ACL、ファイアウォール、ルーティング、DNS のどれが原因か特定できません。必要最小限の確認を使い、トークンや非公開のレスポンス本文を記録しないでください。 + + + +## 検証状況 + +2026-10-18 (KST) の実環境確認では、ネイティブ SOCKS5/HTTP プロキシの通信、両リスナーの無効化、厳格なホスト鍵確認を使った SSH 再接続を検証しました。2種類のプロキシを通じた HTTPS 確認は全10件に成功しました。内訳は、正しい認証 (`200`)、トークンの欠落と誤り (`401`)、禁止されたエンドポイント (`403`)、信頼されていない CA (TLS 失敗) です。別途、ポリシーを変更する前・拒否を確認した時・元に戻した後の確認で tailnet ポリシーによる拒否を検証し、元のポリシーに戻しました。一時的な API サーバーと認証情報は削除済みです。[プロキシの結果](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243157252)、[ポリシーの結果](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243247818)、[HTTPS の結果](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243275452)を参照してください。 + +これらの確認では、自然な VM の入れ替え後の復旧や自動起動は検証していません。比較した環境では、Dots の直接 Bash からは `/workspace` が読み取り専用に見え、ランタイムソケットも見えませんでした。一方、GUI ターミナルと SSH からは書き込み可能なファイルと稼働中のソケットが共有されていました。プロキシ設定を適用する場合は独立した GUI ターミナルを使ってください。直接の agent 実行からサービスを再起動できると思い込まないでください。ライフサイクルの調査は [#20](https://github.com/abruption/dots-tailscale/issues/20#issuecomment-6243404537) で継続中です。 diff --git a/docs/private-http-api.ko.md b/docs/private-http-api.ko.md index f290a7f..1b5abea 100644 --- a/docs/private-http-api.ko.md +++ b/docs/private-http-api.ko.md @@ -1,8 +1,8 @@ # Dots에서 비공개 HTTP API에 접속하기 -[English](private-http-api.md) · [사용 가이드](usage.ko.md#development-helpers) +[English](private-http-api.md) · 한국어 · [简体中文](private-http-api.zh-CN.md) · [日本語](private-http-api.ja.md) · [사용 가이드](usage.ko.md#development-helpers) -다른 tailnet 장비의 HTTPS API를 Dots 터미널 프로세스에서 선택적 로컬 Tailscale 프록시로 호출하는 예시입니다. v0.3.0 도구를 함께 설치해야 하며 v0.2.0에는 없습니다. +다른 tailnet 장비의 HTTPS API를 Dots 터미널에서 선택적 로컬 Tailscale 프록시로 호출하는 예시입니다. 필요한 도구는 v0.3.0부터 제공됩니다. v0.3.0 이상 버전의 helper 파일을 모두 설치하세요. v0.2.0 이하에는 해당 도구가 없습니다. ## 연결 경로와 준비 사항 @@ -15,11 +15,11 @@ SOCKS5를 쓰려면 `socks5h://127.0.0.1:1080`을 사용할 수 있습니다. `h 다음 항목을 별도로 준비하세요. -- 원하는 tailnet에 로그인된 Dots 노드와 v0.3.0 도구 설치. +- 대상 tailnet에 인증된 Dots Tailscale 노드와 v0.3.0 이상 helper 파일 전체. - tailnet에서 접근할 API 장비와 원하는 비공개/Tailscale 인터페이스·포트에서 수신하는 HTTPS 서버. API 장비의 `127.0.0.1`에서만 수신하면 Dots에서 접속할 수 없습니다. 편의상 공개 인터페이스에 노출하지 마세요. - 선택한 프록시를 통해 확인할 수 있고 API 인증서와 일치하는 목적지 이름. 신뢰하는 공개 CA를 사용하거나 클라이언트에 사설 CA 파일을 명시하세요. IP로 호출해도 인증서에 해당 IP가 포함돼야 합니다. - 필요한 출발 장비·도착 장비·TCP 포트만 허용하는 tailnet 정책과 서버의 bearer 인증·API 권한 검사. Tailscale 연결이 API 인증을 대신하지는 않습니다. -- 인증이 필요한 읽기 전용 `/health` 경로. 의도적으로 `{"status":"ok"}` 같은 작은 비공개 정보 없는 응답만 반환하게 만드세요. 예시 주소는 실제 서버의 상태 확인 경로로 바꾸며 데이터 내보내기·변경 경로를 사용하지 않습니다. +- 인증이 필요한 읽기 전용 `/health` 경로. 의도적으로 `{"status":"ok"}` 같은 민감한 정보가 포함되지 않은 짧은 응답만 반환하게 만드세요. 예시 주소는 실제 서버의 상태 확인 경로로 바꾸며 데이터 내보내기·변경 경로를 사용하지 않습니다. ## 네트워크 접근 범위 제한 @@ -127,7 +127,7 @@ print("Expected read-only health response received") PY ``` -리디렉션을 따라가지 않습니다. 자격증명을 다른 주소로 넘기기보다 정확한 최종 경로를 확인하세요. HTTPS 인증서·호스트명 검증은 유지합니다. `ca_file`이 비어 있으면 시스템 신뢰 저장소를 사용하고, 사설 CA가 필요하면 검토한 CA 파일 경로를 넣습니다. 응답은 4 KiB로 제한하고 HTTP 상태와 예상 형식 여부만 표시합니다. 예시 서버는 비공개 정보 없는 응답을 의도적으로 반환합니다. 개인 기록이 포함된 응답에 이 확인 명령을 사용하지 마세요. +리디렉션을 따라가지 않습니다. 자격증명을 다른 주소로 넘기기보다 정확한 최종 경로를 확인하세요. HTTPS 인증서·호스트명 검증은 유지합니다. `ca_file`이 비어 있으면 시스템 신뢰 저장소를 사용하고, 사설 CA가 필요하면 검토한 CA 파일 경로를 넣습니다. 응답은 4 KiB로 제한하고 HTTP 상태와 예상 형식 여부만 표시합니다. 예시 서버는 민감한 정보가 포함되지 않은 응답만 반환하도록 구성합니다. 개인 기록이 포함된 응답에 이 확인 명령을 사용하지 마세요. SOCKS5를 쓰려면 해당 listener를 활성화한 뒤 `proxy`만 `socks5h://127.0.0.1:1080`으로 바꾸세요. `--noproxy ''`는 curl의 우회 목록 때문에 프록시를 건너뛰는 것을 막습니다. `NO_PROXY`·`no_proxy`와 상속된 CA 설정인 `CURL_CA_BUNDLE`·`SSL_CERT_FILE`·`SSL_CERT_DIR`는 curl 자식 프로세스에서만 제거합니다. 셸이나 다른 앱에는 영향이 없습니다. 사설 CA는 검토한 `ca_file`을 `--cacert`로 명시적으로 전달합니다. 시스템 프록시·브라우저·MCP·ChatGPT 플러그인/커넥터 설정은 바꾸지 않습니다. 다른 클라이언트는 해당 프록시를 직접 지원하고 설정해야 하며 임의의 연동 서비스가 자동으로 이 API에 접근할 수 있는 것은 아닙니다. @@ -138,7 +138,7 @@ SOCKS5를 쓰려면 해당 listener를 활성화한 뒤 `proxy`만 `socks5h://12 | 확인 항목 | 기대 결과와 해석 | | --- | --- | | 로컬 설치·소켓·데몬·프록시 설정 | `doctor`와 `proxy show`의 계층별 결과에서 저장한 설정과 실제 실행 상태를 구분 | -| 허용된 출발 장비에서 의도한 호스트·포트 | TCP 성공, 인증한 읽기 전용 상태 확인이 HTTP 200과 약속한 비공개 정보 없는 응답 반환 | +| 허용된 출발 장비에서 의도한 호스트·포트 | TCP에 연결되고 인증된 읽기 전용 상태 확인이 HTTP 200과 사전에 정한 민감 정보 없는 응답을 반환 | | 누락되거나 의도적으로 잘못된 API token | 일반적으로 HTTP 401 거부. 서비스에 따라 403 사용. 민감한 본문은 반환하면 안 됨 | | 정책상 허용하지 않은 장비나 목적지 포트 | API 응답 전에 연결 거부·시간 초과. 정책 테스트와 기존의 넓은 허용 유무도 확인 | | 신뢰하지 않는 CA·호스트명 불일치 | TLS 검증 실패. 검증을 끄지 말고 인증서·신뢰 설정 수정 | @@ -147,6 +147,8 @@ SOCKS5를 쓰려면 해당 listener를 활성화한 뒤 `proxy`만 `socks5h://12 HTTP 401은 HTTP 서버가 응답했다는 뜻이며 네트워크 정책 거부가 아닙니다. 시간 초과만으로 ACL·방화벽·경로·DNS 중 원인을 단정할 수 없습니다. 필요한 최소 검사부터 진행하고 token이나 비공개 응답 본문을 기록하지 마세요. + + ## 검증 상태 2026-10-18(KST) 실환경에서 SOCKS5·HTTP 프록시의 실제 통신, 두 listener 비활성화, 엄격한 호스트 키 검증을 유지한 SSH 재접속을 확인했습니다. 두 프록시에서 정상 인증(`200`), 토큰 누락·오류(`401`), 권한 없는 경로(`403`), 신뢰하지 않는 CA(TLS 실패)의 HTTPS 검사 10개가 모두 통과했습니다. 별도의 허용→차단→복원 비교로 tailnet 정책 차단도 확인했으며 원래 정책으로 복구했습니다. 임시 API 서버와 인증정보는 정리했습니다. [프록시 결과](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243157252), [정책 결과](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243247818), [HTTPS 결과](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243275452)를 참고하세요. diff --git a/docs/private-http-api.md b/docs/private-http-api.md index 4be0688..f62e5b3 100644 --- a/docs/private-http-api.md +++ b/docs/private-http-api.md @@ -1,8 +1,8 @@ # Access a private HTTP API from Dots -[한국어](private-http-api.ko.md) · [Usage](usage.md#development-helpers) +English · [한국어](private-http-api.ko.md) · [简体中文](private-http-api.zh-CN.md) · [日本語](private-http-api.ja.md) · [Usage guide](usage.md#development-helpers) -This recipe covers an HTTPS API on another tailnet device, called from a Dots terminal process through the optional local Tailscale proxy. It requires the v0.3.0 helpers installed together; release v0.2.0 does not contain them. +This recipe covers an HTTPS API on another tailnet device, called from a Dots terminal process through the optional local Tailscale proxy. This recipe requires all helper files from v0.3.0 or newer; v0.2.0 and earlier do not include them. ## Connection path and prerequisites @@ -15,11 +15,11 @@ The optional SOCKS5 alternative is `socks5h://127.0.0.1:1080`; the `h` delegates Prepare these independently: -- A Dots Tailscale node authenticated to the intended tailnet, with the v0.3.0 helpers installed. +- A Dots Tailscale node authenticated to the intended tailnet, with the complete helper set from v0.3.0 or newer installed. - A private API device reachable in that tailnet, with an HTTPS listener bound to its intended private/Tailscale interface and port. A listener bound only to the API device's `127.0.0.1` is not reachable from Dots. Avoid exposing it on a public interface as a shortcut. - A destination name that resolves through the chosen proxy and matches the API certificate. Use a trusted public CA or explicitly provide your private CA file to the client; an IP address also needs a matching certificate identity. - Tailnet permission for only the required source, destination, and TCP port, plus server-side bearer authentication and API authorization. Tailscale connectivity does not replace API authentication. -- A read-only authenticated `/health` endpoint that deliberately returns only a small nonprivate response, for example `{"status":"ok"}`. Replace the sample endpoint with your server's equivalent, not a data-export or mutation endpoint. +- An authenticated, read-only `/health` endpoint that returns only a small response without sensitive information, for example `{"status":"ok"}`. Replace the sample endpoint with your server's equivalent, not a data-export or mutation endpoint. ## Limit network access @@ -127,7 +127,7 @@ print("Expected read-only health response received") PY ``` -The request does not follow redirects; verify the canonical endpoint rather than forwarding a credential to another location. HTTPS certificate and hostname verification stay enabled. The response is capped at 4 KiB and only its status and expected-shape result are printed; the sample server's response contract is intentionally nonprivate. Do not point this check at a response containing private records. +The request does not follow redirects; verify the canonical endpoint rather than forwarding a credential to another location. HTTPS certificate and hostname verification stay enabled. The response is capped at 4 KiB and only its status and expected-shape result are printed; the sample server returns no sensitive information. Do not point this check at a response containing private records. To use SOCKS5 instead, change only `proxy` to `socks5h://127.0.0.1:1080` after enabling that listener. `--noproxy ''` prevents curl's bypass list from skipping the chosen proxy. Clearing `NO_PROXY`/`no_proxy` and inherited CA overrides (`CURL_CA_BUNDLE`, `SSL_CERT_FILE`, `SSL_CERT_DIR`) affects only the curl child process, not your shell or other applications. For a private CA, explicitly supply a reviewed `ca_file` through `--cacert`. No system proxy, browser setting, MCP configuration, or ChatGPT plugin/connector setting is changed. A separate client must explicitly support and configure this proxy; arbitrary integrations are not automatically able to reach this API. @@ -138,7 +138,7 @@ To use SOCKS5 instead, change only `proxy` to `socks5h://127.0.0.1:1080` after e | Check | Expected result and interpretation | | --- | --- | | Local installation, socket, daemon, and proxy configuration | `doctor` and `proxy show` distinguish saved configuration from running state; inspect their layer results | -| Allowed source to intended host and port | TCP succeeds; authenticated read-only health check returns HTTP 200 and the agreed nonprivate response | +| Allowed source to intended host and port | TCP succeeds; the authenticated, read-only health check returns HTTP 200 and the agreed response without sensitive information | | Missing or deliberately invalid API token | Server rejects the request, normally HTTP 401; some services use 403. No sensitive body should be returned | | Device or destination port outside the allowed policy | Connection is denied or times out before an API response; verify policy tests and absence of broader permits | | Untrusted CA or hostname mismatch | TLS verification fails; fix the certificate or trust configuration, never disable verification | @@ -147,6 +147,8 @@ To use SOCKS5 instead, change only `proxy` to `socks5h://127.0.0.1:1080` after e An HTTP 401 means an HTTP server answered; it is not a network-policy denial. A timeout alone cannot identify ACL, firewall, routing, or DNS as the cause. Use the smallest useful check and avoid recording tokens or private response bodies. + + ## Validation status Live checks on 2026-10-18 (KST) confirmed native SOCKS5 and HTTP proxy traffic, disabling both listeners, and strict-host-key SSH reconnection. Across both proxies, all ten HTTPS checks passed: valid authentication (`200`), missing and incorrect tokens (`401`), a forbidden endpoint (`403`), and an untrusted CA (TLS failure). A separate before/block/restored check confirmed tailnet policy denial; the original policy was restored. Temporary API servers and credentials were removed. See the [proxy results](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243157252), [policy results](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243247818), and [HTTPS results](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243275452). diff --git a/docs/private-http-api.zh-CN.md b/docs/private-http-api.zh-CN.md new file mode 100644 index 0000000..bf172af --- /dev/null +++ b/docs/private-http-api.zh-CN.md @@ -0,0 +1,156 @@ +# 从 Dots 访问私有 HTTP API + +[English](private-http-api.md) · [한국어](private-http-api.ko.md) · 简体中文 · [日本語](private-http-api.ja.md) · [使用指南](usage.zh-CN.md#development-helpers) + +本指南介绍如何在 Dots 终端进程中,通过可选的本地 Tailscale 代理调用 tailnet 上另一台设备的 HTTPS API。所需辅助工具从 v0.3.0 起提供;请安装 v0.3.0 或更新版本中的完整辅助工具集。v0.2.0 及更早版本不包含这些工具。 + +## 连接路径与前置条件 + +```text +Dots curl process -> 127.0.0.1:8080 HTTP CONNECT proxy + -> Tailscale -> private API device:8443 -> HTTPS API +``` + +另一种可选方式是使用 SOCKS5:`socks5h://127.0.0.1:1080`;其中的 `h` 表示通过代理解析目标名称。HTTP CONNECT 同样会把目标主机名传给代理。不要改用 `18089` 端口上的守护进程 shim:它为 Tailscale 本身提供 Dots 平台代理通道,不处理应用请求。 + +请分别准备以下各项: + +- 已通过身份验证并加入目标 tailnet 的 Dots Tailscale 节点,且已安装 v0.3.0 或更新版本中的完整辅助工具集。 +- tailnet 中可访问的私有 API 设备,并且 HTTPS 监听器绑定到预期的私有/Tailscale 接口和端口。若监听器只绑定到 API 设备的 `127.0.0.1`,Dots 将无法访问。不要为了省事而将它暴露到公网接口。 +- 一个能通过所选代理解析、且与 API 证书匹配的目标名称。请使用受信任的公共 CA,或明确向客户端提供私有 CA 文件;如果使用 IP 地址,证书身份也必须与该 IP 匹配。 +- 仅允许所需源设备、目标设备和 TCP 端口的 tailnet 权限,并配置服务器端 bearer 身份验证和 API 授权。Tailscale 连通性不能代替 API 身份验证。 +- 一个经过身份验证的只读 `/health` 端点,并确保它只返回少量非私密内容,例如 `{"status":"ok"}`。请将示例端点替换成服务器上功能相同的端点,不要使用导出数据或修改数据的端点。 + +## 限制网络访问 + +以下内容是有效的 JSON,也适用于 Tailscale 策略使用的 JSON5 格式。它演示了源标签、目标标签和一个允许的 TCP 端口;标签名称和管理员归属只是策略示例中的占位值。请将相关条目合并到现有策略中,不要替换整个策略文件。 + +```json +{ + "tagOwners": { + "tag:dots-client": ["autogroup:admin"], + "tag:private-api": ["autogroup:admin"] + }, + "grants": [ + { + "src": ["tag:dots-client"], + "dst": ["tag:private-api"], + "ip": ["tcp:8443"] + } + ] +} +``` + +有权限的 tailnet 管理员必须将源标签分配给目标 Dots 设备,并将目标标签分配给 API 设备。这些脚本不会分配标签或修改策略。授权规则会累加:这条范围较窄的规则不会撤销已有的更宽泛授权或 ACL 规则。在将最终访问视为受限之前,请检查这些规则、标签归属和 API 主机的防火墙规则。请使用 tailnet 策略编辑器,针对实际节点身份运行验证和策略测试。参阅官方 [grants 文档](https://tailscale.com/kb/1324/grants)。 + +## 谨慎启用本地监听器 + +请先阅读[配置与应用步骤](usage.zh-CN.md#development-helpers)。代理监听器默认关闭。仅修改配置只会保存期望状态,不会重启正在运行的守护进程: + +```bash +/workspace/.dots-tailscale/dots-ts proxy show +/workspace/.dots-tailscale/dots-ts proxy configure --socks-port 1080 --http-port 8080 +``` + +请为两种代理使用不同端口。目前此实现会拒绝 SOCKS5 和 HTTP 共用同一端口。期望配置保存在 `/workspace/.dots-tailscale/outbound-proxy.json`;环境替换后必须重新创建运行时监听器。应用配置需要单独执行受保护的守护进程重启,这可能会中断 SSH。在按 `dots-ts proxy apply` 的说明操作前,请确保可以使用独立的 Dots GUI 终端,并准备好回滚方案。应用后,请重新检查外部 SSH 的严格主机密钥验证。 + +这些是供本地进程使用的回环监听器,不是面向其他机器的身份验证网关。任何能够访问这些监听器的进程都可能尝试建立连接。即使只有自己的脚本使用代理,也要保留 API 身份验证和范围严格的 tailnet 权限。 + +## 发起一次受限的认证请求 + +**需要 curl 8.4.0 或更高版本。**从此版本起,`--max-filesize` 也会限制大小未知的响应,包括分块响应。示例会先检查版本,再提示输入令牌或发起 API 请求;如果无法获取版本或版本过低,就会停止。 + +在应用 HTTP 监听器后,于 Dots 终端中运行以下命令。请将 URL 替换为 HTTPS 服务的主机名和端口。示例使用 Python 标准库静默读取 bearer 令牌,并通过标准输入将请求头传给 curl。令牌不会出现在命令行参数、URL、shell 历史记录、环境变量或文件中;请求完成前,它会保留在进程内存里。不要启用 HTTP 跟踪,也不要打印令牌。 + +```bash +python3 - <<'PY' +import getpass +import json +import os +import re +import subprocess + +url = "https://api.example.internal:8443/health" +proxy = "http://127.0.0.1:8080" +ca_file = "" # Optional trusted private CA file path; blank uses system trust. +child_env = os.environ.copy() +for name in ("NO_PROXY", "no_proxy", "CURL_CA_BUNDLE", "SSL_CERT_FILE", "SSL_CERT_DIR"): + child_env.pop(name, None) +try: + version_result = subprocess.run( + ["curl", "--disable", "--version"], + text=True, capture_output=True, timeout=5, env=child_env, + ) +except (OSError, subprocess.TimeoutExpired, UnicodeError): + raise SystemExit("Could not verify curl version; curl 8.4.0 or newer is required") +version_match = re.match(r"curl ([0-9]{1,5})\.([0-9]{1,5})\.([0-9]{1,5})(?:\s|$)", version_result.stdout) +if (version_result.returncode != 0 or version_match is None + or tuple(map(int, version_match.groups())) < (8, 4, 0)): + raise SystemExit("curl 8.4.0 or newer is required for the response size limit") +token = getpass.getpass("API bearer token: ") +if not token or any(c in token for c in "\r\n"): + raise SystemExit("Invalid token input") +command = [ + "curl", "--disable", "--silent", "--show-error", "--proto", "=https", + "--proxy", proxy, "--noproxy", "", + "--connect-timeout", "5", "--max-time", "20", "--max-filesize", "4096", + "--header", "@-", "--write-out", "\n%{http_code}", url, +] +if ca_file: + command.extend(["--cacert", ca_file]) +try: + result = subprocess.run( + command, input="Authorization: Bearer " + token + "\n", + text=True, capture_output=True, timeout=25, env=child_env, + ) +except subprocess.TimeoutExpired: + raise SystemExit("Request timed out") +except (OSError, UnicodeError): + raise SystemExit("Could not execute curl or decode its response") +finally: + token = None +if result.returncode: + raise SystemExit("curl failed (exit %d); check network, DNS and TLS separately" % result.returncode) +body, separator, status = result.stdout.rpartition("\n") +if not separator: + raise SystemExit("Missing HTTP status") +print("HTTP", status) +if status != "200": + raise SystemExit("Unexpected HTTP status; response body omitted") +try: + healthy = json.loads(body) == {"status": "ok"} +except ValueError: + healthy = False +if not healthy: + raise SystemExit("Unexpected health response; body omitted") +print("Expected read-only health response received") +PY +``` + +此请求不会跟随重定向;请确认使用的是规范端点,不要把凭据转发到其他位置。HTTPS 证书和主机名验证始终保持启用。响应上限为 4 KiB;输出仅包含状态码和是否符合预期结构的结果。示例服务器的响应格式特意设计为不含私密信息。不要将此检查用于包含私密记录的响应。 + +若要改用 SOCKS5,请先启用对应监听器,然后只将 `proxy` 改为 `socks5h://127.0.0.1:1080`。`--noproxy ''` 可防止 curl 的绕过列表跳过所选代理。清除 `NO_PROXY`/`no_proxy` 和继承的 CA 覆盖设置(`CURL_CA_BUNDLE`、`SSL_CERT_FILE`、`SSL_CERT_DIR`)只影响 curl 子进程,不会影响 shell 或其他应用。使用私有 CA 时,请通过 `--cacert` 明确指定经过审核的 `ca_file`。此操作不会更改系统代理、浏览器设置、MCP 配置或 ChatGPT 插件/连接器设置。其他客户端必须明确支持并配置此代理;任意集成都不会自动获得访问此 API 的能力。 + +## 逐层验证并检查拒绝场景 + +`dots-ts doctor` 会报告本地各层状态。可选的 `doctor --target api.example.internal:8443` 只执行有界 TCP 探测,并不验证经过身份验证的 HTTPS。TCP 连接成功不能证明证书有效,也不能证明令牌已获授权。 + +| 检查项 | 预期结果与说明 | +| --- | --- | +| 本地安装、套接字、守护进程和代理配置 | `doctor` 与 `proxy show` 会区分已保存的配置和运行状态;请检查各层的结果 | +| 允许的源设备访问指定主机和端口 | TCP 连接成功;经过身份验证的只读健康检查返回 HTTP 200 和约定的非私密响应 | +| 缺少或故意使用无效 API 令牌 | 服务器拒绝请求,通常返回 HTTP 401;部分服务会返回 403。不应返回包含敏感信息的响应正文 | +| 设备或目标端口不在允许策略范围内 | 连接会被拒绝或在 API 响应前超时;请检查策略测试,并确认不存在范围更广的许可规则 | +| CA 不受信任或主机名不匹配 | TLS 验证失败;请修正证书或信任配置,绝不要关闭验证 | +| 无法通过代理解析目标名称 | 出现 DNS/代理错误;请将名称解析问题与 tailnet 权限、API 身份验证分开排查 | +| 已禁用并应用配置 | 本地监听器不可用;单独执行 `disable` 只会记录期望状态,不能证明活动监听器已停止 | + +HTTP 401 表示 HTTP 服务器已作出响应,不代表网络策略拒绝了连接。仅凭超时无法判断原因是 ACL、防火墙、路由还是 DNS。请使用能够解决问题的最小检查,并避免记录令牌或私密响应正文。 + + + +## 验证状态 + +2026-10-18(KST)的实时检查确认了原生 SOCKS5 和 HTTP 代理流量、关闭两个监听器,以及通过 SSH 严格主机密钥验证后重新连接。两个代理共完成十项 HTTPS 检查,全部通过:有效身份验证(`200`)、缺少令牌和错误令牌(`401`)、禁止访问的端点(`403`)以及不受信任的 CA(TLS 失败)。另一次“修改前/阻止后/恢复后”检查确认 tailnet 策略拒绝生效;随后恢复了原策略。临时 API 服务器和凭据均已删除。查看[代理结果](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243157252)、[策略结果](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243247818)和 [HTTPS 结果](https://github.com/abruption/dots-tailscale/pull/24#issuecomment-6243275452)。 + +这些检查不能证明系统能在 VM 自然替换后恢复,也不能证明会自动启动。在所比较的环境中,Dots 直接 Bash 看到的 `/workspace` 为只读,也看不到运行时套接字;GUI 终端和 SSH 则共享可写文件及正在运行的套接字。应用代理更改时,请使用独立的 GUI 终端;不要假设直接通过 agent 执行命令就能重启服务。生命周期研究仍在进行中,见 [#20](https://github.com/abruption/dots-tailscale/issues/20#issuecomment-6243404537)。 diff --git a/docs/usage.ja.md b/docs/usage.ja.md new file mode 100644 index 0000000..eb8e766 --- /dev/null +++ b/docs/usage.ja.md @@ -0,0 +1,356 @@ +# 使用ガイド + +[English](usage.md) · [한국어](usage.ko.md) · [简体中文](usage.zh-CN.md) · 日本語 · [プロジェクト概要](../README.ja.md) + +この手順は、環境の入れ替え後も `/workspace` が保持される環境で、ユーザー権限でインストールする場合を対象とします。保存済みの認証情報が有効な間は、保存済みのインストールファイル、認証状態、SSH ホスト鍵を再利用し、新たにログインせずに復旧できます。インストール時に自動起動フックは登録されないため、新しい環境ごとに start コマンドを実行してください。 + +## 公開リリース + +公開リポジトリの初回 clone と raw ファイルのダウンロードは、準備済みのソースと照合して検証しました。[Release v0.3.1](https://github.com/abruption/dots-tailscale/releases/tag/v0.3.1) には ZIP と tar.gz のアーカイブに加えて、`tailscale-setup.sh`、`shim.py`、`dots-ts`、`dots_proxy.py` が個別のアセットとして含まれています。インストール例ではソースを `v0.3.1` に固定しています。同梱される Tailscale のバージョンは引き続き `1.102.4` です。 + + + +## ダウンロード接続とプロキシ設定 + +2026-10-15 に検証した Dots SSH 環境では、GitHub リポジトリ、raw ファイル、リリースアセットの各 URL への HTTPS 直接リクエストは失敗しましたが、`http://browser-proxy:8889` を明示的に使うと成功しました。プロキシ経由で clone した際、`v0.2.0` は commit `643484` に解決され、ダウンロードした raw ファイルはそのリビジョンのチェックサムと一致しました。この結果はその環境に限ったものであり、GitHub 全体がブロックされていることを示すものではありません。ブラウザーからのダウンロードは別途確認してください。ここに示すターミナルでの確認では、ブラウザーのエラーは診断できません。 + +clone やダウンロードに失敗した場合は、影響を受けているターミナルで次の範囲を限定した確認を実行し、プロキシ経由の接続を調べます。レスポンス本文は破棄し、リダイレクト後の署名付きダウンロード URL を表示せずに、最終 HTTP ステータスだけを出力します。 + +```bash +for url in \ + 'https://github.com/abruption/dots-tailscale' \ + 'https://raw.githubusercontent.com/abruption/dots-tailscale/v0.2.0/tailscale-setup.sh' \ + 'https://github.com/abruption/dots-tailscale/releases/download/v0.2.0/tailscale-setup.sh' +do + printf '%s\n' "$url" + curl --fail --silent --show-error --location \ + --proxy http://browser-proxy:8889 --connect-timeout 5 --max-time 20 \ + --output /dev/null --write-out 'HTTP %{http_code}\n' "$url" +done +``` + +直接接続との比較では、同じコマンドの `--proxy http://browser-proxy:8889` を `--noproxy '*'` に置き換えます。curl の終了コード `7` は接続失敗、`28` は制限時間への到達を意味します。どちらの結果も、それだけでホスト名がブロックされている証拠にはなりません。証明書の検証は有効のままにし、異なる場合は実際の環境で提供されているプロキシアドレスを使ってください。 + +設定ごとに適用範囲が異なります。 + +| 設定 | 適用範囲 | +| --- | --- | +| `HTTPS_PROXY` / `HTTP_PROXY` を `git clone` または `git fetch` の前に指定 | その Git コマンドだけに適用。Git のグローバル設定は変更しない | +| curl の `--proxy` | そのダウンロードまたは診断リクエストだけに適用 | +| インストーラーの `UPSTREAM_PROXY` | インストール中の Tailscale パッケージ/チェックサムのダウンロード、および起動中の shim の上流プロキシに適用 | +| `127.0.0.1:18089` のローカル shim | プロセス環境を通じて、起動した Tailscale デーモンが使う TLS/WebSocket ブリッジ | + +ローカル shim はグローバルプロキシを設定せず、無関係なアプリケーションを Tailscale 経由にもしません。環境で異なる上流アドレスを使う場合は、インストール時とその後の起動時に `UPSTREAM_PROXY` を明示してください。 + +## インストールと起動 + +セットアップスクリプトと `shim.py` を一緒に使えるよう、リポジトリを `https://github.com/abruption/dots-tailscale` から `/workspace` に clone します。clone には Git と、クラウドコンピューターのプロキシ経由で GitHub HTTPS にアクセスできることが必要です。セットアップには Linux x86_64、Python 3、`timeout`、`flock`、OpenSSL、`ssh-keygen`、`/usr/sbin/sshd` が必要です。インストール時にはさらに curl、tar、sha256sum が必要です。現在のユーザーがポート 22 をバインドできる必要があります。root や systemd は必要ありません。 + +v0.3.1 では、ソースファイル 4 つをすべて同じ場所に置いてください: `tailscale-setup.sh`、`shim.py`、`dots-ts`、`dots_proxy.py`。旧版 v0.2.0 のインストールでは元の 2 ファイルだけを使い、これらのヘルパーは含まれません。 + +```bash +cd /workspace +HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \ + git clone --branch v0.3.1 --depth 1 'https://github.com/abruption/dots-tailscale' dots-tailscale +cd dots-tailscale +bash tailscale-setup.sh check +bash tailscale-setup.sh install +bash /workspace/.dots-tailscale/start.sh login +``` + +このプロキシ設定は clone コマンドにのみ適用され、Git のグローバル設定は変更しません。アドレスが異なる場合は、環境で指定されているプロキシを使ってください。HTTPS 証明書の検証は有効のままにしてください。 + +`check` は選択したインストールファイルの有無を一覧表示し、デーモンソケットと CLI を利用できる場合は Tailscale の状態も表示します。コンポーネントを起動したり、依存関係を確認したりはしません。ファイルがないだけでは終了コードは非ゼロになりません。未インストールの環境では `missing:` の行を表示して終了コード `0` で終わることがあります。起動の成功と実際の SSH 接続で準備状況を判断してください。 + +`install` はプロセスを起動せずにファイルを配置します。既存のバイナリは保持されます。設定ファイルまたはスクリプトが競合している場合は、手動確認のためインストールを停止します。ダウンロードする Tailscale アーカイブはスクリプトに記載されたバージョンに固定され、公式の SHA-256 ファイルと照合されます。 + +`login` は不足しているコンポーネントを起動し、180 秒の制限時間内で対話形式の Tailscale 認証を行います。表示された URL を開き、対象のアカウントと tailnet を承認してください。スクリプトに auth key は埋め込まれていません。ファイルを使う任意の認証方法は後述します。 + +認証後、不足しているコンポーネントを起動するには次のコマンドを使います。 + +```bash +bash /workspace/.dots-tailscale/start.sh +``` + +確認や明示的な認証を行うには、インストール済みラッパーに `check` または `login` を渡します。インストール時にインストールディレクトリへ `start.sh` が生成され、`tailscale-setup.sh` は `setup.sh` としてコピーされます。生成されたラッパーは自身の場所からディレクトリを特定し、同じ場所にある `setup.sh` を実行します。既存のインストールを更新する場合は、下記の[更新手順](#restart-update)を使ってください。 + +| 設定 | 既定値 | +| --- | --- | +| `DOTS_TS_DIR` | `/workspace/.dots-tailscale` | +| `UPSTREAM_PROXY` | `http://browser-proxy:8889` | +| `NODE_HOSTNAME` | `dots-sandbox` | + +別の `DOTS_TS_DIR` を指定する場合は、空白を含まない絶対パスにしてください。`UPSTREAM_PROXY` には認証情報を含まない HTTP プロキシ URL を指定します。インストール時とその後の起動時で同じ設定を使います。shim は現在ポート 18089、SSH はポート 22 で待ち受けるため、この構成で同時に実行できるインストールは 1 つだけです。 + +### SSH 鍵と接続準備 + +SSH は `127.0.0.1:22` で待ち受け、公開鍵認証を使います。Tailscale SSH は無効で、シェル接続はローカルの OpenSSH サーバーが処理します。HOME に `.ssh/authorized_keys` がすでにある場合、インストール時に永続化されるインストール先へコピーされます。ない場合は、クライアントの公開鍵を次のファイルに追加してください。 + +```text +/workspace/.dots-tailscale/sshd/authorized_keys +``` + +クライアントの公開鍵を1行まるごと追記し、ファイルのモードを `600` にします。クライアントでは `.pub` ファイルから公開鍵を取得してください。秘密鍵を転送してはいけません。クライアントで受け入れる前に、クラウドコンピューター上でサーバーのホスト鍵フィンガープリントを確認してください。 + +```bash +ssh-keygen -lf /workspace/.dots-tailscale/sshd/ssh_host_ed25519_key.pub +``` + +Dots ターミナルで、インストール済みラッパーを使ってノードアドレスを取得します。 + +```bash +bash /workspace/.dots-tailscale/start.sh check +``` + +インストール済み CLI と、ユーザーごとの `/tmp/dots-tailscale-$(id -u)/tailscaled.sock` を使用します。グローバルな `tailscale` コマンドや既定のソケットは必要ありません。アドレスの表示にはデーモンが起動済みである必要があります。カスタムインストールの場合は、そのインストール先にある `start.sh` を使ってください。 + +初回接続では、tailnet のクライアントで次のコマンドを実行します。ホスト鍵の確認プロンプトで、表示されたフィンガープリントを Dots ターミナルで別途取得した値と比較してください。一致しない場合は `no` と答えて中止してください。鍵を登録したり、既存の登録を削除したりしないでください。`ssh-keyscan` の出力だけを信頼してはいけません。値が一致した場合に限って受け入れてください。これによりクライアントの `known_hosts` ファイルに鍵が記録されます。 + +```bash +ssh -o StrictHostKeyChecking=ask -i /path/to/client_private_key 'agent@' 'id; pwd' +``` + +検証済みの鍵が記録されたら、以降の接続では厳格な確認を使います。 + +```bash +ssh -o StrictHostKeyChecking=yes -i /path/to/client_private_key 'agent@' 'id; pwd' +``` + +クラウドコンピューター上のユーザー名が異なる場合は、例のユーザー名を置き換えてください。デーモンの起動中に `start.sh check` が表示したアドレスを使います。`StrictHostKeyChecking=yes` は信頼済みのホスト鍵を要求し、新しい鍵を対話形式で受け入れることはできません。鍵が変わった場合は、保存済みのエントリーを変更する前に新しいフィンガープリントを別途確認してください。ホスト鍵の検証を回避してはいけません。 + +`running: Tailscale and local SSH ready` は、backend が Running でローカル SSH バナーが応答したことを意味します。クライアント鍵での認証成功を示すものではありません。実際に SSH コマンドを実行して別途確認してください。 + +| 起動結果 | 終了コード | +| --- | --- | +| Running かつローカル SSH 準備完了 | `0` | +| 認証が必要 (`needs_auth`) | `10` | +| backend の準備期限切れ、またはその他の利用不可状態 | `11` | +| 期限までに有効な backend 状態を取得できなかった | `12` | +| 管理者によるデバイス承認が必要 (`needs_machine_auth`) | `13` | +| backend が Running のときに明示的な認証コマンドが失敗 | 元のコマンドの失敗コード | +| セットアップまたはコンポーネントの失敗 | 非ゼロ。通常は `1` | + +backend の準備確認では、各フェーズにつき単調時計で計る30秒の期限を設け、再試行とスリープもその中に含めます。各ステータス照会は最大3秒で、残り時間を超えません。`NoState` と `Starting` は再試行します。有効な一時状態が一度でも確認されていれば、その後の照会に失敗しても期限切れは終了コード `11` です。フェーズ中に有効な状態を一度も取得できなかった場合は `12` です。CLI との通信に成功しただけでは準備完了ではありません。この期限が適用されるのは backend のフェーズであり、すべてのコンポーネント初期化や明示的な認証を合わせた時間ではありません。`login` と `auth` は最初に最大30秒間、有効なローカル API 応答を待ちます。この事前確認では Running を要求せず `NoState`/`Starting` も許容するため、未認証の新しいデーモンでも明示的な `up` を実行できます。`login` には別途180秒、`auth` の認証には別途60秒の制限があり、その後に最終的な backend 準備確認を行います。デバイス承認がすでに保留中の場合は `up` を省略します。`up` が失敗またはタイムアウトしても backend を確認します。NeedsMachineAuth は `13`、NeedsLogin は `10` に対応します。backend が Running でもコマンドの失敗を隠しません。新しい起動時のステータス確認は最大3秒です。 + +通常の `start` は login を実行しません。認証済みだった state が消失した場合は、ID を黙って置き換えず、手動確認のため失敗します。競合する既存ファイル、不完全な CA インストール、すでに設定済みのホスト鍵の欠落についても確認が必要です。スクリプトを更新するためにインストールディレクトリを削除しないでください。ノードの state と秘密鍵が含まれています。 + +カスタム TLS/WebSocket shim は、設定されたローカル HTTP CONNECT ブリッジを実装します。ローカルテストでは、分離したソケットを使って初期ペイロードの保持と通信境界を確認しています。修正版 shim は2026-10-14に展開され、プロキシ経由で実環境の DERP ping と、厳格な公開鍵 SSH 接続の確認に成功しました。以前の shim では、メンテナーが環境入れ替え後もインストールファイルが保持され、手動再起動に成功したことを確認しています。修正版で、その後の入れ替えを経た復旧はまだ検証していません。プラットフォームのプロキシに変更があると動作しなくなる可能性があります。 + +このリポジトリには VM cron、systemd サービス、自動起動登録はありません。Dots オートメーション経由の起動も未検証です。VM 内のプロセスループは、その環境が破棄されると存続できません。新しい環境でインストールを明示的に起動するか、別途検証済みのプラットフォーム実行手段を使ってください。 + + + +## v0.3.1 ヘルパーと任意のアプリケーションプロキシ + +これらのコマンドを使うには、v0.3.1 のファイル 4 つをまとめてインストールする必要があります。release v0.2.0 では利用できません。`dots-ts` は Python 3 の標準ライブラリを使い、自身の場所からインストール先を特定します。グローバルな `tailscale` コマンド、ソケットのシンボリックリンク、シェルプロファイルの変更は不要です。 + +```bash +/workspace/.dots-tailscale/dots-ts cli status +/workspace/.dots-tailscale/dots-ts doctor +/workspace/.dots-tailscale/dots-ts doctor --json +/workspace/.dots-tailscale/dots-ts doctor --target api.example.internal:8443 +``` + +`cli` はインストールのランタイムソケットを使って Tailscale CLI を実行し、引数をそのまま渡します。生の出力と対話動作を保ち、ヘルパー独自のタイムアウトはありません。アドレスなどの機密情報を含む CLI 出力が表示されることがあります。長時間実行するコマンドには独自の制限を設けてください。単独ではログインもデーモン起動もしません。 + +`doctor` は読み取り専用です。プロセスの起動、認証、ファイル修復、プロキシ設定の変更はしません。すべてのアプリケーションを準備完了と判定するのではなく、層ごとにローカル確認結果を機密情報を伏せて報告します。`overall` と各結果を確認してください。`degraded` でも終了コードが `0` の場合があります。`--target HOST:PORT` を指定すると、アプリケーションの HTTP/SOCKS リスナーではなく、ローカルの Tailscale デーモン経由で範囲を限定した TCP 到達性確認も行います。資格情報は送信せず、TLS、API 認証、認可も証明しません。ターゲット名の指定は明示的なネットワークリクエストなので、接続する意図のあるサービスを選んでください。アプリケーションの確認方法と検証一覧は、[プライベート HTTP API の利用例](private-http-api.ja.md)を参照してください。 + +### 実行コンテキストの診断 + +`doctor` が示すのは、現在の実行コンテキストと、そこで見えるプロセスだけです。追加された JSON の `context` オブジェクトには、インストール/ランタイムの可視性、メタデータに基づく読み書き権限、ファイルシステムの `read_only`/`read_write`/`unknown` 状態が含まれます。これらはメタデータとアクセス権の確認であり、実際の書き込みは行いません。`advice` には固定のガイダンス ID が入り、`skipped_checks` は未実施の確認項目を示します。人向けの出力にはガイダンスの説明が表示されます。 + +ソケットが見つからない場合でも、デーモンは別の場所で動作しており、単に現在のコンテキストから見えないことがあります。その場合も現在のコンテキストについては `unhealthy`、終了コード `1` となります。アクセス拒否や応答しないソケットも失敗です。確認結果が不明な場合や、サービスが正常でもインストール先が読み取り専用の場合は `degraded`、終了コード `0` となります。`healthy` が true になるのは `overall=healthy` の場合だけです。終了コード `0` だけで準備完了と判断せず、各フィールドを確認してください。ソケットのメタデータと接続結果では、欠落、アクセス拒否、利用不可を区別します。 + +インストール/ランタイムが読み取り専用またはアクセス不可の場合、あるいはソケットが欠落または応答しない場合は、クラウドコンピューターの独立した GUI ターミナルを開き、そこで `doctor` を実行してください。再インストール、ログイン、権限変更、state のリセットが必要だと決めつけないでください。ユーザー ID が同じでも、ファイルやソケットの可視性が同じとは限りません。この診断は別の namespace に入ったり、マウントを変更したりしません。 + +### プロキシの設定と適用は別々に行う + +```bash +/workspace/.dots-tailscale/dots-ts proxy show +/workspace/.dots-tailscale/dots-ts proxy configure --socks-port 1080 --http-port 8080 +``` + +設定コマンドは `/workspace/.dots-tailscale/outbound-proxy.json` に loopback リスナーの意図した設定を記録します。デーモンを再起動せず、リスナーもすぐには利用可能になりません。SOCKS5 と HTTP プロキシは既定で無効です。2つのリスナーには別々のポートを使ってください。ネイティブ Tailscale では同じポートを使えますが、この実装では当初は共有ポートを拒否します。設定は `/workspace` に保持されますが、環境の入れ替え後にはソケットと実行中プロセスを作り直す必要があります。 + +設定の変更を適用するにはデーモンを明示的に再起動する必要があり、Tailscale SSH が切断される可能性があります。最初に独立した Dots GUI ターミナルを確保し、現在の設定と接続状況を記録して、ロールバックを準備してください。そのターミナルから `/workspace/.dots-tailscale/dots-ts proxy apply` を実行します。`SSH_CONNECTION` または `SSH_CLIENT` が設定されている場合、変更の適用は拒否されます。すでに適用済みの設定なら何も行いません。置き換えに失敗してロールバックが成功すると、以前のデーモンが復元され、保存した設定は未適用のまま残ります。既存の SSH セッションを復旧手段とみなしたり、ローカル再起動の成功だけでリモート接続も可能だと判断したりしないでください。デーモンの状態、ローカル SSH、外部からの厳格な鍵確認を使った SSH、対象アプリケーションを再確認し、失敗した場合は GUI ターミナルからロールバックしてください。`proxy configure` だけでは本番環境にプロキシは展開されません。 + +```bash +/workspace/.dots-tailscale/dots-ts proxy disable +``` + +`disable` は無効状態の設定を保存します。すでに稼働しているリスナーは、変更を明示的に適用するまで残ります。無効化や適用によってノードの state、鍵、インストールが削除されることはありません。 + +アプリケーション用プロキシは、明示的に設定されたローカルクライアント向けに loopback で待ち受けます。これはデーモン用 TLS/WebSocket shim の `127.0.0.1:18089` とは別のものです。その shim をアプリケーション用 HTTP プロキシとして使わないでください。個々の curl リクエストまたは子プロセスにプロキシを設定してください。グローバルな環境設定、ブラウザー、MCP、ChatGPT コネクターには伝播しません。loopback プロキシに接続できるローカルプロセスは利用を試みられるため、ネットワーク方針と API 認証は引き続き必要です。 + +### Git を使わずに v0.3.1 をインストール + +リリースファイル 4 つを別のソースディレクトリにダウンロードします。1つでもリクエストに失敗したら中止し、インストール前にファイルとハッシュを確認してください。 + +```bash +( + set -euo pipefail + mkdir -p /workspace/dots-tailscale-v0.3.1 + cd /workspace/dots-tailscale-v0.3.1 + DOTS_TS_REF=v0.3.1 + for file in tailscale-setup.sh shim.py dots-ts dots_proxy.py; do + curl -fsSL --proxy http://browser-proxy:8889 --connect-timeout 5 --max-time 60 \ + "https://github.com/abruption/dots-tailscale/releases/download/${DOTS_TS_REF}/${file}" \ + -o "$file" + done + sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.py +) +``` + +すべてのダウンロードに成功し、ファイルの確認を終えたら、新規インストールには次のコマンドを実行します。 + +```bash +cd /workspace/dots-tailscale-v0.3.1 +bash tailscale-setup.sh check +bash tailscale-setup.sh install +bash /workspace/.dots-tailscale/start.sh login +``` + +セットアップを実行する前に、4つのファイルを同じディレクトリに置いてください。リリースアセットのホストを利用できない場合は、同じタグとファイル名で `https://raw.githubusercontent.com/abruption/dots-tailscale/v0.3.1/` から取得するか、信頼できる別の端末から確認済みのファイルを転送します。既存インストールを更新する場合は、下記の[更新手順](#restart-update)を使ってください。`install` は内容が異なるインストール済みソースファイルを上書きしません。 + +### 任意の非対話認証 + +ブラウザーを使わず認証する必要がある場合は、Tailscale auth key を非公開のファイルで渡し、明示的に `auth` を実行します。 + +```bash +chmod 600 /workspace/.dots-tailscale/auth.key +DOTS_TS_AUTH_KEY_FILE=/workspace/.dots-tailscale/auth.key \ + bash /workspace/.dots-tailscale/start.sh auth +``` + +安全な方法で別途キーファイルを作成してください。キーをチャット、ソースコード、コマンドライン引数、Git に貼り付けないでください。`DOTS_TS_AUTH_KEY_FILE` には、現在のユーザーが所有する通常ファイルの絶対パスを指定します。シンボリックリンクは不可で、権限は `600` または `400` でなければなりません。キーは `--auth-key=file:PATH` として Tailscale に渡されます。 + +backend がすでに Running の場合、`auth` はキーファイルを読み取らず、また要求もせずに認証を省略します。認証済みを示すマーカーがある状態で state が失われている場合は、このモードでも停止し、既存のノード ID を黙って置き換えることはありません。スクリプトは tailnet のタグや ACL 方針を変更せず、再認証を強制せず、設定をリセットしません。auth コマンドにはプロセスの60秒、接続の45秒の制限があります。ブラウザー認証へのフォールバックはありません。auth key による実際の登録は未検証です。通常の `start` が auth key に自動で切り替わることもありません。 + +## HOME の継続性を確認する + +Dots のリポジトリ checkout から次の probe を実行します。 + +```bash +python3 /workspace/dots-tailscale/home-persistence-probe.py +``` + +初回実行では `$HOME/.dots-home-persistence-marker.json` にランダムなマーカーを作成し、`/workspace/.dots-persistence-probe` の下に baseline、workspace マーカー、最新結果、履歴を保存します。以降の実行では最初の baseline と比較します。probe が読むのは自身のマーカーとプロセスのコンテキストであり、認証 state やユーザーのドキュメントは読みません。 + +| `assessment` | 意味 | +| --- | --- | +| `baseline_created` | 初回の観測を記録 | +| `markers_intact` | 2つのマーカー ID が baseline と一致 | +| `home_marker_changed_workspace_intact` | workspace マーカーはそのままで、HOME マーカーが変化または消失 | +| `marker_continuity_changed` | その他のマーカー継続性の変化 | + +マーカーが失われたことはマーカーの消失を示しますが、HOME ディレクトリ全体がリセットされた証拠ではありません。hostname、boot ID、PID 1 の開始時刻は状況判断の材料になります。boot ID だけではコンテナーの入れ替えとホストの再起動を区別できません。workspace の baseline も消失した場合、次回実行時に新しい baseline が作られるため、比較用の結果を VM の外にも保存してください。probe を SSH 経由で実行し、結果をクライアント側に保存することもできます。 + +```bash +ssh agent@dots-sandbox 'python3 /workspace/dots-tailscale/home-persistence-probe.py' > observation.json +``` + +SSH キーとホスト別名は自分の設定に合わせてください。結果には hostname とマーカー ID が含まれるため、公開前に匿名化してください。 + +カスタムパスは `--home` と `--probe-root` で指定できます。この probe の定期実行は登録されません。 + + + +## インストール済みスクリプトの更新 + +自動アップデーターはありません。動いているブランチからインストール済みファイルを直接更新するのではなく、選択した commit または release を使い、`tailscale-setup.sh` と `shim.py` を `/workspace/.dots-tailscale` の外で一緒に準備して確認します。そのバージョンと2つの SHA-256 ハッシュを記録してください。インストールを変更する前に、準備した shell の構文を検証し、Python ソースを解析してください。 + +v0.3.1 に更新する場合は、同じ commit の `dots-ts` と `dots_proxy.py` も準備します。下の互換性ブロックが検証するのは元の setup/shim の組み合わせだけで、新しいヘルパーは対象外です。さらに、2つの Python ヘルパーを実行せずにコンパイルし、4ファイルすべてのソースハッシュを記録してください。展開とロールバックの手順では、`setup.sh` と `shim/shim.py` に加え、`dots-ts` と `dots_proxy.py` もインストールルートに置き、モードを `700` にします。`outbound-proxy.json` とノード ID ファイルは保持してください。文書やコードの更新に付随する作業として、プロキシ設定を適用したりデーモンを再起動したりしないでください。 + +```bash +python3 -c 'from pathlib import Path; [compile(Path(p).read_text(), p, "exec") for p in ("dots-ts", "dots_proxy.py")]' +sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.py +``` + +別の staging checkout を `/workspace/dots-tailscale` に用意します(必要ならパスを変更してください)。`` は確認済みの40文字の SHA に置き換えます。このブロックはブランチと detached HEAD のどちらでも、繰り返し実行できます。追跡済み、ステージ済み、未追跡の変更を破棄せず、そのまま検出して停止します。fetch、リビジョン確認、checkout、検証が失敗すると subshell が停止します。ブロック全体が成功した場合に限り展開を続けてください。変更対象は staging checkout だけで、インストール済みファイルや認証 state は変更しません。プロキシ設定は fetch にのみ適用します。証明書検証は有効のままにしてください。 + + +```bash +( + set -euo pipefail + cd /workspace/dots-tailscale + REVIEWED_COMMIT='' + [[ "$REVIEWED_COMMIT" =~ ^[0-9a-fA-F]{40}$ ]] || { + printf 'Stop: specify the full reviewed commit SHA.\n' >&2 + exit 1 + } + status=$(git status --porcelain --untracked-files=all) + [[ -z "$status" ]] || { + printf 'Stop: preserve and review local changes before updating.\n%s\n' "$status" >&2 + exit 1 + } + HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \ + git fetch --tags origin + target=$(git rev-parse --verify "${REVIEWED_COMMIT}^{commit}") + git switch --detach "$target" + [[ "$(git rev-parse HEAD)" = "$target" ]] + bash -n tailscale-setup.sh + python3 -c 'from pathlib import Path; compile(Path("shim.py").read_text(), "shim.py", "exec")' + git rev-parse HEAD + sha256sum tailscale-setup.sh shim.py +) +``` + + +shim を変更する場合は、次の手順で展開します。 + +1. Tailscale SSH とは独立した、クラウドコンピューターのプラットフォーム GUI ターミナルを開きます。復旧に使えるよう、そのターミナルは開いたままにします。インストール済み `setup.sh` と `shim/shim.py` のハッシュ、ノードアドレス、SSH 公開ホスト鍵のフィンガープリントを VM の外に記録します。認証済みの基準状態を確認し、候補バージョンとは別に記録してください。 +2. 古い `setup.sh` と `shim/shim.py` のソースファイルだけを、ロールバック用に非公開で保存します。v0.3.1 では既存の `dots-ts` と `dots_proxy.py` も含め、更新前にファイルがなかった場合はその事実を記録してください。認証 state、CA 鍵、SSH ホスト鍵、authorized_keys はそのままにし、別のインストール先へ state をコピーしないでください。プロセス停止前に、時間を区切った切り離し再起動の手順とロールバック手順を準備します。切り離して実行する処理は SSH セッションが切れても続く必要があります。このリポジトリがそのアップデーターを提供しているわけではありません。 +3. 正確なコマンドラインとインストール済みスクリプトのパスを使って、実行中の shim を特定します。独立したターミナルから、特定した shim の PID だけを停止します。プロセス名を使った広範な終了操作はしないでください。終了したことと、重複する shim が残っていないことを確認します。確認済みの `shim.py` を `shim/shim.py` に、`tailscale-setup.sh` を `setup.sh` に、それぞれモード `700` で配置します。v0.3.1 では確認済みの `dots-ts` と `dots_proxy.py` もインストールルートにモード `700` で配置します。 +4. 準備した切り離し起動を実行し、インストール済みソースのハッシュを staging 側と比較します。shim が1つだけであることと、想定されるデーモンプロセスが動作していることを確認します。独立ターミナルで Tailscale CLI の状態とローカル SSH バナーを確認し、続いて外部から DERP 接続と、厳格なホスト鍵検証を使った実際の SSH コマンドを試します。結果を候補バージョンと合わせて記録してください。 +5. 検証に失敗した場合は、独立ターミナルから特定済みの候補 shim だけを停止し、古いソースファイルを戻して再起動します。古いハッシュと基準時の接続を再確認してください。ロールバック中も既存 state と鍵を保持し、予期しないホスト鍵の変更があれば受け入れずに調査してください。 + +更新の近道として `install` を実行したり、インストールディレクトリを削除したりしないでください。インストール処理は意図的に競合ファイルを拒否し、インストール先には ID 情報が含まれます。setup スクリプトだけを更新する場合も、バージョンとハッシュを記録して起動検証を行う必要があります。それだけでは、別途変更した shim が読み込まれたことを確認できません。 + +制御された再起動の成功で確認できるのは、その環境でプロセスが復旧することです。VM の入れ替え後も保持されることの確認にはなりません。継続性 probe は観測用です。自然な環境変更を待ち、結果を外部に保存して、認証状態が継続したかを別途評価してください。この probe のために強制リセットや再起動を行う必要はありません。 + +## ファイルの保存場所と SSH の権限 + +| パス | 内容 | +| --- | --- | +| `/workspace/.dots-tailscale/state` | Tailscale の認証 state | +| `/workspace/.dots-tailscale/shim` | shim、CA、秘密鍵、証明書 | +| `/workspace/.dots-tailscale/sshd` | SSH 設定、ホスト鍵、`authorized_keys` | +| `/tmp/dots-tailscale-$(id -u)` | ユーザーごとのソケット、PID、実行ログ | + +同じ Tailscale ノードの state を、同時に稼働する複数の VM にコピーしないでください。`/workspace` は自動的に非公開になる場所ではありません。アクセスを管理できるディレクトリだけを使ってください。SSH サーバーは、親ディレクトリ `/workspace` の権限によって OpenSSH が公開鍵認証を拒否することがあるため、`StrictModes no` を使用します。セットアップは保存ディレクトリをモード `700`、SSH キー、`authorized_keys`、CA 秘密鍵をモード `600` に制限します。起動時の確認対象は Tailscale CLI とローカル SSH バナーまでで、リモートからの公開鍵ログインを毎回試すことはありません。再起動時には 1 MiB を超えるログを1世代だけローテーションします。プロセスが動き続けている間、ログサイズに上限はありません。 + +`/proc` 内でアクセスできないプロセスがあっても探索を続け、CLI に応答する既存の tailscaled を再利用します。識別できないデーモンのソケットは自動削除しません。 + +## 生成ファイルを保護する + +Tailscale state、ホストとクライアントの秘密鍵、shim の CA 秘密鍵、auth key ファイル、生成したバックアップ、ランタイムログを Git に含めないでください。 + + + +## 現在の検証状況 + +![Dots デスクトップターミナルでインストールファイル、状態、Tailscale のバージョンを示すスクリーンショット](assets/dots-tailscale-ssh-verification.png) + +メンテナー提供のこのスクリーンショットには、Dots デスクトップターミナルの既存インストールファイルと状態、および Tailscale `1.102.4` が写っています。新規インストールや VM の入れ替えを示すものではありません。 + +```bash +bash /workspace/.dots-tailscale/start.sh check +/workspace/.dots-tailscale/tailscale version +``` + +- 以前のバージョンでは、インストールが24時間を超えて稼働することを確認しています。環境の入れ替え後も `/workspace` のインストールファイルが保持され、`start.sh` による手動再起動に成功しました。 +- 2026-10-14、修正版 shim は実環境の DERP ping と、厳格なホスト鍵確認を用いた SSH 公開鍵ログインおよびコマンド実行に成功しました。この版で、その後の VM 入れ替え後の復旧はまだ確認されていません。 +- `check` はファイルとローカル状態を報告します。`running: Tailscale and local SSH ready` は backend が Running で、ローカル SSH バナーが応答することを意味します。リモートの公開鍵ログインは確認しません。 +- Auth Key による登録と、Dots オートメーション経由の起動は未検証です。 + +## オフライン回帰チェック + +リポジトリ checkout から `python3 -m unittest discover -s tests -v` と `bash -n tailscale-setup.sh` を実行します。オンボーディングテストには Git、Bash、Python 3、OpenSSH のクライアント/サーバーツールが必要です。テストでは使い捨てのリポジトリ、鍵、`known_hosts`、モックした Tailscale CLI、分離した Unix ソケットを使います。SSH はリスナーなしでローカル stdio 経由で実行します。これらのテストでは tailnet 認証、実際のプロキシ接続、VM の入れ替えは検証しません。 + +## トラブルシューティング + +- `cannot inspect process` または `process inspection failed`: 現在のスクリプトはアクセスできない `/proc` の項目をスキップします。既存インストールの `setup.sh` は、上記の[更新手順](#restart-update)に従って確認済みのコードに更新してください。 +- `Permission denied (publickey)`: `sshd/authorized_keys` にある公開鍵のフィンガープリントを確認し、必要に応じてクライアントの公開鍵を追加してください。`running` の表示はリモートの公開鍵認証成功を意味しません。 +- `existing file differs`: インストール済みファイルがソースと異なります。設定と鍵を確認し、必要なファイルだけを更新してください。インストールディレクトリ全体を削除しないでください。state とホスト鍵も含まれています。 +- `socket exists but daemon cannot be identified`: 既存のデーモンとソケットを確認してください。スクリプトは識別できないソケットを自動削除しません。 +- 貼り付け後に `^[[200~` などの端末制御文字が表示される場合: コマンドを中止し、1行ずつ入力してください。続行前に、実際に実行するコマンドとホスト鍵のフィンガープリントを確認してください。 diff --git a/docs/usage.ko.md b/docs/usage.ko.md index e601533..4948be8 100644 --- a/docs/usage.ko.md +++ b/docs/usage.ko.md @@ -1,8 +1,10 @@ # 사용 가이드 -[English](usage.md) · [프로젝트 소개](../README.ko.md) +[English](usage.md) · 한국어 · [简体中文](usage.zh-CN.md) · [日本語](usage.ja.md) · [프로젝트 소개](../README.ko.md) -`/workspace`가 환경 교체 후에도 유지되는 Dots 환경에서 실행합니다. 설치·인증 상태와 실행 중에만 필요한 파일을 다른 위치에 두어 재기동 시 기존 장치 신원을 이어 사용합니다. +`/workspace`가 환경 교체 후에도 유지되는 Dots 환경에서 실행합니다. 저장된 자격증명이 유효하면 설치 파일, 인증 상태, SSH 호스트 키를 재사용해 새로 로그인하지 않고 복구할 수 있습니다. 설치 시 자동 시작 훅은 등록하지 않으므로 새 환경마다 start 명령을 실행해야 합니다. + +## 공개 릴리스 공개 저장소 주소는 `https://github.com/abruption/dots-tailscale`입니다. 최초 공개 시 실제 clone과 raw 다운로드를 확인했습니다. [v0.3.1 릴리스](https://github.com/abruption/dots-tailscale/releases/tag/v0.3.1)는 ZIP·tar.gz와 `tailscale-setup.sh`·`shim.py`·`dots-ts`·`dots_proxy.py` 개별 파일을 제공합니다. `main`은 이후 변경될 수 있으므로 같은 소스로 다시 설치하려면 검토한 tag나 commit을 지정하세요. @@ -61,7 +63,7 @@ bash /workspace/.dots-tailscale/start.sh login `check`는 설치 파일의 존재 여부를 출력하고, 실행 중인 소켓이 있으면 Tailscale 상태도 보여줍니다. `missing:`이 있어도 종료코드가 `0`일 수 있으므로 성공 코드만으로 설치 완료를 판단하지 마세요. -기존 `$HOME/.ssh/authorized_keys`가 있으면 설치 시 복사합니다. 없으면 빈 `/workspace/.dots-tailscale/sshd/authorized_keys`를 만들므로 사용할 장비의 **공개키**를 한 줄로 추가하고 권한을 `600`으로 지정합니다. SSH 호스트 키 지문은 VM의 GUI 터미널 등 별도 경로로 확인하고 클라이언트 지문과 대조하세요. 호스트 키 검증을 우회하지 않습니다. +SSH는 `127.0.0.1:22`에서 수신하고 공개키 인증을 사용합니다. Tailscale SSH는 꺼져 있으며 셸 연결은 로컬 OpenSSH 서버가 처리합니다. 기존 `$HOME/.ssh/authorized_keys`가 있으면 설치 시 복사합니다. 없으면 빈 `/workspace/.dots-tailscale/sshd/authorized_keys`를 만들므로 사용할 장비의 **공개키**를 한 줄로 추가하고 권한을 `600`으로 지정합니다. SSH 호스트 키 지문은 VM의 GUI 터미널 등 별도 경로로 확인하고 클라이언트 지문과 대조하세요. 호스트 키 검증을 우회하지 않습니다. Dots의 GUI 터미널에서 다음 명령으로 지문을 확인합니다. @@ -168,11 +170,31 @@ bash /workspace/.dots-tailscale/start.sh bash /workspace/.dots-tailscale/start.sh check ``` -일반 기동에서는 인증을 시작하지 않습니다. `start`·`login`·`auth`에서 backend가 `Running`이고 로컬 SSH 배너를 확인하면 종료코드 `0`입니다. 인증 필요는 `10`, backend 준비 시간 초과나 그 밖의 준비되지 않은 상태는 `11`, 기한 내 유효한 backend 상태를 한 번도 받지 못하면 `12`, 관리자 장치 승인 필요는 `13`입니다. 기타 구성·기동 실패도 비영 종료코드입니다. 필요할 때만 `start.sh login`을 실행합니다. +일반 기동에서는 인증을 시작하지 않습니다. 종료코드는 다음과 같습니다. + +| 기동 결과 | 종료코드 | +|---|---| +| backend 실행 및 로컬 SSH 배너 확인 | `0` | +| 인증 필요 (`needs_auth`) | `10` | +| backend 준비 시간 초과 또는 준비되지 않은 상태 | `11` | +| 기한 내 유효한 backend 상태를 받지 못함 | `12` | +| 관리자 장치 승인 필요 (`needs_machine_auth`) | `13` | +| backend가 실행 중인데 명시적 인증 명령이 실패함 | 원래 명령의 종료코드 | +| 설정 또는 구성요소 기동 실패 | 비영(보통 `1`) | -backend 준비 단계마다 단조 시계 기준 총 30초 동안 조회와 재시도 대기를 수행합니다. 각 조회는 최대 3초이며 남은 예산을 넘지 않습니다. `NoState`·`Starting`은 재조회하고 CLI가 응답했다는 이유만으로 준비 완료로 판단하지 않습니다. 유효한 준비 중 상태를 확인했다면 이후 조회가 실패해도 기한 만료는 `11`로 유지합니다. 이 한도는 backend 단계에 적용되며 전체 구성요소 초기화나 명시적인 인증 시간을 모두 합친 한도가 아닙니다. `login`·`auth`는 먼저 최대 30초 동안 로컬 API의 유효한 응답을 기다립니다. 이 사전 확인은 `NoState`·`Starting`도 허용하므로 새 데몬에 아직 인증이 없어도 명시적인 `up`을 실행할 수 있습니다. 로그인 명령은 별도 180초, Auth Key 인증 명령은 별도 60초 한도를 유지하며, 이후 최종 backend 준비 단계를 수행합니다. 이미 관리자 승인을 기다리는 상태에서는 `up`을 생략합니다. 인증 명령 실패·시간 초과 후에도 backend를 확인해 승인 필요는 `13`, 로그인 필요는 `10`으로 구분합니다. backend가 Running이어도 인증 명령 실패를 성공으로 숨기지 않고 원래 실패 코드를 반환합니다. 새 데몬 기동 여부를 확인하는 상태 조회는 최대 3초입니다. +필요할 때만 `start.sh login`을 실행하세요. -설치 위치는 `DOTS_TS_DIR`, 장비 이름은 `NODE_HOSTNAME`, 업스트림 프록시는 `UPSTREAM_PROXY`로 변경합니다. 설치된 래퍼는 자기 디렉터리를 `DOTS_TS_DIR`로 사용합니다. 프록시 URL은 인증 없는 HTTP CONNECT 주소를 지원합니다. +backend 준비 단계마다 단조 시계 기준 총 30초 동안 조회와 재시도 대기를 수행합니다. 각 조회는 최대 3초이며 남은 예산을 넘지 않습니다. `NoState`·`Starting`은 재조회하고 CLI가 응답했다는 이유만으로 준비 완료로 판단하지 않습니다. 유효한 준비 중 상태를 확인했다면 이후 조회가 실패해도 기한 만료는 `11`로 유지하고, 해당 단계에서 유효한 상태를 한 번도 받지 못하면 `12`를 반환합니다. 이 한도는 backend 단계에 적용되며 전체 구성요소 초기화나 명시적인 인증 시간을 모두 합친 한도가 아닙니다. `login`·`auth`는 먼저 최대 30초 동안 로컬 API의 유효한 응답을 기다립니다. 이 사전 확인은 `NoState`·`Starting`도 허용하므로 새 데몬에 아직 인증이 없어도 명시적인 `up`을 실행할 수 있습니다. 로그인 명령은 별도 180초, Auth Key 인증 명령은 별도 60초 한도를 유지하며, 이후 최종 backend 준비 단계를 수행합니다. 이미 관리자 승인을 기다리는 상태에서는 `up`을 생략합니다. 인증 명령 실패·시간 초과 후에도 backend를 확인해 승인 필요는 `13`, 로그인 필요는 `10`으로 구분합니다. backend가 Running이어도 인증 명령 실패를 성공으로 숨기지 않고 원래 실패 코드를 반환합니다. 새 데몬 기동 여부를 확인하는 상태 조회는 최대 3초입니다. + +설정은 다음 기본값을 사용합니다. + +| 변수 | 기본값 | +|---|---| +| `DOTS_TS_DIR` | `/workspace/.dots-tailscale` | +| `UPSTREAM_PROXY` | `http://browser-proxy:8889` | +| `NODE_HOSTNAME` | `dots-sandbox` | + +`DOTS_TS_DIR`을 바꾸려면 공백이 없는 절대 경로를 지정하세요. 설치와 이후 기동에서 같은 값을 사용해야 합니다. 설치된 래퍼는 자기 디렉터리를 `DOTS_TS_DIR`로 사용합니다. `UPSTREAM_PROXY`에는 인증 정보가 포함되지 않은 HTTP 프록시 주소를 지정하세요. shim은 18089번, SSH는 22번 포트를 사용하므로 한 설치 인스턴스만 실행할 수 있습니다. shim 수정이 포함된 업데이트는 **setup.sh만 교체해서 완료되지 않습니다.** 다음 절차는 운영자가 별도로 수행할 배치·복구 절차이며 현재 자동 updater로 구현돼 있지 않습니다. 2026-10-14 shim 수정본의 실환경 배포 후 DERP ping과 엄격한 호스트 키 검증을 유지한 SSH 연결은 성공했습니다. 수정본 상태에서 이후 VM 교체를 거친 복구는 아직 관찰하지 않았습니다. 자세한 범위는 아래 [현재 확인한 범위](#current-validation)를 참고하세요. @@ -224,22 +246,25 @@ sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.py 이 업데이트 작업은 프로세스 재시작이며 VM 교체 검증이 아닙니다. 자연스러운 실제 교체 전후에는 HOME/workspace 관찰 결과를 클라우드 컴퓨터 밖에 보관하고 비교합니다. 아직 교체가 확인되지 않으면 관찰 대기로 유지합니다. -| 보관 위치 | 내용 | +## 파일 저장 위치와 SSH 권한 + +| 경로 | 내용 | |---|---| | `/workspace/.dots-tailscale/state` | Tailscale 인증 state | -| `…/shim` | shim·CA·개인키·인증서 | -| `…/sshd` | SSH 설정·호스트 키·authorized_keys | -| `/tmp/dots-tailscale-1000` | UID 1000 기준 소켓·PID·로그 | +| `/workspace/.dots-tailscale/shim` | shim·CA·개인키·인증서 | +| `/workspace/.dots-tailscale/sshd` | SSH 설정·호스트 키·`authorized_keys` | +| `/tmp/dots-tailscale-$(id -u)` | 사용자별 소켓·PID·실행 로그 | -이미 인증했던 설치에서 state가 사라지면 다른 신원을 자동 생성하지 않고 중단합니다. 동일 state를 여러 실행 중인 VM에 복제하지 마세요. `/proc`의 일부 프로세스가 접근 거부돼도 탐색을 계속하며, CLI가 응답하는 기존 tailscaled는 재사용합니다. 식별되지 않는 데몬의 소켓은 자동 삭제하지 않습니다. +이전에 인증한 설치에서 state가 사라지면 스크립트는 다른 장치 신원을 자동 생성하지 않고 중단합니다. 같은 Tailscale state를 동시에 실행 중인 여러 VM에 복제하지 마세요. `/workspace`는 자동으로 비공개 처리되는 위치가 아니므로 접근을 통제할 수 있는 디렉터리만 사용하세요. SSH 설정은 `/workspace` 상위 디렉터리 권한 때문에 OpenSSH가 공개키 인증을 거부하지 않도록 `StrictModes no`를 사용합니다. 보관 디렉터리는 `700`, SSH 키·`authorized_keys`·CA 개인키는 `600` 권한으로 제한합니다. 기동 시에는 Tailscale CLI와 로컬 SSH 배너까지만 확인하며, 외부 공개키 로그인을 매번 시험하지 않습니다. 재기동 시 1 MiB가 넘는 로그는 한 세대만 남기고 회전하지만, 프로세스가 계속 실행 중일 때 로그 크기에는 상한이 없습니다. -`StrictModes no`를 사용하되 보관 디렉터리는 `700`, SSH 키·authorized_keys·CA 개인키는 `600`으로 제한합니다. `/workspace` 상위 경로 권한 때문에 공개키 인증이 거부되는 것을 피하기 위한 설정입니다. 기동 검증은 CLI와 로컬 SSH 배너까지이며 원격 공개키 로그인을 매번 확인하지 않습니다. 로그는 재기동 시 1 MiB 초과 파일을 한 세대 회전하며 계속 실행 중인 로그 크기에는 상한이 없습니다. +`/proc`의 일부 프로세스에 접근할 수 없어도 탐색을 계속하며, CLI가 응답하는 기존 tailscaled는 재사용합니다. 식별되지 않는 데몬의 소켓은 자동 삭제하지 않습니다. ## 선택적 Auth Key 인증 브라우저 없이 등록하려면 Tailscale 관리 화면에서 발급한 Auth Key를 별도 파일에 저장합니다. 키는 채팅·스크립트·Git에 넣지 마세요. 파일은 현재 사용자 소유의 일반 파일이어야 하며, 절대 경로·심볼릭 링크 없는 경로·권한 `600` 또는 `400`이 필요합니다. ```bash +chmod 600 /workspace/.dots-tailscale/auth.key DOTS_TS_AUTH_KEY_FILE=/workspace/.dots-tailscale/auth.key \ bash /workspace/.dots-tailscale/start.sh auth ``` @@ -268,7 +293,7 @@ hostname·boot_id·PID 1 시작 값도 비교합니다. 식별 파일 소실은 ssh agent@dots-sandbox 'python3 /workspace/dots-tailscale/home-persistence-probe.py' > observation.json ``` -SSH 키 파일·호스트 별칭은 자신의 설정에 맞게 지정하세요. 결과에는 hostname과 식별자가 포함되므로 공개하기 전에 익명화하세요. +SSH 키 파일·호스트 별칭은 자신의 설정에 맞게 지정하세요. 결과에는 hostname과 식별자가 포함되므로 공개하기 전에 익명화하세요. `--home`과 `--probe-root` 옵션으로 사용자 지정 경로를 지정할 수 있습니다. @@ -288,10 +313,12 @@ bash /workspace/.dots-tailscale/start.sh check - `check`는 파일 목록과 상태 확인용입니다. `start`의 `running: Tailscale and local SSH ready`는 backend와 로컬 SSH 배너 확인을 뜻하며, 외부 장비에서의 SSH 공개키 로그인을 매번 검사하는 것은 아닙니다. - Auth Key 인증과 Dots 자동화를 이용한 자동 기동은 아직 시험하지 않았습니다. -## 검증과 문제 해결 +## 오프라인 회귀 검사 저장소 체크아웃에서 `python3 -m unittest discover -s tests -v`와 `bash -n tailscale-setup.sh`를 실행합니다. 최초 접속 회귀 테스트에는 Git·Bash·Python 3·OpenSSH 클라이언트/서버 도구가 필요합니다. 임시 저장소·키·`known_hosts`, 모의 Tailscale CLI와 격리된 Unix socket을 사용합니다. SSH는 listener 없이 로컬 표준 입출력으로 실행하며 tailnet 인증·실제 프록시 연결·VM 교체는 검증하지 않습니다. +## 문제 해결 + - 이전 버전의 `cannot inspect process` 또는 현재의 `process inspection failed`: 현재 스크립트는 일부 `/proc` 접근 거부를 건너뜁니다. 기존 설치의 `setup.sh`는 위 [업데이트 절차](#restart-update)에 따라 검토한 코드로 갱신하세요. - `Permission denied (publickey)`: `sshd/authorized_keys`의 공개키 지문을 확인하고 접속 장비의 공개키를 등록하세요. `running` 메시지는 공개키 인증 성공까지 확인하지 않습니다. - `existing file differs`: 설치 파일 차이입니다. 설정·키를 검토한 뒤 필요한 파일만 갱신하세요. 설치 디렉터리 전체를 지우면 state와 호스트 키도 사라집니다. diff --git a/docs/usage.md b/docs/usage.md index 41ff508..7feecb5 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,17 +1,13 @@ # Usage -[English README](../README.md) · [한국어 사용법](usage.ko.md) +English · [한국어](usage.ko.md) · [简体中文](usage.zh-CN.md) · [日本語](usage.ja.md) · [Project overview](../README.md) These instructions target a user-level installation in an environment that preserves `/workspace` across environment replacements. The recovery path reuses saved installation files, authentication state, and SSH host keys, without a new login while saved credentials remain valid. Installation does not register a startup hook; run the start command in each new environment. -## Published version and verification +## Published release The initial public repository clone and raw downloads were verified against the prepared source. [Release v0.3.1](https://github.com/abruption/dots-tailscale/releases/tag/v0.3.1) includes ZIP and tar.gz archives plus `tailscale-setup.sh`, `shim.py`, `dots-ts`, and `dots_proxy.py` as individual assets. The installation examples pin the source to `v0.3.1`. The bundled Tailscale version remains `1.102.4`. -![Dots desktop terminal screenshot of installation checks and Tailscale version](assets/dots-tailscale-ssh-verification.png) - -This screenshot provided by the maintainer shows `start.sh check` and Tailscale version `1.102.4` in the Dots desktop terminal. The supplied image is used unchanged. It documents the existing installation's files, status, and CLI version, rather than a new installation or a VM replacement. - ## Download connectivity and proxy settings @@ -76,7 +72,7 @@ After authentication, use this command to start missing components: bash /workspace/.dots-tailscale/start.sh ``` -For inspection or explicit authentication, pass `check` or `login` to that installed wrapper. Installation generates `start.sh` in the installation directory and copies `tailscale-setup.sh` to `setup.sh`. The generated wrapper derives that directory from its own location and invokes the neighboring `setup.sh`. +For inspection or explicit authentication, pass `check` or `login` to that installed wrapper. Installation generates `start.sh` in the installation directory and copies `tailscale-setup.sh` to `setup.sh`. The generated wrapper derives that directory from its own location and invokes the neighboring `setup.sh`. For an existing installation, use the [update procedure](#restart-update) below. | Setting | Default | | --- | --- | @@ -84,7 +80,7 @@ For inspection or explicit authentication, pass `check` or `login` to that insta | `UPSTREAM_PROXY` | `http://browser-proxy:8889` | | `NODE_HOSTNAME` | `dots-sandbox` | -An alternate `DOTS_TS_DIR` must be an absolute path without whitespace. Use consistent settings for installation and subsequent starts. The shim currently listens on port 18089 and SSH on port 22; this is a single-installation setup. +An alternate `DOTS_TS_DIR` must be an absolute path without whitespace. `UPSTREAM_PROXY` must be an unauthenticated HTTP proxy URL. Use consistent settings for installation and subsequent starts. The shim currently listens on port 18089 and SSH on port 22; this is a single-installation setup. ### SSH keys and readiness @@ -247,10 +243,18 @@ The first run creates a random marker in `$HOME/.dots-home-persistence-marker.js | `home_marker_changed_workspace_intact` | HOME marker changed or disappeared while workspace marker remained intact | | `marker_continuity_changed` | Other marker continuity change | -Marker loss is evidence of marker loss, not proof that the entire HOME directory was reset. Hostname, boot ID, and PID 1 start ticks provide context; boot ID alone cannot distinguish a container replacement from a host reboot. If the workspace baseline disappears too, the next run creates a new baseline, so retain a result outside the VM for comparison. +Marker loss is evidence of marker loss, not proof that the entire HOME directory was reset. Hostname, boot ID, and PID 1 start ticks provide context; boot ID alone cannot distinguish a container replacement from a host reboot. If the workspace baseline disappears too, the next run creates a new baseline, so retain a result outside the VM for comparison. You can run the probe over SSH and save its result on your client: + +```bash +ssh agent@dots-sandbox 'python3 /workspace/dots-tailscale/home-persistence-probe.py' > observation.json +``` + +Adjust the SSH key and host alias for your setup. The result includes the hostname and marker IDs; anonymize them before sharing it publicly. Custom paths are available through `--home` and `--probe-root`. No periodic execution is registered for this probe. + + ## Update installed scripts There is no automatic updater. Stage and review `tailscale-setup.sh` and `shim.py` together outside `/workspace/.dots-tailscale`, using a selected commit or release rather than updating installed files in place from a moving branch. Record that version and both SHA-256 hashes. Validate the staged shell syntax and parse the staged Python source before changing the installation. @@ -304,10 +308,49 @@ Do not run `install` as an update shortcut or delete the installation directory. A successful controlled restart verifies process recovery for that environment, not persistence after VM replacement. The continuity probe remains observational: wait for a natural environment change, retain its results externally, and evaluate authenticated continuity separately. No forced reset or restart is needed for the continuity probe. +## File storage and SSH permissions + +| Path | Contents | +| --- | --- | +| `/workspace/.dots-tailscale/state` | Tailscale authentication state | +| `/workspace/.dots-tailscale/shim` | Shim, CA, private keys, and certificates | +| `/workspace/.dots-tailscale/sshd` | SSH configuration, host keys, and `authorized_keys` | +| `/tmp/dots-tailscale-$(id -u)` | Per-user sockets, PIDs, and runtime logs | + +The SSH server uses `StrictModes no` because permissions on parent `/workspace` directories can otherwise cause OpenSSH to reject public-key authentication. The setup restricts storage directories to mode `700` and SSH keys, `authorized_keys`, and the CA private key to mode `600`. Startup checks the Tailscale CLI and local SSH banner; it does not test remote public-key login on every start. On restart, logs larger than 1 MiB are rotated for one generation. Logs have no size limit while a process keeps running. + +Process inspection continues past `/proc` entries that cannot be accessed. If the CLI responds, the setup reuses the existing `tailscaled`. It does not automatically remove a socket belonging to an unidentified daemon. + ## Protect generated files Keep Tailscale state, host and client private keys, shim CA private keys, auth-key files, generated backups, and runtime logs out of Git. Do not copy one Tailscale node's state into multiple simultaneously running VMs. `/workspace` is not automatically a private location; only use a directory whose access you control. + + +## Current validation + +![Dots desktop terminal screenshot showing installation files, status, and Tailscale version](assets/dots-tailscale-ssh-verification.png) + +This maintainer-provided screenshot shows the existing installation files and status, plus Tailscale version `1.102.4`, in the Dots desktop terminal. It does not show a new installation or a VM replacement. + +```bash +bash /workspace/.dots-tailscale/start.sh check +/workspace/.dots-tailscale/tailscale version +``` + +- The maintainer previously observed the installation running for more than 24 hours. After an environment replacement, the `/workspace` installation files remained and `start.sh` restarted it manually. +- On 2026-10-14, the revised shim passed a live DERP ping and strict-host-key SSH public-key login and command checks. Recovery after a later VM replacement has not been observed with that revision. +- `check` reports files and local status. `running: Tailscale and local SSH ready` means the backend is Running and the local SSH banner responds; it does not verify remote public-key login. +- Auth-key enrollment and startup through Dots automation have not been tested. + ## Offline regression checks Run `python3 -m unittest discover -s tests -v` and `bash -n tailscale-setup.sh` from the repository checkout. The onboarding tests require Git, Bash, Python 3, and OpenSSH client/server tools. They use disposable repositories, keys, `known_hosts`, a mocked Tailscale CLI, and an isolated Unix socket. SSH runs over local stdio without a listener; these tests do not authenticate to a tailnet, exercise the live proxy, or validate VM replacement. + +## Troubleshooting + +- `cannot inspect process` or `process inspection failed`: The current script skips `/proc` entries it cannot access. Update the installed `setup.sh` using the reviewed procedure above. +- `Permission denied (publickey)`: Check the public-key fingerprint in `sshd/authorized_keys` and add your client public key if needed. The `running` message does not confirm remote public-key authentication. +- `existing file differs`: An installed file differs from the source. Review the configuration and keys, then update only the files that need it. Do not delete the installation directory; it also contains state and host keys. +- `socket exists but daemon cannot be identified`: Check the existing daemon and socket. The script does not automatically remove an unidentified socket. +- Terminal text such as `^[[200~` appears after pasting: Cancel the command and enter it one line at a time. Check the command and host-key fingerprint before continuing. diff --git a/docs/usage.zh-CN.md b/docs/usage.zh-CN.md new file mode 100644 index 0000000..9dec75b --- /dev/null +++ b/docs/usage.zh-CN.md @@ -0,0 +1,356 @@ +# 使用指南 + +[English](usage.md) · [한국어](usage.ko.md) · 简体中文 · [日本語](usage.ja.md) · [项目介绍](../README.zh-CN.md) + +本指南适用于用户级安装,前提是环境替换后 `/workspace` 仍会保留。恢复时会复用已保存的安装文件、身份验证状态和 SSH 主机密钥;只要已保存的凭据仍有效,就无需重新登录。安装过程不会注册启动钩子;每次进入新环境时都要运行启动命令。 + +## 已发布版本 + +首次公开仓库的克隆与原始文件下载已与准备好的源文件核对确认。[版本 v0.3.1](https://github.com/abruption/dots-tailscale/releases/tag/v0.3.1)包含 ZIP 和 tar.gz 压缩包,也提供 `tailscale-setup.sh`、`shim.py`、`dots-ts` 和 `dots_proxy.py` 四个单独的资源文件。安装示例将源版本固定为 `v0.3.1`。捆绑的 Tailscale 版本仍为 `1.102.4`。 + + + +## 下载连通性与代理设置 + +在 2026-10-15 测试的 Dots SSH 环境中,直接向 GitHub 仓库、原始文件和发布资源 URL 发出的 HTTPS 请求均失败,而明确使用 `http://browser-proxy:8889` 的请求成功。通过代理克隆后,`v0.2.0` 解析到提交 `643484`,下载的原始文件也与该修订版的校验和一致。这些观察结果只适用于该环境,不能据此认定 GitHub 在全局范围内遭到屏蔽。浏览器下载需要单独检查;这些终端检查无法诊断浏览器错误。 + +如果克隆或下载失败,可在受影响的终端运行以下有时限的检查,查看代理路径是否可用。命令会丢弃响应正文,并仅显示最终 HTTP 状态,不会泄露重定向后的签名下载 URL: + +```bash +for url in \ + 'https://github.com/abruption/dots-tailscale' \ + 'https://raw.githubusercontent.com/abruption/dots-tailscale/v0.2.0/tailscale-setup.sh' \ + 'https://github.com/abruption/dots-tailscale/releases/download/v0.2.0/tailscale-setup.sh' +do + printf '%s\n' "$url" + curl --fail --silent --show-error --location \ + --proxy http://browser-proxy:8889 --connect-timeout 5 --max-time 20 \ + --output /dev/null --write-out 'HTTP %{http_code}\n' "$url" +done +``` + +如需与直连路径对比,可在相同命令中将 `--proxy http://browser-proxy:8889` 替换为 `--noproxy '*'`。curl 退出码 `7` 表示连接失败,`28` 表示超时;这两种结果都不能单独证明某个主机名遭到屏蔽。请保持证书验证开启;如果环境提供的代理地址不同,请使用实际提供的地址。 + +这些设置的作用范围各不相同: + +| 设置 | 作用范围 | +| --- | --- | +| `git clone` 或 `git fetch` 之前设置 `HTTPS_PROXY` / `HTTP_PROXY` | 仅对该 Git 命令生效;不会更改全局 Git 设置 | +| curl `--proxy` | 仅对该下载或诊断请求生效 | +| 安装程序的 `UPSTREAM_PROXY` | 安装期间下载 Tailscale 软件包和校验文件时生效,也作为启动期间 shim 的上游代理 | +| `127.0.0.1:18089` 上的本地 shim | Tailscale 守护进程通过其进程环境使用的 TLS/WebSocket 桥接服务 | + +本地 shim 不会配置全局代理,也不会让无关应用通过 Tailscale 路由。如果环境使用其他上游地址,请在安装和之后每次启动时显式设置 `UPSTREAM_PROXY`。 + +## 安装并启动 + +将仓库从 `https://github.com/abruption/dots-tailscale` 克隆到 `/workspace`,以便安装脚本和 `shim.py` 位于一起。克隆需要 Git,以及云计算机通过代理访问 GitHub HTTPS 的能力。安装程序需要 Linux x86_64、Python 3、`timeout`、`flock`、OpenSSL、`ssh-keygen` 和 `/usr/sbin/sshd`。此外,安装还需要 curl、tar 和 sha256sum。当前用户必须获准绑定端口 22;无需 root 或 systemd。 + +对于 v0.3.1,请将以下四个源文件放在一起:`tailscale-setup.sh`、`shim.py`、`dots-ts` 和 `dots_proxy.py`。较旧的 v0.2.0 安装只使用原来的两个文件,不包含这些辅助工具。 + +```bash +cd /workspace +HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \ + git clone --branch v0.3.1 --depth 1 'https://github.com/abruption/dots-tailscale' dots-tailscale +cd dots-tailscale +bash tailscale-setup.sh check +bash tailscale-setup.sh install +bash /workspace/.dots-tailscale/start.sh login +``` + +这些代理设置仅对克隆命令生效,不会更改全局 Git 配置。如果环境提供的代理地址不同,请使用该地址。请保持 HTTPS 证书验证开启。 + +`check` 会列出所选安装文件是否存在;如果守护进程套接字和 CLI 可用,还会输出 Tailscale 状态。它不会启动组件,也不会运行依赖项检查。仅仅缺少文件不会导致退出码非零:未安装的环境可能会输出 `missing:` 行并以退出码 `0` 退出。要判断是否已就绪,请确认成功启动并实际建立 SSH 连接。 + +`install` 只会放置文件,不会启动进程。它会保留现有二进制文件;如果发现配置或脚本冲突,就会停止安装并要求人工检查。下载的 Tailscale 压缩包固定为脚本中指定的版本,并会根据其官方 SHA-256 文件进行校验。 + +`login` 会启动尚未运行的组件,并以交互方式进行 Tailscale 身份验证,时限为 180 秒。打开显示的 URL,确认要使用的账户和 tailnet 并完成授权。这些脚本中没有嵌入身份验证密钥;下文会介绍可选的基于文件的身份验证方式。 + +身份验证完成后,使用以下命令启动尚未运行的组件: + +```bash +bash /workspace/.dots-tailscale/start.sh +``` + +如需检查状态或显式进行身份验证,可向已安装的包装脚本传入 `check` 或 `login`。安装会在安装目录中生成 `start.sh`,并将 `tailscale-setup.sh` 复制为 `setup.sh`。生成的包装脚本会根据自身位置确定该目录,并调用同级的 `setup.sh`。更新现有安装时,请按照下文的[更新步骤](#restart-update)操作。 + +| 设置 | 默认值 | +| --- | --- | +| `DOTS_TS_DIR` | `/workspace/.dots-tailscale` | +| `UPSTREAM_PROXY` | `http://browser-proxy:8889` | +| `NODE_HOSTNAME` | `dots-sandbox` | + +备用的 `DOTS_TS_DIR` 必须是绝对路径,且不能包含空白字符。`UPSTREAM_PROXY` 必须使用不含身份验证信息的 HTTP 代理 URL。安装和之后每次启动都应使用一致的设置。当前 shim 监听端口 18089,SSH 监听端口 22;此配置仅支持单个安装实例。 + +### SSH 密钥与就绪状态 + +SSH 监听 `127.0.0.1:22`,并使用公钥身份验证。Tailscale SSH 已禁用;shell 会话由本地 OpenSSH 服务器处理。如果 HOME 中已有 `.ssh/authorized_keys`,安装程序会将其复制到持久化安装目录中。否则,请将客户端公钥添加到: + +```text +/workspace/.dots-tailscale/sshd/authorized_keys +``` + +将客户端公钥作为一整行附加到文件中,并将文件权限设为 `600`。在客户端上,从对应的 `.pub` 文件获取公钥;绝不要传输私钥。在客户端接受服务器主机密钥之前,请先在云计算机上核对主机密钥指纹: + +```bash +ssh-keygen -lf /workspace/.dots-tailscale/sshd/ssh_host_ed25519_key.pub +``` + +在 Dots 终端中,通过已安装的包装脚本获取节点地址: + +```bash +bash /workspace/.dots-tailscale/start.sh check +``` + +此操作使用已安装的 CLI 及其每用户套接字 `/tmp/dots-tailscale-$(id -u)/tailscaled.sock`;无需全局 `tailscale` 命令或默认套接字。要显示节点地址,守护进程必须已经在运行。自定义安装时,请使用该安装目录中的 `start.sh`。 + +首次连接时,在 tailnet 客户端上运行以下命令。在主机密钥提示处,将显示的指纹与之前在 Dots 终端中独立获取的指纹进行比较。如果两者不一致,请回答 `no` 并停止;不要登记该密钥,也不要删除现有条目。不要仅凭 `ssh-keyscan` 的输出信任密钥。只有指纹一致时才接受;接受后,客户端会将该密钥记录到 `known_hosts` 文件中。 + +```bash +ssh -o StrictHostKeyChecking=ask -i /path/to/client_private_key 'agent@' 'id; pwd' +``` + +已记录并验证密钥后,后续连接请启用严格检查: + +```bash +ssh -o StrictHostKeyChecking=yes -i /path/to/client_private_key 'agent@' 'id; pwd' +``` + +如果云计算机上的用户名与示例不同,请替换示例用户名。守护进程运行时,请使用 `start.sh check` 报告的地址。`StrictHostKeyChecking=yes` 要求主机密钥已经受信任;它无法通过交互方式接受新密钥。如果密钥发生变化,请先独立核验新指纹,再修改保存的条目;不要绕过主机密钥验证。 + +`running: Tailscale and local SSH ready` 表示后端处于 Running 状态,且本地 SSH banner 有响应。这并不能证明客户端公钥身份验证成功;请另行测试实际 SSH 命令。 + +| 启动结果 | 退出码 | +| --- | --- | +| Tailscale 运行且本地 SSH 已就绪 | `0` | +| 需要身份验证 (`needs_auth`) | `10` | +| 后端就绪时限已到或处于其他不可用状态 | `11` | +| 截止时限仍未收到有效后端状态 | `12` | +| 需要管理员批准这台设备 (`needs_machine_auth`) | `13` | +| 后端处于 Running 状态时显式身份验证命令失败 | 原始命令的失败退出码 | +| 安装或组件故障 | 非零,通常为 `1` | + +后端就绪检查在每个阶段使用一个 30 秒单调时钟截止时间,其中包括重试和等待;每次状态查询最多耗时三秒,且不能超过剩余时限。`NoState` 和 `Starting` 会重试。如果此前观察到有效的暂态,则即使后续查询失败,时限到期仍返回退出码 `11`;退出码 `12` 表示该阶段始终未获得有效状态。仅 CLI 通信成功并不代表已就绪。此截止时间仅覆盖后端阶段,不包括所有组件初始化或显式身份验证:`login` 和 `auth` 会先等待本地 API 返回有效响应,最长 30 秒。此预检查接受 NoState/Starting 状态,不要求后端处于 Running,因此刚启动且尚未身份验证的守护进程也可以继续执行显式 `up`。`login` 仍有单独的 180 秒命令时限;`auth` 则有单独的 60 秒身份验证时限;之后两者都会执行最终的后端就绪检查。如果机器已处于待批准状态,则跳过 `up`。如果 `up` 失败或超时,仍会检查后端状态:NeedsMachineAuth 映射为 `13`,NeedsLogin 映射为 `10`;后端处于 Running 状态也不会掩盖命令失败。新启动后的状态探测最多耗时三秒。 + +普通的 `start` 不会调用 login。先前已通过身份验证的状态若消失,程序会报错并要求人工检查,而不会静默替换身份。现有文件冲突、CA 安装不完整或已建立的主机密钥缺失,也都需要人工检查。更新脚本时不要删除安装目录:该目录包含节点状态和私钥。 + +自定义 TLS/WebSocket shim 实现了配置好的本地 HTTP CONNECT 桥接服务。使用隔离套接字进行的本地测试涵盖了初始载荷保留和传输边界。修订后的 shim 于 2026-10-14 部署,并通过代理路径完成了实时 DERP ping 和严格公钥 SSH 检查。使用旧版 shim 时,维护者观察到安装文件在环境替换后仍被保留,并成功手动重启。修订版尚未经过后续环境替换验证。平台代理发生变化时,该 shim 可能无法工作。 + +本仓库没有 VM cron、systemd 服务或自动启动注册。尚未测试通过 Dots 自动化启动。VM 内的进程循环无法在该环境销毁后继续运行。请在新环境中显式启动安装,或通过单独验证过的平台执行机制启动。 + + + +## v0.3.1 辅助工具与可选应用代理 + +以下命令要求 v0.3.1 的文件已一起安装;v0.2.0 发布版不提供这些命令。`dots-ts` 使用 Python 3 标准库,并根据自身位置查找安装目录。无需全局 `tailscale` 命令、套接字符号链接或修改 shell 配置文件。 + +```bash +/workspace/.dots-tailscale/dots-ts cli status +/workspace/.dots-tailscale/dots-ts doctor +/workspace/.dots-tailscale/dots-ts doctor --json +/workspace/.dots-tailscale/dots-ts doctor --target api.example.internal:8443 +``` + +`cli` 使用安装目录的运行时套接字执行已安装的 Tailscale CLI,并转发其参数。它会保留原始输出和交互行为,没有辅助工具时限,而且可能暴露地址或其他敏感 CLI 输出。运行时间较长的命令请自行设置时限。它不会自行登录或启动守护进程。 + +`doctor` 仅执行只读检查:不会启动进程、进行身份验证、修复文件或更改代理设置。它按层级报告经过脱敏的本地检查结果,不会宣称每个应用都已就绪。请查看 `overall` 和各项结果:即使为 `degraded`,退出码也可能是 `0`。`--target HOST:PORT` 还会通过本地 Tailscale 守护进程执行有时限的 TCP 连通性检查,而不会连接应用 HTTP/SOCKS 监听器;该检查不会发送凭据,也无法证明 TLS、API 身份验证或授权状态。目标名称仍会触发明确的网络请求,因此请选择你确实要联系的服务。应用检查和验证矩阵请参阅[私有 HTTP API 配置示例](private-http-api.zh-CN.md)。 + +### 执行上下文诊断 + +`doctor` 只描述当前执行上下文及其中可见的进程。它会在 JSON 中新增 `context` 对象,用于报告安装目录和运行时的可见性、基于元数据判断的读写权限,以及文件系统的 `read_only` / `read_write` / `unknown` 状态。这些都是元数据和访问权限检查;不会尝试写入。`advice` 包含固定的指导 ID,`skipped_checks` 标出未执行的检查;人类可读输出会展开相应指导内容。 + +套接字缺失可能意味着它在当前上下文中不可见,但守护进程仍在其他位置运行。就当前上下文而言,该结果仍为 `unhealthy`,退出码为 `1`;权限被拒绝或套接字无响应同样属于失败。检查结果未知,或安装目录只读但其他服务正常时,结果为 `degraded`,退出码为 `0`;只有 `overall=healthy` 时,`healthy` 才为 true。请检查各个字段,不要仅凭退出码 `0` 判断已就绪。套接字元数据和连接结果会区分缺失、访问被拒和不可用状态。 + +如果安装目录或运行时为只读或无法访问,或者套接字缺失/无响应,请打开云计算机上独立的 GUI 终端,并在那里运行 `doctor`。不要据此推断必须重新安装、登录、更改权限或重置状态。用户 ID 相同并不能证明文件系统或套接字的可见性相同。此诊断不会进入其他命名空间,也不会更改挂载。 + +### 分别配置并应用代理 + +```bash +/workspace/.dots-tailscale/dots-ts proxy show +/workspace/.dots-tailscale/dots-ts proxy configure --socks-port 1080 --http-port 8080 +``` + +配置会将所需的 loopback 监听器记录到 `/workspace/.dots-tailscale/outbound-proxy.json`;它不会重启守护进程,也不会立即启用监听器。SOCKS5 和 HTTP 代理默认禁用。两个监听器请使用不同端口:即使原生 Tailscale 支持共用一个端口,此实现最初仍会拒绝共用端口的设置。配置会保存在 `/workspace`,而环境替换后必须重新创建套接字和运行中的进程。 + +应用更改后的配置需要显式重启守护进程,且可能中断 Tailscale SSH。首先确保可以使用独立的 Dots GUI 终端,记录当前设置和连通性,并准备好回滚方案。在该终端运行 `/workspace/.dots-tailscale/dots-ts proxy apply`。如果设置了 `SSH_CONNECTION` 或 `SSH_CLIENT`,更改配置的操作会被拒绝;已经应用的配置则不会产生变化。如果替换失败但回滚成功,旧守护进程会恢复,而所需配置仍处于待应用状态。不要把现有 SSH 会话当作恢复途径,也不要认为本地重启成功就证明远程访问正常。请重新检查守护进程状态、本地 SSH、外部严格主机密钥 SSH 和目标应用;如果检查失败,请从 GUI 终端回滚。仅执行 `proxy configure` 不会部署生产代理。 + +```bash +/workspace/.dots-tailscale/dots-ts proxy disable +``` + +`disable` 会保存禁用配置;已运行的监听器仍会继续运行,直到显式应用更改。禁用代理或应用设置不会删除节点状态、密钥或安装文件。 + +应用代理会在 loopback 上监听,供显式配置的本地客户端使用。它们与守护进程的 TLS/WebSocket shim(位于 `127.0.0.1:18089`)不同。不要把该 shim 当作应用 HTTP 代理。请在单次 curl 请求或子进程上设置代理;代理设置不会传递到全局环境、浏览器、MCP 或 ChatGPT 连接器。能够访问 loopback 代理的本地进程都可以尝试使用它,因此仍需设置网络策略并进行 API 身份验证。 + +### 不使用 Git 安装 v0.3.1 + +将全部四个发布文件下载到单独的源文件目录中。如果任一请求失败,请停止;安装前请检查文件和哈希: + +```bash +( + set -euo pipefail + mkdir -p /workspace/dots-tailscale-v0.3.1 + cd /workspace/dots-tailscale-v0.3.1 + DOTS_TS_REF=v0.3.1 + for file in tailscale-setup.sh shim.py dots-ts dots_proxy.py; do + curl -fsSL --proxy http://browser-proxy:8889 --connect-timeout 5 --max-time 60 \ + "https://github.com/abruption/dots-tailscale/releases/download/${DOTS_TS_REF}/${file}" \ + -o "$file" + done + sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.py +) +``` + +所有文件下载成功且检查完毕后,对于全新安装,请运行以下命令: + +```bash +cd /workspace/dots-tailscale-v0.3.1 +bash tailscale-setup.sh check +bash tailscale-setup.sh install +bash /workspace/.dots-tailscale/start.sh login +``` + +运行安装程序前,请将全部四个文件保存在同一目录中。如果发布资源主机不可用,请从 `https://raw.githubusercontent.com/abruption/dots-tailscale/v0.3.1/` 下载相同标签和文件名的文件,或从另一台可信设备传输已检查的文件。若为现有安装,请参阅[下文的更新步骤](#restart-update);`install` 不会覆盖内容不同的已安装源文件。 + +### 可选的非交互式身份验证 + +如果需要无浏览器身份验证,请将 Tailscale 身份验证密钥放在私密文件中,然后显式运行 `auth`: + +```bash +chmod 600 /workspace/.dots-tailscale/auth.key +DOTS_TS_AUTH_KEY_FILE=/workspace/.dots-tailscale/auth.key \ + bash /workspace/.dots-tailscale/start.sh auth +``` + +请使用安全方式单独创建密钥文件。不要将密钥粘贴到聊天、源代码、命令行参数或 Git 中。`DOTS_TS_AUTH_KEY_FILE` 必须指定当前用户拥有的常规文件的绝对路径;该文件不能是符号链接,权限必须恰好为 `600` 或 `400`。密钥会以 `--auth-key=file:PATH` 的形式传给 Tailscale。 + +如果后端已处于 Running 状态,`auth` 会跳过身份验证,不读取也不要求提供密钥文件。已有身份验证标记之后若状态丢失,此模式仍会拒绝继续;不会静默替换现有节点身份。脚本不会更改 tailnet 标签或 ACL 策略、强制重新身份验证或重置偏好设置。auth 命令受 60 秒进程超时和 45 秒连接超时限制;不会回退到浏览器身份验证。尚未测试实际的身份验证密钥注册。普通 `start` 绝不会自动回退到身份验证密钥。 + +## 检查 HOME 连续性 + +在 Dots 的仓库检出目录中运行探测程序: + +```bash +python3 /workspace/dots-tailscale/home-persistence-probe.py +``` + +首次运行会在 `$HOME/.dots-home-persistence-marker.json` 中创建一个随机标记,并在 `/workspace/.dots-persistence-probe` 下创建基线、workspace 标记、最新结果和历史记录。后续运行会与该初始基线进行比较。探测程序只读取自身的标记和进程上下文,不读取身份验证状态或用户文档。 + +| `assessment` | 含义 | +| --- | --- | +| `baseline_created` | 已记录首次观察结果 | +| `markers_intact` | 两个标记的身份都与基线一致 | +| `home_marker_changed_workspace_intact` | HOME 标记发生变化或已消失,但 workspace 标记仍然完好 | +| `marker_continuity_changed` | 其他标记连续性发生变化 | + +标记丢失只能证明标记丢失,不能证明整个 HOME 目录已被重置。主机名、启动 ID 和 PID 1 的启动时刻可提供上下文;仅凭启动 ID 无法区分容器替换和主机重启。如果 workspace 基线也消失,下次运行会创建新基线,因此请在 VM 外部保留结果以便比较。 + +你可以通过 SSH 运行探测程序,并将结果保存在 VM 外的客户端以便比较: + +```bash +ssh agent@dots-sandbox 'python3 /workspace/dots-tailscale/home-persistence-probe.py' > observation.json +``` + +请根据自己的设置调整 SSH 密钥文件和主机别名。结果包含主机名和标记 ID,公开分享前请先匿名化。可使用 `--home` 和 `--probe-root` 指定自定义路径。此探测程序不会注册定期运行。 + + + +## 更新已安装脚本 + +本项目没有自动更新程序。请在 `/workspace/.dots-tailscale` 之外将 `tailscale-setup.sh` 和 `shim.py` 一起暂存并检查;请选择特定提交或发布版本,不要直接从会变化的分支就地更新已安装文件。记录该版本和这两个文件的 SHA-256 哈希。更改安装目录之前,请验证暂存 shell 脚本的语法,并解析暂存的 Python 源码。 + +更新到 v0.3.1 时,也要从该确切提交暂存 `dots-ts` 和 `dots_proxy.py`。下方的兼容性代码块只验证原有的 setup/shim 文件对,不会验证新增的辅助工具。另外,请在不执行的情况下编译这两个 Python 辅助工具,并记录全部四个源文件的哈希。部署和回滚时,请将 `dots-ts` 和 `dots_proxy.py` 与 `setup.sh`、`shim/shim.py` 一同放在安装根目录,权限设为 `700`。保留 `outbound-proxy.json` 和节点身份文件。不要在文档或更新过程中顺带应用代理配置或重启守护进程。 + +```bash +python3 -c 'from pathlib import Path; [compile(Path(p).read_text(), p, "exec") for p in ("dots-ts", "dots_proxy.py")]' +sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.py +``` + +使用独立的暂存检出目录 `/workspace/dots-tailscale`(如有需要可调整路径)。将 `` 替换为你检查过的完整 40 字符 SHA。无论当前位于分支还是分离的 HEAD,此代码块都可用于重复更新。它会拒绝处理已跟踪、已暂存或未跟踪的改动,且不会丢弃这些改动。fetch、修订版本、checkout 或验证失败都会停止子 shell;只有整个代码块成功后才能继续部署。它只会修改暂存检出目录,不会更改已安装文件或身份验证状态。代理设置仅对 fetch 生效;请保持证书验证开启。 + + +```bash +( + set -euo pipefail + cd /workspace/dots-tailscale + REVIEWED_COMMIT='' + [[ "$REVIEWED_COMMIT" =~ ^[0-9a-fA-F]{40}$ ]] || { + printf 'Stop: specify the full reviewed commit SHA.\n' >&2 + exit 1 + } + status=$(git status --porcelain --untracked-files=all) + [[ -z "$status" ]] || { + printf 'Stop: preserve and review local changes before updating.\n%s\n' "$status" >&2 + exit 1 + } + HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \ + git fetch --tags origin + target=$(git rev-parse --verify "${REVIEWED_COMMIT}^{commit}") + git switch --detach "$target" + [[ "$(git rev-parse HEAD)" = "$target" ]] + bash -n tailscale-setup.sh + python3 -c 'from pathlib import Path; compile(Path("shim.py").read_text(), "shim.py", "exec")' + git rev-parse HEAD + sha256sum tailscale-setup.sh shim.py +) +``` + + +shim 发生变化时,请按以下步骤部署: + +1. 打开云计算机平台的 GUI 终端,该终端必须独立于 Tailscale SSH。保留此终端以便恢复。将已安装的 `setup.sh` 和 `shim/shim.py` 哈希、节点地址及 SSH 公钥主机密钥指纹记录在 VM 外部。确认基线已通过身份验证,并将基线记录与候选版本分开保存。 +2. 仅保存旧版 `setup.sh` 和 `shim/shim.py` 源文件的私密回滚副本;对于 v0.3.1,还要包含现有的 `dots-ts` 和 `dots_proxy.py`,并记录它们在更新前是否不存在。保留身份验证状态、CA 密钥、SSH 主机密钥和授权密钥;不要将状态复制到另一个安装目录。在停止进程之前,准备好有时限的脱离终端重启方案和回滚方案。脱离终端执行的操作必须能在 SSH 会话断开后继续;本仓库没有为你实现此更新程序。 +3. 根据确切命令行和已安装脚本路径识别正在运行的 shim。从独立终端只停止已识别的 shim PID。不要按宽泛的进程名称终止进程。确认该进程已退出,且没有重复的 shim。将暂存的 `shim.py` 安装到 `shim/shim.py`,并将暂存的 `tailscale-setup.sh` 安装为 `setup.sh`,权限设为 `700`。对于 v0.3.1,还要将暂存的 `dots-ts` 和 `dots_proxy.py` 放在安装根目录,权限设为 `700`。 +4. 执行准备好的脱离终端启动操作,然后将已安装源文件的哈希与暂存文件的哈希进行比较。确认只有一个 shim,且守护进程符合预期。在独立终端检查 Tailscale CLI 状态和本地 SSH banner,然后通过外部连接检查 DERP 连通性,并使用严格主机密钥验证运行真实 SSH 命令。将结果记录在候选版本名下。 +5. 如果验证失败,请使用独立终端只停止已识别的候选 shim,恢复旧源文件,然后重新启动。重新检查旧版哈希和基线连接。回滚期间请保留现有状态和密钥;如发现主机密钥意外变化,应先调查,不要直接接受。 + +不要将 `install` 当作快捷更新方式,也不要删除安装目录。安装程序会有意拒绝冲突文件,而安装目录中包含身份材料。仅更新 setup 脚本也需要记录版本和哈希,并验证启动;这并不能证明已加载单独变更的 shim。 + +受控重启成功只能证明该环境中的进程恢复能力,不能证明 VM 替换后的持久性。连续性探测仅用于观察:等待环境自然变化,在外部保留结果,并单独评估已通过身份验证的连续性。连续性探测无需强制重置或重启。 + +## 文件存储位置与 SSH 权限 + +| 路径 | 内容 | +|---|---| +| `/workspace/.dots-tailscale/state` | Tailscale 身份验证状态 | +| `/workspace/.dots-tailscale/shim` | shim、CA、私钥和证书 | +| `/workspace/.dots-tailscale/sshd` | SSH 配置、主机密钥和 `authorized_keys` | +| `/tmp/dots-tailscale-$(id -u)` | 每用户的套接字、PID 和运行日志 | + +如果已认证安装的 state 文件丢失,脚本会停止,不会自动生成另一个设备身份。不要将同一个 Tailscale 节点的 state 复制到多个同时运行的 VM。`/workspace` 并非自动私密的位置;只能使用你能控制访问权限的目录。SSH 服务器使用 `StrictModes no`,因为 `/workspace` 上级目录的权限可能导致 OpenSSH 拒绝公钥身份验证。存储目录权限会限制为 `700`;SSH 密钥、`authorized_keys` 和 CA 私钥的权限会限制为 `600`。启动时只检查 Tailscale CLI 和本地 SSH banner;不会在每次启动时测试远程公钥登录。重启时,超过 1 MiB 的日志会轮换并只保留一代。进程持续运行期间,日志大小没有上限。 + +探测进程时即使无法访问 `/proc` 中的部分条目,脚本也会继续;如果 CLI 能够响应,则会复用现有的 tailscaled。对于无法识别的守护进程套接字,脚本不会自动删除。 + +## 保护生成的文件 + +不要将 Tailscale 状态、主机和客户端私钥、shim CA 私钥、身份验证密钥文件、生成的备份或运行时日志提交到 Git。不要将一个 Tailscale 节点的状态复制到多个同时运行的 VM。`/workspace` 并非自动私密的位置;只能使用你能控制访问权限的目录。 + + + +## 当前验证情况 + +![Dots 桌面终端中显示安装文件、状态和 Tailscale 版本的截图](assets/dots-tailscale-ssh-verification.png) + +这张由维护者提供的截图展示了 Dots 桌面终端中现有安装的文件和状态,以及 Tailscale 版本 `1.102.4`。截图并未展示新安装或 VM 替换。 + +```bash +bash /workspace/.dots-tailscale/start.sh check +/workspace/.dots-tailscale/tailscale version +``` + +- 维护者此前观察到安装连续运行超过 24 小时。环境替换后,`/workspace` 中的安装文件仍被保留,并通过手动运行 `start.sh` 成功重启。 +- 2026-10-14,修订版 shim 完成了实时 DERP ping,以及在严格主机密钥验证下的 SSH 公钥登录和命令检查。该修订版之后是否经历过 VM 替换并成功恢复,尚未得到观察。 +- `check` 会报告文件和本地状态。`running: Tailscale and local SSH ready` 表示 backend 为 Running,且本地 SSH banner 有响应;这并不能验证远程公钥登录。 +- Auth Key 注册和通过 Dots 自动化启动尚未经过测试。 + +## 离线回归检查 + +在仓库检出目录中运行 `python3 -m unittest discover -s tests -v` 和 `bash -n tailscale-setup.sh`。入门测试需要 Git、Bash、Python 3,以及 OpenSSH 客户端和服务器工具。测试会使用一次性仓库、密钥、`known_hosts`、模拟的 Tailscale CLI 和隔离的 Unix 套接字。SSH 通过本地 stdio 运行,不会开启监听器;这些测试不会向 tailnet 进行身份验证、测试实时代理,也不会验证 VM 替换。 + +## 故障排查 + +- `cannot inspect process`(旧版本)或 `process inspection failed`(当前版本):当前脚本会跳过无法访问的部分 `/proc` 条目。请按照上文的[更新步骤](#restart-update),用经过审查的代码更新已安装的 `setup.sh`。 +- `Permission denied (publickey)`:检查 `sshd/authorized_keys` 中公钥的指纹,并按需添加客户端公钥。`running` 消息并不能确认远程公钥身份验证成功。 +- `existing file differs`:已安装文件与源文件不一致。检查配置和密钥,然后只更新需要更新的文件。不要删除整个安装目录,其中还保存着 state 和主机密钥。 +- `socket exists but daemon cannot be identified`:检查现有守护进程和套接字。脚本不会自动删除无法识别的套接字。 +- 粘贴后出现 `^[[200~` 等终端控制文本:取消命令,然后逐行输入。继续前请检查命令内容和主机密钥指纹。