sshler 0.3.3__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sshler-0.3.3/sshler.egg-info → sshler-0.4.0}/PKG-INFO +103 -31
- {sshler-0.3.3 → sshler-0.4.0}/README.md +102 -30
- {sshler-0.3.3 → sshler-0.4.0}/pyproject.toml +1 -1
- {sshler-0.3.3 → sshler-0.4.0}/sshler/webapp.py +9 -4
- {sshler-0.3.3 → sshler-0.4.0/sshler.egg-info}/PKG-INFO +103 -31
- {sshler-0.3.3 → sshler-0.4.0}/tests/test_routes.py +19 -2
- {sshler-0.3.3 → sshler-0.4.0}/tests/test_websocket.py +48 -24
- {sshler-0.3.3 → sshler-0.4.0}/MANIFEST.in +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/setup.cfg +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/__init__.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/cli.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/config.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/scripts/install-sshler-task.ps1 +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/scripts/remove-sshler-task.ps1 +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/scripts/run-sshler.ps1 +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/ssh.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/ssh_config.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/state.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/base.js +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/favicon-terminal-local.svg +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/favicon-terminal.svg +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/favicon.svg +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/file-edit.js +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/file-view.js +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/style.css +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/static/term.js +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/base.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/box.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/docs.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/file_edit.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/file_view.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/index.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/new_box.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/partials/dir_listing.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler/templates/term.html +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler.egg-info/SOURCES.txt +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler.egg-info/dependency_links.txt +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler.egg-info/entry_points.txt +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler.egg-info/requires.txt +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/sshler.egg-info/top_level.txt +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/tests/test_basic.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/tests/test_config.py +0 -0
- {sshler-0.3.3 → sshler-0.4.0}/tests/test_ssh.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sshler
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A local FastAPI-powered SSH multiplexer for tmux-in-browser — from your laptop only.
|
|
5
5
|
Author: You
|
|
6
6
|
License-Expression: MIT
|
|
@@ -91,12 +91,10 @@ sshler serve
|
|
|
91
91
|
|
|
92
92
|
The app will open `http://127.0.0.1:8822` in your default browser.
|
|
93
93
|
|
|
94
|
-
## Configuration
|
|
94
|
+
## Configuration
|
|
95
95
|
|
|
96
96
|
sshler reads your existing OpenSSH config (`~/.ssh/config`) and shows every concrete `Host` entry automatically. Any favourites, default directories, or custom hosts you add through the UI are stored in a companion YAML file.
|
|
97
97
|
|
|
98
|
-
- **日本語:** OpenSSH の設定 (`~/.ssh/config`) を読み取り、すべての `Host` が自動的に一覧に表示されます。お気に入りやデフォルトディレクトリ、カスタムホストは付属の YAML に保存されます。
|
|
99
|
-
|
|
100
98
|
A config file is created on first run:
|
|
101
99
|
|
|
102
100
|
- Windows: `%APPDATA%\sshler\boxes.yaml`
|
|
@@ -119,27 +117,21 @@ boxes:
|
|
|
119
117
|
default_dir: /home/gabu
|
|
120
118
|
```
|
|
121
119
|
|
|
122
|
-
> Tip: Set `default_dir` if your home path isn
|
|
120
|
+
> Tip: Set `default_dir` if your home path isn't `/home/<user>`.
|
|
123
121
|
> If you rely on an OpenSSH alias, add `ssh_alias:` and sshler will run `ssh -G` to expand it when DNS fails.
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
### Resetting overrides / 上書き設定のリセット
|
|
128
|
-
|
|
129
|
-
Boxes imported from SSH config show a highlighted border and “Refresh” button. If you change something in `~/.ssh/config`, hit Refresh to drop any stored overrides (host/user/port/key) so the new settings take effect without editing `boxes.yaml`.
|
|
130
|
-
|
|
131
|
-
- **日本語:** SSH 設定から取り込まれたボックスは枠が強調表示され、「Refresh」ボタンで上書き設定を削除できます。`~/.ssh/config` を更新した際はボタンを押すだけで最新状態になります。
|
|
123
|
+
### Resetting overrides
|
|
132
124
|
|
|
133
|
-
|
|
125
|
+
Boxes imported from SSH config show a highlighted border and "Refresh" button. If you change something in `~/.ssh/config`, hit Refresh to drop any stored overrides (host/user/port/key) so the new settings take effect without editing `boxes.yaml`.
|
|
134
126
|
|
|
135
|
-
|
|
127
|
+
### Adding custom boxes
|
|
136
128
|
|
|
137
|
-
|
|
129
|
+
Hit "Add Box" in the UI to define a host that isn't in your SSH config (for example, a throwaway Docker container). Fields you leave blank fall back to your SSH defaults.
|
|
138
130
|
|
|
139
131
|
### Security model (important)
|
|
140
132
|
|
|
141
133
|
- sshler is designed for **single-user localhost** use. By default `sshler serve` binds to `127.0.0.1` and prints a random `X-SSHLER-TOKEN` that every state-changing request must send.
|
|
142
|
-
- File uploads are capped at 50
|
|
134
|
+
- File uploads are capped at 50 MB (tunable via `--max-upload-mb`). Uploaded content is never executed server-side.
|
|
143
135
|
- SSH connections still honour your system `known_hosts`. Only set `known_hosts: ignore` if you fully understand the risk.
|
|
144
136
|
- If you expose sshler beyond localhost, opt-in via `--allow-origin` and add `--auth user:pass` (basic auth). Use it only on networks you trust and put TLS in front (nginx, Caddy, etc.).
|
|
145
137
|
- There is no telemetry, analytics, or call-home behaviour.
|
|
@@ -168,8 +160,6 @@ sshler serve \
|
|
|
168
160
|
|
|
169
161
|
The server prints the token (and, if enabled, the basic auth username) on startup so you can copy it into API clients or browser extensions.
|
|
170
162
|
|
|
171
|
-
- **日本語:** サーバー起動時にトークン(および Basic 認証を有効にした場合はユーザー名)を表示するので、API クライアントやブラウザ拡張に貼り付けて利用できます。
|
|
172
|
-
|
|
173
163
|
### Terminal notifications
|
|
174
164
|
|
|
175
165
|
- Send a bell (`printf '\a'`) from tmux or your shell to flash the browser title and raise a desktop notification whenever the sshler tab is hidden.
|
|
@@ -177,19 +167,15 @@ The server prints the token (and, if enabled, the basic auth username) on startu
|
|
|
177
167
|
- JSON payloads are also supported: `printf '\033]777;notify={"title":"Codex","message":"All tasks finished"}\a'`.
|
|
178
168
|
- The first notification prompts the browser for permission. Denying it still leaves the in-app toast and title badge when you return to the tab.
|
|
179
169
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
## Autostart / 自動起動
|
|
170
|
+
## Autostart
|
|
183
171
|
|
|
184
172
|
### Windows (Task Scheduler)
|
|
185
173
|
|
|
186
174
|
1. Run `where sshler` to locate the installed executable (for example, `%LOCALAPPDATA%\Programs\Python\Python312\Scripts\sshler.exe`).
|
|
187
175
|
2. Open **Task Scheduler → Create Task…**.
|
|
188
|
-
3. Under **Triggers**, add
|
|
189
|
-
4. Under **Actions**, choose
|
|
190
|
-
5. Tick
|
|
191
|
-
|
|
192
|
-
- **日本語:** Task Scheduler で「ログオン時」をトリガーにし、`sshler.exe serve --no-browser` を実行するタスクを作成すると、サインイン時に自動起動します。
|
|
176
|
+
3. Under **Triggers**, add "At log on".
|
|
177
|
+
4. Under **Actions**, choose "Start a program" and point to the `sshler.exe` path. Add arguments such as `serve --no-browser` and set **Start in** to a writable directory.
|
|
178
|
+
5. Tick "Run with highest privileges" if you need WSL access, then save. sshler will now launch automatically every time you sign in.
|
|
193
179
|
|
|
194
180
|
### Linux / macOS (systemd user service)
|
|
195
181
|
|
|
@@ -216,9 +202,7 @@ systemctl --user daemon-reload
|
|
|
216
202
|
systemctl --user enable --now sshler.service
|
|
217
203
|
```
|
|
218
204
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
### Dependencies & licenses
|
|
205
|
+
## Dependencies & licenses
|
|
222
206
|
|
|
223
207
|
- FastAPI, uvicorn, asyncssh, platformdirs, yaml (PyPI packages, permissive licenses)
|
|
224
208
|
- HTMX (MIT) and xterm.js (MIT) are loaded from unpkg
|
|
@@ -226,8 +210,6 @@ systemctl --user enable --now sshler.service
|
|
|
226
210
|
|
|
227
211
|
All assets are used under their respective MIT/BSD-style licenses. sshler itself ships under the MIT license.
|
|
228
212
|
|
|
229
|
-
- **日本語:** 依存ライブラリはいずれも寛容なライセンス (MIT/BSD) で提供されています。sshler 本体も MIT ライセンスで配布されます。
|
|
230
|
-
|
|
231
213
|
## Why "sshler"?
|
|
232
214
|
|
|
233
215
|
Because sometimes you want less VS Code, more terminal — but still in a nice browser tab.
|
|
@@ -314,6 +296,96 @@ boxes:
|
|
|
314
296
|
|
|
315
297
|
> ヒント: ホームパスが `/home/<user>` でない場合は `default_dir` を設定してください。OpenSSH エイリアスを使用する場合は `ssh_alias:` を追加すると、DNS 失敗時に `ssh -G` で解決します。
|
|
316
298
|
|
|
299
|
+
### 上書き設定のリセット
|
|
300
|
+
|
|
301
|
+
SSH 設定から取り込まれたボックスは枠が強調表示され、「Refresh」ボタンで上書き設定を削除できます。`~/.ssh/config` を更新した際はボタンを押すだけで最新状態になります。
|
|
302
|
+
|
|
303
|
+
### カスタムボックスの追加
|
|
304
|
+
|
|
305
|
+
UI の "Add Box" から SSH 設定に存在しないホストも追加できます(例: 一時的な Docker コンテナ)。未入力の項目は SSH のデフォルト値が使われます。
|
|
306
|
+
|
|
307
|
+
### セキュリティモデル(重要)
|
|
308
|
+
|
|
309
|
+
- sshler は **シングルユーザー・ローカルホスト専用** に設計されています。デフォルトでは `sshler serve` は `127.0.0.1` にバインドし、すべての状態変更リクエストに必要なランダムな `X-SSHLER-TOKEN` を出力します。
|
|
310
|
+
- ファイルアップロードは 50 MB まで(`--max-upload-mb` で調整可能)。アップロードされたコンテンツはサーバー側で実行されません。
|
|
311
|
+
- SSH 接続はシステムの `known_hosts` を尊重します。リスクを完全に理解している場合のみ `known_hosts: ignore` を設定してください。
|
|
312
|
+
- ローカルホスト以外に公開する場合は、`--allow-origin` でオプトインし、`--auth user:pass`(Basic 認証)を追加してください。信頼できるネットワークでのみ使用し、TLS(nginx、Caddy など)を前段に配置してください。
|
|
313
|
+
- テレメトリ、アナリティクス、コールホーム機能は一切ありません。
|
|
314
|
+
|
|
315
|
+
### CLI オプション
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
sshler serve \
|
|
319
|
+
--host 127.0.0.1 \
|
|
320
|
+
--port 8822 \
|
|
321
|
+
--max-upload-mb 50 \
|
|
322
|
+
--allow-origin http://workstation:8822 \
|
|
323
|
+
--auth coder:supersecret \
|
|
324
|
+
--no-ssh-alias \
|
|
325
|
+
--log-level info
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- `--host`(別名 `--bind`): バインドアドレスを設定(デフォルト: `127.0.0.1` でローカルホストのみ)。すべてのインターフェースに公開するには `0.0.0.0` を使用しますが、**信頼できるネットワーク上でのみ `--auth` と TLS を併用してください**。
|
|
329
|
+
- `--port`: ポート番号を設定(デフォルト: `8822`)。
|
|
330
|
+
- `--allow-origin`: CORS を拡張するために繰り返し使用可能。ローカルホスト以外に UI を公開する場合は `--auth` と組み合わせてください。
|
|
331
|
+
- `--auth user:pass`: HTTP Basic 認証を有効化(`0.0.0.0` にバインドする場合は推奨)。
|
|
332
|
+
- `--max-upload-mb`: アップロードサイズ制限を設定(デフォルト: 50 MB)。
|
|
333
|
+
- `--no-ssh-alias`: DNS 失敗時の `ssh -G` フォールバックを無効化。
|
|
334
|
+
- `--token`: 独自の `X-SSHLER-TOKEN` を指定(指定しない場合は安全なランダム値が生成されます)。
|
|
335
|
+
- `--log-level`: uvicorn に直接渡されます(オプション: `critical`、`error`、`warning`、`info`、`debug`、`trace`)。
|
|
336
|
+
|
|
337
|
+
サーバーは起動時にトークン(および有効にした場合は Basic 認証のユーザー名)を出力するので、API クライアントやブラウザ拡張機能にコピーできます。
|
|
338
|
+
|
|
339
|
+
### ターミナル通知
|
|
340
|
+
|
|
341
|
+
- tmux またはシェルからベル(`printf '\a'`)を送信すると、sshler タブが非表示のときにブラウザタイトルが点滅し、デスクトップ通知が表示されます。
|
|
342
|
+
- より豊富なメッセージには OSC 777 を使用: `printf '\033]777;notify=Codex%20done|Check%20the%20output\a'`。`|` の前のテキストがタイトルになり、後半が本文になります。
|
|
343
|
+
- JSON ペイロードもサポート: `printf '\033]777;notify={"title":"Codex","message":"All tasks finished"}\a'`。
|
|
344
|
+
- 初回の通知はブラウザの許可を求めます。拒否した場合でも、タブに戻ったときにアプリ内トーストとタイトルバッジが表示されます。
|
|
345
|
+
|
|
346
|
+
## 自動起動
|
|
347
|
+
|
|
348
|
+
### Windows(タスク スケジューラ)
|
|
349
|
+
|
|
350
|
+
1. `where sshler` を実行してインストールされた実行可能ファイルを見つけます(例: `%LOCALAPPDATA%\Programs\Python\Python312\Scripts\sshler.exe`)。
|
|
351
|
+
2. **タスク スケジューラ → タスクの作成…** を開きます。
|
|
352
|
+
3. **トリガー** で「ログオン時」を追加。
|
|
353
|
+
4. **操作** で「プログラムの開始」を選択し、`sshler.exe` のパスを指定。引数に `serve --no-browser` を追加し、**開始** を書き込み可能なディレクトリに設定。
|
|
354
|
+
5. WSL アクセスが必要な場合は「最上位の特権で実行する」にチェックを入れて保存。サインインするたびに sshler が自動起動します。
|
|
355
|
+
|
|
356
|
+
### Linux / macOS(systemd ユーザーサービス)
|
|
357
|
+
|
|
358
|
+
`~/.config/systemd/user/sshler.service` を作成:
|
|
359
|
+
|
|
360
|
+
```ini
|
|
361
|
+
[Unit]
|
|
362
|
+
Description=sshler – local tmux bridge
|
|
363
|
+
After=network.target
|
|
364
|
+
|
|
365
|
+
[Service]
|
|
366
|
+
Type=simple
|
|
367
|
+
ExecStart=%h/.local/bin/sshler serve --bind 127.0.0.1 --no-browser
|
|
368
|
+
Restart=on-failure
|
|
369
|
+
|
|
370
|
+
[Install]
|
|
371
|
+
WantedBy=default.target
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
リロードして有効化:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
systemctl --user daemon-reload
|
|
378
|
+
systemctl --user enable --now sshler.service
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## 依存関係とライセンス
|
|
382
|
+
|
|
383
|
+
- FastAPI、uvicorn、asyncssh、platformdirs、yaml(PyPI パッケージ、寛容なライセンス)
|
|
384
|
+
- HTMX(MIT)と xterm.js(MIT)は unpkg から読み込まれます
|
|
385
|
+
- CodeMirror(MIT)がエディタを駆動
|
|
386
|
+
|
|
387
|
+
すべてのアセットはそれぞれの MIT/BSD スタイルのライセンスの下で使用されています。sshler 自体は MIT ライセンスで配布されます。
|
|
388
|
+
|
|
317
389
|
## 名前の由来
|
|
318
390
|
|
|
319
391
|
VS Code だけに頼らず、ブラウザタブの中で軽快にターミナルを扱いたい──そんな願いからこの名前になりました。
|
|
@@ -50,12 +50,10 @@ sshler serve
|
|
|
50
50
|
|
|
51
51
|
The app will open `http://127.0.0.1:8822` in your default browser.
|
|
52
52
|
|
|
53
|
-
## Configuration
|
|
53
|
+
## Configuration
|
|
54
54
|
|
|
55
55
|
sshler reads your existing OpenSSH config (`~/.ssh/config`) and shows every concrete `Host` entry automatically. Any favourites, default directories, or custom hosts you add through the UI are stored in a companion YAML file.
|
|
56
56
|
|
|
57
|
-
- **日本語:** OpenSSH の設定 (`~/.ssh/config`) を読み取り、すべての `Host` が自動的に一覧に表示されます。お気に入りやデフォルトディレクトリ、カスタムホストは付属の YAML に保存されます。
|
|
58
|
-
|
|
59
57
|
A config file is created on first run:
|
|
60
58
|
|
|
61
59
|
- Windows: `%APPDATA%\sshler\boxes.yaml`
|
|
@@ -78,27 +76,21 @@ boxes:
|
|
|
78
76
|
default_dir: /home/gabu
|
|
79
77
|
```
|
|
80
78
|
|
|
81
|
-
> Tip: Set `default_dir` if your home path isn
|
|
79
|
+
> Tip: Set `default_dir` if your home path isn't `/home/<user>`.
|
|
82
80
|
> If you rely on an OpenSSH alias, add `ssh_alias:` and sshler will run `ssh -G` to expand it when DNS fails.
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
### Resetting overrides / 上書き設定のリセット
|
|
87
|
-
|
|
88
|
-
Boxes imported from SSH config show a highlighted border and “Refresh” button. If you change something in `~/.ssh/config`, hit Refresh to drop any stored overrides (host/user/port/key) so the new settings take effect without editing `boxes.yaml`.
|
|
89
|
-
|
|
90
|
-
- **日本語:** SSH 設定から取り込まれたボックスは枠が強調表示され、「Refresh」ボタンで上書き設定を削除できます。`~/.ssh/config` を更新した際はボタンを押すだけで最新状態になります。
|
|
82
|
+
### Resetting overrides
|
|
91
83
|
|
|
92
|
-
|
|
84
|
+
Boxes imported from SSH config show a highlighted border and "Refresh" button. If you change something in `~/.ssh/config`, hit Refresh to drop any stored overrides (host/user/port/key) so the new settings take effect without editing `boxes.yaml`.
|
|
93
85
|
|
|
94
|
-
|
|
86
|
+
### Adding custom boxes
|
|
95
87
|
|
|
96
|
-
|
|
88
|
+
Hit "Add Box" in the UI to define a host that isn't in your SSH config (for example, a throwaway Docker container). Fields you leave blank fall back to your SSH defaults.
|
|
97
89
|
|
|
98
90
|
### Security model (important)
|
|
99
91
|
|
|
100
92
|
- sshler is designed for **single-user localhost** use. By default `sshler serve` binds to `127.0.0.1` and prints a random `X-SSHLER-TOKEN` that every state-changing request must send.
|
|
101
|
-
- File uploads are capped at 50
|
|
93
|
+
- File uploads are capped at 50 MB (tunable via `--max-upload-mb`). Uploaded content is never executed server-side.
|
|
102
94
|
- SSH connections still honour your system `known_hosts`. Only set `known_hosts: ignore` if you fully understand the risk.
|
|
103
95
|
- If you expose sshler beyond localhost, opt-in via `--allow-origin` and add `--auth user:pass` (basic auth). Use it only on networks you trust and put TLS in front (nginx, Caddy, etc.).
|
|
104
96
|
- There is no telemetry, analytics, or call-home behaviour.
|
|
@@ -127,8 +119,6 @@ sshler serve \
|
|
|
127
119
|
|
|
128
120
|
The server prints the token (and, if enabled, the basic auth username) on startup so you can copy it into API clients or browser extensions.
|
|
129
121
|
|
|
130
|
-
- **日本語:** サーバー起動時にトークン(および Basic 認証を有効にした場合はユーザー名)を表示するので、API クライアントやブラウザ拡張に貼り付けて利用できます。
|
|
131
|
-
|
|
132
122
|
### Terminal notifications
|
|
133
123
|
|
|
134
124
|
- Send a bell (`printf '\a'`) from tmux or your shell to flash the browser title and raise a desktop notification whenever the sshler tab is hidden.
|
|
@@ -136,19 +126,15 @@ The server prints the token (and, if enabled, the basic auth username) on startu
|
|
|
136
126
|
- JSON payloads are also supported: `printf '\033]777;notify={"title":"Codex","message":"All tasks finished"}\a'`.
|
|
137
127
|
- The first notification prompts the browser for permission. Denying it still leaves the in-app toast and title badge when you return to the tab.
|
|
138
128
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
## Autostart / 自動起動
|
|
129
|
+
## Autostart
|
|
142
130
|
|
|
143
131
|
### Windows (Task Scheduler)
|
|
144
132
|
|
|
145
133
|
1. Run `where sshler` to locate the installed executable (for example, `%LOCALAPPDATA%\Programs\Python\Python312\Scripts\sshler.exe`).
|
|
146
134
|
2. Open **Task Scheduler → Create Task…**.
|
|
147
|
-
3. Under **Triggers**, add
|
|
148
|
-
4. Under **Actions**, choose
|
|
149
|
-
5. Tick
|
|
150
|
-
|
|
151
|
-
- **日本語:** Task Scheduler で「ログオン時」をトリガーにし、`sshler.exe serve --no-browser` を実行するタスクを作成すると、サインイン時に自動起動します。
|
|
135
|
+
3. Under **Triggers**, add "At log on".
|
|
136
|
+
4. Under **Actions**, choose "Start a program" and point to the `sshler.exe` path. Add arguments such as `serve --no-browser` and set **Start in** to a writable directory.
|
|
137
|
+
5. Tick "Run with highest privileges" if you need WSL access, then save. sshler will now launch automatically every time you sign in.
|
|
152
138
|
|
|
153
139
|
### Linux / macOS (systemd user service)
|
|
154
140
|
|
|
@@ -175,9 +161,7 @@ systemctl --user daemon-reload
|
|
|
175
161
|
systemctl --user enable --now sshler.service
|
|
176
162
|
```
|
|
177
163
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
### Dependencies & licenses
|
|
164
|
+
## Dependencies & licenses
|
|
181
165
|
|
|
182
166
|
- FastAPI, uvicorn, asyncssh, platformdirs, yaml (PyPI packages, permissive licenses)
|
|
183
167
|
- HTMX (MIT) and xterm.js (MIT) are loaded from unpkg
|
|
@@ -185,8 +169,6 @@ systemctl --user enable --now sshler.service
|
|
|
185
169
|
|
|
186
170
|
All assets are used under their respective MIT/BSD-style licenses. sshler itself ships under the MIT license.
|
|
187
171
|
|
|
188
|
-
- **日本語:** 依存ライブラリはいずれも寛容なライセンス (MIT/BSD) で提供されています。sshler 本体も MIT ライセンスで配布されます。
|
|
189
|
-
|
|
190
172
|
## Why "sshler"?
|
|
191
173
|
|
|
192
174
|
Because sometimes you want less VS Code, more terminal — but still in a nice browser tab.
|
|
@@ -273,6 +255,96 @@ boxes:
|
|
|
273
255
|
|
|
274
256
|
> ヒント: ホームパスが `/home/<user>` でない場合は `default_dir` を設定してください。OpenSSH エイリアスを使用する場合は `ssh_alias:` を追加すると、DNS 失敗時に `ssh -G` で解決します。
|
|
275
257
|
|
|
258
|
+
### 上書き設定のリセット
|
|
259
|
+
|
|
260
|
+
SSH 設定から取り込まれたボックスは枠が強調表示され、「Refresh」ボタンで上書き設定を削除できます。`~/.ssh/config` を更新した際はボタンを押すだけで最新状態になります。
|
|
261
|
+
|
|
262
|
+
### カスタムボックスの追加
|
|
263
|
+
|
|
264
|
+
UI の "Add Box" から SSH 設定に存在しないホストも追加できます(例: 一時的な Docker コンテナ)。未入力の項目は SSH のデフォルト値が使われます。
|
|
265
|
+
|
|
266
|
+
### セキュリティモデル(重要)
|
|
267
|
+
|
|
268
|
+
- sshler は **シングルユーザー・ローカルホスト専用** に設計されています。デフォルトでは `sshler serve` は `127.0.0.1` にバインドし、すべての状態変更リクエストに必要なランダムな `X-SSHLER-TOKEN` を出力します。
|
|
269
|
+
- ファイルアップロードは 50 MB まで(`--max-upload-mb` で調整可能)。アップロードされたコンテンツはサーバー側で実行されません。
|
|
270
|
+
- SSH 接続はシステムの `known_hosts` を尊重します。リスクを完全に理解している場合のみ `known_hosts: ignore` を設定してください。
|
|
271
|
+
- ローカルホスト以外に公開する場合は、`--allow-origin` でオプトインし、`--auth user:pass`(Basic 認証)を追加してください。信頼できるネットワークでのみ使用し、TLS(nginx、Caddy など)を前段に配置してください。
|
|
272
|
+
- テレメトリ、アナリティクス、コールホーム機能は一切ありません。
|
|
273
|
+
|
|
274
|
+
### CLI オプション
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
sshler serve \
|
|
278
|
+
--host 127.0.0.1 \
|
|
279
|
+
--port 8822 \
|
|
280
|
+
--max-upload-mb 50 \
|
|
281
|
+
--allow-origin http://workstation:8822 \
|
|
282
|
+
--auth coder:supersecret \
|
|
283
|
+
--no-ssh-alias \
|
|
284
|
+
--log-level info
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- `--host`(別名 `--bind`): バインドアドレスを設定(デフォルト: `127.0.0.1` でローカルホストのみ)。すべてのインターフェースに公開するには `0.0.0.0` を使用しますが、**信頼できるネットワーク上でのみ `--auth` と TLS を併用してください**。
|
|
288
|
+
- `--port`: ポート番号を設定(デフォルト: `8822`)。
|
|
289
|
+
- `--allow-origin`: CORS を拡張するために繰り返し使用可能。ローカルホスト以外に UI を公開する場合は `--auth` と組み合わせてください。
|
|
290
|
+
- `--auth user:pass`: HTTP Basic 認証を有効化(`0.0.0.0` にバインドする場合は推奨)。
|
|
291
|
+
- `--max-upload-mb`: アップロードサイズ制限を設定(デフォルト: 50 MB)。
|
|
292
|
+
- `--no-ssh-alias`: DNS 失敗時の `ssh -G` フォールバックを無効化。
|
|
293
|
+
- `--token`: 独自の `X-SSHLER-TOKEN` を指定(指定しない場合は安全なランダム値が生成されます)。
|
|
294
|
+
- `--log-level`: uvicorn に直接渡されます(オプション: `critical`、`error`、`warning`、`info`、`debug`、`trace`)。
|
|
295
|
+
|
|
296
|
+
サーバーは起動時にトークン(および有効にした場合は Basic 認証のユーザー名)を出力するので、API クライアントやブラウザ拡張機能にコピーできます。
|
|
297
|
+
|
|
298
|
+
### ターミナル通知
|
|
299
|
+
|
|
300
|
+
- tmux またはシェルからベル(`printf '\a'`)を送信すると、sshler タブが非表示のときにブラウザタイトルが点滅し、デスクトップ通知が表示されます。
|
|
301
|
+
- より豊富なメッセージには OSC 777 を使用: `printf '\033]777;notify=Codex%20done|Check%20the%20output\a'`。`|` の前のテキストがタイトルになり、後半が本文になります。
|
|
302
|
+
- JSON ペイロードもサポート: `printf '\033]777;notify={"title":"Codex","message":"All tasks finished"}\a'`。
|
|
303
|
+
- 初回の通知はブラウザの許可を求めます。拒否した場合でも、タブに戻ったときにアプリ内トーストとタイトルバッジが表示されます。
|
|
304
|
+
|
|
305
|
+
## 自動起動
|
|
306
|
+
|
|
307
|
+
### Windows(タスク スケジューラ)
|
|
308
|
+
|
|
309
|
+
1. `where sshler` を実行してインストールされた実行可能ファイルを見つけます(例: `%LOCALAPPDATA%\Programs\Python\Python312\Scripts\sshler.exe`)。
|
|
310
|
+
2. **タスク スケジューラ → タスクの作成…** を開きます。
|
|
311
|
+
3. **トリガー** で「ログオン時」を追加。
|
|
312
|
+
4. **操作** で「プログラムの開始」を選択し、`sshler.exe` のパスを指定。引数に `serve --no-browser` を追加し、**開始** を書き込み可能なディレクトリに設定。
|
|
313
|
+
5. WSL アクセスが必要な場合は「最上位の特権で実行する」にチェックを入れて保存。サインインするたびに sshler が自動起動します。
|
|
314
|
+
|
|
315
|
+
### Linux / macOS(systemd ユーザーサービス)
|
|
316
|
+
|
|
317
|
+
`~/.config/systemd/user/sshler.service` を作成:
|
|
318
|
+
|
|
319
|
+
```ini
|
|
320
|
+
[Unit]
|
|
321
|
+
Description=sshler – local tmux bridge
|
|
322
|
+
After=network.target
|
|
323
|
+
|
|
324
|
+
[Service]
|
|
325
|
+
Type=simple
|
|
326
|
+
ExecStart=%h/.local/bin/sshler serve --bind 127.0.0.1 --no-browser
|
|
327
|
+
Restart=on-failure
|
|
328
|
+
|
|
329
|
+
[Install]
|
|
330
|
+
WantedBy=default.target
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
リロードして有効化:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
systemctl --user daemon-reload
|
|
337
|
+
systemctl --user enable --now sshler.service
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 依存関係とライセンス
|
|
341
|
+
|
|
342
|
+
- FastAPI、uvicorn、asyncssh、platformdirs、yaml(PyPI パッケージ、寛容なライセンス)
|
|
343
|
+
- HTMX(MIT)と xterm.js(MIT)は unpkg から読み込まれます
|
|
344
|
+
- CodeMirror(MIT)がエディタを駆動
|
|
345
|
+
|
|
346
|
+
すべてのアセットはそれぞれの MIT/BSD スタイルのライセンスの下で使用されています。sshler 自体は MIT ライセンスで配布されます。
|
|
347
|
+
|
|
276
348
|
## 名前の由来
|
|
277
349
|
|
|
278
350
|
VS Code だけに頼らず、ブラウザタブの中で軽快にターミナルを扱いたい──そんな願いからこの名前になりました。
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "sshler"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.4.0"
|
|
9
9
|
description = "A local FastAPI-powered SSH multiplexer for tmux-in-browser — from your laptop only."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.12"
|
|
@@ -12,7 +12,6 @@ from dataclasses import dataclass, field
|
|
|
12
12
|
from pathlib import Path, PurePosixPath
|
|
13
13
|
|
|
14
14
|
import asyncssh
|
|
15
|
-
from markdown_it import MarkdownIt
|
|
16
15
|
from fastapi import (
|
|
17
16
|
Depends,
|
|
18
17
|
FastAPI,
|
|
@@ -29,6 +28,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
29
28
|
from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse, Response
|
|
30
29
|
from fastapi.staticfiles import StaticFiles
|
|
31
30
|
from fastapi.templating import Jinja2Templates
|
|
31
|
+
from markdown_it import MarkdownIt
|
|
32
32
|
|
|
33
33
|
from . import __version__, state
|
|
34
34
|
from .config import (
|
|
@@ -265,7 +265,8 @@ async def _open_local_tmux(
|
|
|
265
265
|
# For now, let's try using script to provide a PTY
|
|
266
266
|
if LOCAL_IS_WINDOWS:
|
|
267
267
|
# Use 'script' command in WSL to create a PTY
|
|
268
|
-
|
|
268
|
+
cmd_str = " ".join(f"'{arg}'" if " " in arg else arg for arg in command[2:])
|
|
269
|
+
script_command = ["wsl", "--", "script", "-qfc", cmd_str, "/dev/null"]
|
|
269
270
|
return await asyncio.create_subprocess_exec(
|
|
270
271
|
*script_command,
|
|
271
272
|
stdin=asyncio.subprocess.PIPE,
|
|
@@ -1650,7 +1651,10 @@ def make_app(settings: ServerSettings | None = None) -> FastAPI:
|
|
|
1650
1651
|
normalized_directory = _normalize_local_path(box.default_dir)
|
|
1651
1652
|
|
|
1652
1653
|
# Debug: Log the command we're about to run
|
|
1653
|
-
logger.info(
|
|
1654
|
+
logger.info(
|
|
1655
|
+
f"Starting local tmux: transport={transport}, "
|
|
1656
|
+
f"dir={normalized_directory}, session={session}"
|
|
1657
|
+
)
|
|
1654
1658
|
|
|
1655
1659
|
try:
|
|
1656
1660
|
process = await _open_local_tmux(normalized_directory, session)
|
|
@@ -1730,7 +1734,8 @@ def make_app(settings: ServerSettings | None = None) -> FastAPI:
|
|
|
1730
1734
|
transport,
|
|
1731
1735
|
)
|
|
1732
1736
|
elif "bytes" in message and message["bytes"] is not None:
|
|
1733
|
-
|
|
1737
|
+
num_bytes = len(message["bytes"])
|
|
1738
|
+
logger.debug(f"Writer: got {num_bytes} bytes, writing to stdin")
|
|
1734
1739
|
process.stdin.write(message["bytes"])
|
|
1735
1740
|
except WebSocketDisconnect:
|
|
1736
1741
|
logger.info("Writer: websocket disconnected")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sshler
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A local FastAPI-powered SSH multiplexer for tmux-in-browser — from your laptop only.
|
|
5
5
|
Author: You
|
|
6
6
|
License-Expression: MIT
|
|
@@ -91,12 +91,10 @@ sshler serve
|
|
|
91
91
|
|
|
92
92
|
The app will open `http://127.0.0.1:8822` in your default browser.
|
|
93
93
|
|
|
94
|
-
## Configuration
|
|
94
|
+
## Configuration
|
|
95
95
|
|
|
96
96
|
sshler reads your existing OpenSSH config (`~/.ssh/config`) and shows every concrete `Host` entry automatically. Any favourites, default directories, or custom hosts you add through the UI are stored in a companion YAML file.
|
|
97
97
|
|
|
98
|
-
- **日本語:** OpenSSH の設定 (`~/.ssh/config`) を読み取り、すべての `Host` が自動的に一覧に表示されます。お気に入りやデフォルトディレクトリ、カスタムホストは付属の YAML に保存されます。
|
|
99
|
-
|
|
100
98
|
A config file is created on first run:
|
|
101
99
|
|
|
102
100
|
- Windows: `%APPDATA%\sshler\boxes.yaml`
|
|
@@ -119,27 +117,21 @@ boxes:
|
|
|
119
117
|
default_dir: /home/gabu
|
|
120
118
|
```
|
|
121
119
|
|
|
122
|
-
> Tip: Set `default_dir` if your home path isn
|
|
120
|
+
> Tip: Set `default_dir` if your home path isn't `/home/<user>`.
|
|
123
121
|
> If you rely on an OpenSSH alias, add `ssh_alias:` and sshler will run `ssh -G` to expand it when DNS fails.
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
### Resetting overrides / 上書き設定のリセット
|
|
128
|
-
|
|
129
|
-
Boxes imported from SSH config show a highlighted border and “Refresh” button. If you change something in `~/.ssh/config`, hit Refresh to drop any stored overrides (host/user/port/key) so the new settings take effect without editing `boxes.yaml`.
|
|
130
|
-
|
|
131
|
-
- **日本語:** SSH 設定から取り込まれたボックスは枠が強調表示され、「Refresh」ボタンで上書き設定を削除できます。`~/.ssh/config` を更新した際はボタンを押すだけで最新状態になります。
|
|
123
|
+
### Resetting overrides
|
|
132
124
|
|
|
133
|
-
|
|
125
|
+
Boxes imported from SSH config show a highlighted border and "Refresh" button. If you change something in `~/.ssh/config`, hit Refresh to drop any stored overrides (host/user/port/key) so the new settings take effect without editing `boxes.yaml`.
|
|
134
126
|
|
|
135
|
-
|
|
127
|
+
### Adding custom boxes
|
|
136
128
|
|
|
137
|
-
|
|
129
|
+
Hit "Add Box" in the UI to define a host that isn't in your SSH config (for example, a throwaway Docker container). Fields you leave blank fall back to your SSH defaults.
|
|
138
130
|
|
|
139
131
|
### Security model (important)
|
|
140
132
|
|
|
141
133
|
- sshler is designed for **single-user localhost** use. By default `sshler serve` binds to `127.0.0.1` and prints a random `X-SSHLER-TOKEN` that every state-changing request must send.
|
|
142
|
-
- File uploads are capped at 50
|
|
134
|
+
- File uploads are capped at 50 MB (tunable via `--max-upload-mb`). Uploaded content is never executed server-side.
|
|
143
135
|
- SSH connections still honour your system `known_hosts`. Only set `known_hosts: ignore` if you fully understand the risk.
|
|
144
136
|
- If you expose sshler beyond localhost, opt-in via `--allow-origin` and add `--auth user:pass` (basic auth). Use it only on networks you trust and put TLS in front (nginx, Caddy, etc.).
|
|
145
137
|
- There is no telemetry, analytics, or call-home behaviour.
|
|
@@ -168,8 +160,6 @@ sshler serve \
|
|
|
168
160
|
|
|
169
161
|
The server prints the token (and, if enabled, the basic auth username) on startup so you can copy it into API clients or browser extensions.
|
|
170
162
|
|
|
171
|
-
- **日本語:** サーバー起動時にトークン(および Basic 認証を有効にした場合はユーザー名)を表示するので、API クライアントやブラウザ拡張に貼り付けて利用できます。
|
|
172
|
-
|
|
173
163
|
### Terminal notifications
|
|
174
164
|
|
|
175
165
|
- Send a bell (`printf '\a'`) from tmux or your shell to flash the browser title and raise a desktop notification whenever the sshler tab is hidden.
|
|
@@ -177,19 +167,15 @@ The server prints the token (and, if enabled, the basic auth username) on startu
|
|
|
177
167
|
- JSON payloads are also supported: `printf '\033]777;notify={"title":"Codex","message":"All tasks finished"}\a'`.
|
|
178
168
|
- The first notification prompts the browser for permission. Denying it still leaves the in-app toast and title badge when you return to the tab.
|
|
179
169
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
## Autostart / 自動起動
|
|
170
|
+
## Autostart
|
|
183
171
|
|
|
184
172
|
### Windows (Task Scheduler)
|
|
185
173
|
|
|
186
174
|
1. Run `where sshler` to locate the installed executable (for example, `%LOCALAPPDATA%\Programs\Python\Python312\Scripts\sshler.exe`).
|
|
187
175
|
2. Open **Task Scheduler → Create Task…**.
|
|
188
|
-
3. Under **Triggers**, add
|
|
189
|
-
4. Under **Actions**, choose
|
|
190
|
-
5. Tick
|
|
191
|
-
|
|
192
|
-
- **日本語:** Task Scheduler で「ログオン時」をトリガーにし、`sshler.exe serve --no-browser` を実行するタスクを作成すると、サインイン時に自動起動します。
|
|
176
|
+
3. Under **Triggers**, add "At log on".
|
|
177
|
+
4. Under **Actions**, choose "Start a program" and point to the `sshler.exe` path. Add arguments such as `serve --no-browser` and set **Start in** to a writable directory.
|
|
178
|
+
5. Tick "Run with highest privileges" if you need WSL access, then save. sshler will now launch automatically every time you sign in.
|
|
193
179
|
|
|
194
180
|
### Linux / macOS (systemd user service)
|
|
195
181
|
|
|
@@ -216,9 +202,7 @@ systemctl --user daemon-reload
|
|
|
216
202
|
systemctl --user enable --now sshler.service
|
|
217
203
|
```
|
|
218
204
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
### Dependencies & licenses
|
|
205
|
+
## Dependencies & licenses
|
|
222
206
|
|
|
223
207
|
- FastAPI, uvicorn, asyncssh, platformdirs, yaml (PyPI packages, permissive licenses)
|
|
224
208
|
- HTMX (MIT) and xterm.js (MIT) are loaded from unpkg
|
|
@@ -226,8 +210,6 @@ systemctl --user enable --now sshler.service
|
|
|
226
210
|
|
|
227
211
|
All assets are used under their respective MIT/BSD-style licenses. sshler itself ships under the MIT license.
|
|
228
212
|
|
|
229
|
-
- **日本語:** 依存ライブラリはいずれも寛容なライセンス (MIT/BSD) で提供されています。sshler 本体も MIT ライセンスで配布されます。
|
|
230
|
-
|
|
231
213
|
## Why "sshler"?
|
|
232
214
|
|
|
233
215
|
Because sometimes you want less VS Code, more terminal — but still in a nice browser tab.
|
|
@@ -314,6 +296,96 @@ boxes:
|
|
|
314
296
|
|
|
315
297
|
> ヒント: ホームパスが `/home/<user>` でない場合は `default_dir` を設定してください。OpenSSH エイリアスを使用する場合は `ssh_alias:` を追加すると、DNS 失敗時に `ssh -G` で解決します。
|
|
316
298
|
|
|
299
|
+
### 上書き設定のリセット
|
|
300
|
+
|
|
301
|
+
SSH 設定から取り込まれたボックスは枠が強調表示され、「Refresh」ボタンで上書き設定を削除できます。`~/.ssh/config` を更新した際はボタンを押すだけで最新状態になります。
|
|
302
|
+
|
|
303
|
+
### カスタムボックスの追加
|
|
304
|
+
|
|
305
|
+
UI の "Add Box" から SSH 設定に存在しないホストも追加できます(例: 一時的な Docker コンテナ)。未入力の項目は SSH のデフォルト値が使われます。
|
|
306
|
+
|
|
307
|
+
### セキュリティモデル(重要)
|
|
308
|
+
|
|
309
|
+
- sshler は **シングルユーザー・ローカルホスト専用** に設計されています。デフォルトでは `sshler serve` は `127.0.0.1` にバインドし、すべての状態変更リクエストに必要なランダムな `X-SSHLER-TOKEN` を出力します。
|
|
310
|
+
- ファイルアップロードは 50 MB まで(`--max-upload-mb` で調整可能)。アップロードされたコンテンツはサーバー側で実行されません。
|
|
311
|
+
- SSH 接続はシステムの `known_hosts` を尊重します。リスクを完全に理解している場合のみ `known_hosts: ignore` を設定してください。
|
|
312
|
+
- ローカルホスト以外に公開する場合は、`--allow-origin` でオプトインし、`--auth user:pass`(Basic 認証)を追加してください。信頼できるネットワークでのみ使用し、TLS(nginx、Caddy など)を前段に配置してください。
|
|
313
|
+
- テレメトリ、アナリティクス、コールホーム機能は一切ありません。
|
|
314
|
+
|
|
315
|
+
### CLI オプション
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
sshler serve \
|
|
319
|
+
--host 127.0.0.1 \
|
|
320
|
+
--port 8822 \
|
|
321
|
+
--max-upload-mb 50 \
|
|
322
|
+
--allow-origin http://workstation:8822 \
|
|
323
|
+
--auth coder:supersecret \
|
|
324
|
+
--no-ssh-alias \
|
|
325
|
+
--log-level info
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- `--host`(別名 `--bind`): バインドアドレスを設定(デフォルト: `127.0.0.1` でローカルホストのみ)。すべてのインターフェースに公開するには `0.0.0.0` を使用しますが、**信頼できるネットワーク上でのみ `--auth` と TLS を併用してください**。
|
|
329
|
+
- `--port`: ポート番号を設定(デフォルト: `8822`)。
|
|
330
|
+
- `--allow-origin`: CORS を拡張するために繰り返し使用可能。ローカルホスト以外に UI を公開する場合は `--auth` と組み合わせてください。
|
|
331
|
+
- `--auth user:pass`: HTTP Basic 認証を有効化(`0.0.0.0` にバインドする場合は推奨)。
|
|
332
|
+
- `--max-upload-mb`: アップロードサイズ制限を設定(デフォルト: 50 MB)。
|
|
333
|
+
- `--no-ssh-alias`: DNS 失敗時の `ssh -G` フォールバックを無効化。
|
|
334
|
+
- `--token`: 独自の `X-SSHLER-TOKEN` を指定(指定しない場合は安全なランダム値が生成されます)。
|
|
335
|
+
- `--log-level`: uvicorn に直接渡されます(オプション: `critical`、`error`、`warning`、`info`、`debug`、`trace`)。
|
|
336
|
+
|
|
337
|
+
サーバーは起動時にトークン(および有効にした場合は Basic 認証のユーザー名)を出力するので、API クライアントやブラウザ拡張機能にコピーできます。
|
|
338
|
+
|
|
339
|
+
### ターミナル通知
|
|
340
|
+
|
|
341
|
+
- tmux またはシェルからベル(`printf '\a'`)を送信すると、sshler タブが非表示のときにブラウザタイトルが点滅し、デスクトップ通知が表示されます。
|
|
342
|
+
- より豊富なメッセージには OSC 777 を使用: `printf '\033]777;notify=Codex%20done|Check%20the%20output\a'`。`|` の前のテキストがタイトルになり、後半が本文になります。
|
|
343
|
+
- JSON ペイロードもサポート: `printf '\033]777;notify={"title":"Codex","message":"All tasks finished"}\a'`。
|
|
344
|
+
- 初回の通知はブラウザの許可を求めます。拒否した場合でも、タブに戻ったときにアプリ内トーストとタイトルバッジが表示されます。
|
|
345
|
+
|
|
346
|
+
## 自動起動
|
|
347
|
+
|
|
348
|
+
### Windows(タスク スケジューラ)
|
|
349
|
+
|
|
350
|
+
1. `where sshler` を実行してインストールされた実行可能ファイルを見つけます(例: `%LOCALAPPDATA%\Programs\Python\Python312\Scripts\sshler.exe`)。
|
|
351
|
+
2. **タスク スケジューラ → タスクの作成…** を開きます。
|
|
352
|
+
3. **トリガー** で「ログオン時」を追加。
|
|
353
|
+
4. **操作** で「プログラムの開始」を選択し、`sshler.exe` のパスを指定。引数に `serve --no-browser` を追加し、**開始** を書き込み可能なディレクトリに設定。
|
|
354
|
+
5. WSL アクセスが必要な場合は「最上位の特権で実行する」にチェックを入れて保存。サインインするたびに sshler が自動起動します。
|
|
355
|
+
|
|
356
|
+
### Linux / macOS(systemd ユーザーサービス)
|
|
357
|
+
|
|
358
|
+
`~/.config/systemd/user/sshler.service` を作成:
|
|
359
|
+
|
|
360
|
+
```ini
|
|
361
|
+
[Unit]
|
|
362
|
+
Description=sshler – local tmux bridge
|
|
363
|
+
After=network.target
|
|
364
|
+
|
|
365
|
+
[Service]
|
|
366
|
+
Type=simple
|
|
367
|
+
ExecStart=%h/.local/bin/sshler serve --bind 127.0.0.1 --no-browser
|
|
368
|
+
Restart=on-failure
|
|
369
|
+
|
|
370
|
+
[Install]
|
|
371
|
+
WantedBy=default.target
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
リロードして有効化:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
systemctl --user daemon-reload
|
|
378
|
+
systemctl --user enable --now sshler.service
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## 依存関係とライセンス
|
|
382
|
+
|
|
383
|
+
- FastAPI、uvicorn、asyncssh、platformdirs、yaml(PyPI パッケージ、寛容なライセンス)
|
|
384
|
+
- HTMX(MIT)と xterm.js(MIT)は unpkg から読み込まれます
|
|
385
|
+
- CodeMirror(MIT)がエディタを駆動
|
|
386
|
+
|
|
387
|
+
すべてのアセットはそれぞれの MIT/BSD スタイルのライセンスの下で使用されています。sshler 自体は MIT ライセンスで配布されます。
|
|
388
|
+
|
|
317
389
|
## 名前の由来
|
|
318
390
|
|
|
319
391
|
VS Code だけに頼らず、ブラウザタブの中で軽快にターミナルを扱いたい──そんな願いからこの名前になりました。
|
|
@@ -20,8 +20,25 @@ def build_client() -> TestClient:
|
|
|
20
20
|
return TestClient(make_app(ServerSettings(csrf_token=TEST_TOKEN)))
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def test_directory_listing_returns_error_message(monkeypatch):
|
|
24
|
-
|
|
23
|
+
def test_directory_listing_returns_error_message(monkeypatch, tmp_path):
|
|
24
|
+
config_dir = tmp_path / "config"
|
|
25
|
+
config_dir.mkdir()
|
|
26
|
+
monkeypatch.setenv("SSHLER_CONFIG_DIR", str(config_dir))
|
|
27
|
+
(config_dir / "boxes.yaml").write_text(
|
|
28
|
+
yaml.safe_dump({"boxes": []}, sort_keys=False), encoding="utf-8"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
ssh_config = tmp_path / "ssh_config"
|
|
32
|
+
ssh_config.write_text(
|
|
33
|
+
"""
|
|
34
|
+
Host gabu-server
|
|
35
|
+
HostName gabu.example.com
|
|
36
|
+
User gabu
|
|
37
|
+
""".strip(),
|
|
38
|
+
encoding="utf-8",
|
|
39
|
+
)
|
|
40
|
+
monkeypatch.setenv("SSHLER_SSH_CONFIG", str(ssh_config))
|
|
41
|
+
|
|
25
42
|
client = build_client()
|
|
26
43
|
try:
|
|
27
44
|
|
|
@@ -63,37 +63,61 @@ def configured_app_fixture() -> TestClient:
|
|
|
63
63
|
client.close()
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def test_websocket_falls_back_to_default_directory(monkeypatch,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
def test_websocket_falls_back_to_default_directory(monkeypatch, tmp_path):
|
|
67
|
+
import yaml
|
|
68
|
+
|
|
69
|
+
config_dir = tmp_path / "config"
|
|
70
|
+
config_dir.mkdir()
|
|
71
|
+
monkeypatch.setenv("SSHLER_CONFIG_DIR", str(config_dir))
|
|
72
|
+
(config_dir / "boxes.yaml").write_text(
|
|
73
|
+
yaml.safe_dump({"boxes": []}, sort_keys=False), encoding="utf-8"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
ssh_config = tmp_path / "ssh_config"
|
|
77
|
+
ssh_config.write_text(
|
|
78
|
+
"""
|
|
79
|
+
Host demo-box
|
|
80
|
+
HostName demo.internal
|
|
81
|
+
User demo
|
|
82
|
+
""".strip(),
|
|
83
|
+
encoding="utf-8",
|
|
84
|
+
)
|
|
85
|
+
monkeypatch.setenv("SSHLER_SSH_CONFIG", str(ssh_config))
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
client = TestClient(make_app(ServerSettings(csrf_token=TEST_TOKEN)))
|
|
88
|
+
try:
|
|
89
|
+
config = load_config()
|
|
90
|
+
box = next(b for b in config.boxes if b.name != "local")
|
|
91
|
+
fallback_directory = box.default_dir or f"/home/{box.user}"
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
fake_process = FakeProcess()
|
|
94
|
+
captured: dict[str, object] = {}
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
async def fake_connect(*_args, **_kwargs):
|
|
97
|
+
return FakeConnection()
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return fake_process
|
|
99
|
+
async def fake_sftp_is_directory(_connection, _path):
|
|
100
|
+
return False
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
async def fake_open_tmux(*_args, **kwargs):
|
|
103
|
+
captured["working_directory"] = kwargs["working_directory"]
|
|
104
|
+
return fake_process
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
monkeypatch.setattr("sshler.webapp.connect", fake_connect)
|
|
107
|
+
monkeypatch.setattr("sshler.webapp.sftp_is_directory", fake_sftp_is_directory)
|
|
108
|
+
monkeypatch.setattr("sshler.webapp.open_tmux", fake_open_tmux)
|
|
109
|
+
|
|
110
|
+
with client.websocket_connect(
|
|
111
|
+
f"/ws/term?host={box.name}&dir=/does-not-exist&session=check&token={TEST_TOKEN}"
|
|
112
|
+
) as websocket:
|
|
113
|
+
websocket.send_bytes(b"hello")
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
assert captured["working_directory"] == fallback_directory
|
|
116
|
+
assert fake_process.stdin.messages == [b"hello"]
|
|
117
|
+
assert fake_process.stdin.eof_called is True
|
|
118
|
+
assert fake_process.closed is True
|
|
119
|
+
finally:
|
|
120
|
+
client.close()
|
|
97
121
|
|
|
98
122
|
|
|
99
123
|
def test_local_box_connects_successfully(monkeypatch, configured_app: TestClient):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|