termainer 0.4.0__tar.gz → 0.5.1__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.
- termainer-0.5.1/MANIFEST.in +1 -0
- {termainer-0.4.0 → termainer-0.5.1}/PKG-INFO +108 -70
- {termainer-0.4.0 → termainer-0.5.1}/README.md +107 -69
- {termainer-0.4.0 → termainer-0.5.1}/pyproject.toml +7 -2
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/app.py +103 -2
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/config.py +6 -4
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/locale.py +94 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/docker.py +85 -55
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/kubernetes.py +57 -26
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/openshift.py +33 -12
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/podman.py +54 -24
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/swarm.py +42 -25
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/remote/ssh.py +30 -13
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/ssh_config.py +2 -0
- termainer-0.5.1/src/termainer/storage.py +110 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/ui/dashboard.py +208 -65
- termainer-0.5.1/src/termainer/ui/environment.py +770 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/ui/home.py +8 -3
- termainer-0.5.1/src/termainer/ui/logo.png +0 -0
- termainer-0.5.1/src/termainer/ui/styles.tcss +1239 -0
- termainer-0.5.1/src/termainer/version.py +1 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer.egg-info/PKG-INFO +108 -70
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer.egg-info/SOURCES.txt +4 -0
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_app.py +13 -4
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_providers.py +4 -1
- termainer-0.4.0/src/termainer/ui/environment.py +0 -300
- termainer-0.4.0/src/termainer/version.py +0 -1
- {termainer-0.4.0 → termainer-0.5.1}/setup.cfg +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/__init__.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/config_manager.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/__init__.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/providers/base.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/remote/__init__.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/server_manager.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/ui/__init__.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/ui/splash.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/ui/widgets.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/utils/__init__.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer/utils/helpers.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer.egg-info/dependency_links.txt +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer.egg-info/entry_points.txt +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer.egg-info/requires.txt +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/src/termainer.egg-info/top_level.txt +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_config.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_config_manager.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_server_manager.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_ssh_config.py +0 -0
- {termainer-0.4.0 → termainer-0.5.1}/tests/test_ui.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include src/termainer/ui *.tcss *.png
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termainer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Container observability and operations directly from your terminal
|
|
5
5
|
Author: Alan Stefanov
|
|
6
6
|
License: MIT
|
|
@@ -31,6 +31,7 @@ Building scalable platforms, leading teams, and optimizing cloud operations.
|
|
|
31
31
|
[](https://www.linkedin.com/in/alanstefanov/)
|
|
32
32
|
[](mailto:alan.emanuel.stefanov@gmail.com)
|
|
33
33
|
[](https://github.com/AlanStefanov)
|
|
34
|
+
[](https://astefanov.com)
|
|
34
35
|
|
|
35
36
|
</div>
|
|
36
37
|
|
|
@@ -159,9 +160,84 @@ Depending on how you installed Termainer:
|
|
|
159
160
|
|
|
160
161
|
---
|
|
161
162
|
|
|
163
|
+
## Quick Start
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Just run it — Termainer auto-detects local Docker, Podman, kubectl, or oc
|
|
167
|
+
termainer
|
|
168
|
+
|
|
169
|
+
# If no local runtime is found, configure remote servers via ~/.ssh/config
|
|
170
|
+
# (see below) and restart, or click any technology card to probe SSH servers
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Termainer works **with zero configuration** for local environments. For remote servers, just add entries to `~/.ssh/config` — no config file needed.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
162
177
|
## Configuration
|
|
163
178
|
|
|
164
|
-
|
|
179
|
+
### SSH Config Auto-Discovery (Recommended)
|
|
180
|
+
|
|
181
|
+
Termainer automatically discovers servers from your **`~/.ssh/config`** file. This is the **recommended approach** — no YAML or `.env` file needed.
|
|
182
|
+
|
|
183
|
+
Simply define your remote servers in `~/.ssh/config`:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Host prod-web
|
|
187
|
+
HostName ec2-54-123-45-67.us-east-1.compute.amazonaws.com
|
|
188
|
+
User ubuntu
|
|
189
|
+
IdentityFile ~/.ssh/production.pem
|
|
190
|
+
|
|
191
|
+
Host staging-k8s
|
|
192
|
+
HostName k8s-staging.example.com
|
|
193
|
+
User admin
|
|
194
|
+
IdentityFile ~/.ssh/staging-key
|
|
195
|
+
|
|
196
|
+
Host dev-docker
|
|
197
|
+
HostName 192.168.1.100
|
|
198
|
+
User devuser
|
|
199
|
+
IdentityFile ~/.ssh/dev-key
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Then launch Termainer — all SSH entries appear in the server dropdown automatically:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
termainer
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
When you select a technology card (e.g. "Kubernetes"), Termainer shows a **server selection dialog** with checkboxes for all configured SSH servers. Selected servers are probed **in parallel** for the chosen provider, and successful connections open the dashboard. Server preferences are cached in `~/.config/termainer/provider_servers.json`.
|
|
209
|
+
|
|
210
|
+
You can also **add and remove servers directly from the UI** via the "Manage servers" button. Custom servers are persisted in `~/.config/termainer/servers.json`.
|
|
211
|
+
|
|
212
|
+
> **⚠️ Docker/Swarm note:** For Docker and Swarm over SSH, Termainer uses SSH port
|
|
213
|
+
> forwarding to tunnel the remote Docker socket to a local Unix socket. The remote
|
|
214
|
+
> user must have access to the Docker daemon (be in the `docker` group) without `sudo`.
|
|
215
|
+
> Kubernetes, Podman, and OpenShift commands run directly over SSH, no tunnel needed.
|
|
216
|
+
|
|
217
|
+
### Dashboard server switching
|
|
218
|
+
|
|
219
|
+
Within a dashboard, switching servers only probes the **current provider** on the target server (no multi-probe). If the server doesn't have that provider, a notification is shown and the dashboard stays on the current server.
|
|
220
|
+
|
|
221
|
+
#### SSH Authentication Methods
|
|
222
|
+
|
|
223
|
+
| Method | How to use |
|
|
224
|
+
|---|---|
|
|
225
|
+
| **Key (`.pem`)** | Set `IdentityFile` in `~/.ssh/config` |
|
|
226
|
+
| **Password** | `--ssh-password 'mypass'` or `TERMAINER_REMOTE_PASSWORD` in `.env` (requires `sshpass`) |
|
|
227
|
+
|
|
228
|
+
For password-based auth, install `sshpass`:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Debian/Ubuntu
|
|
232
|
+
sudo apt install sshpass
|
|
233
|
+
|
|
234
|
+
# RHEL/CentOS/Fedora
|
|
235
|
+
sudo yum install sshpass
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Multi-Server via config.yaml (Advanced)
|
|
239
|
+
|
|
240
|
+
For advanced setups, create `~/.config/termainer/config.yaml`:
|
|
165
241
|
|
|
166
242
|
```yaml
|
|
167
243
|
lang: en
|
|
@@ -186,9 +262,9 @@ servers:
|
|
|
186
262
|
|
|
187
263
|
See the full [Configuration Reference](guide/configuration-reference.md) for all options.
|
|
188
264
|
|
|
189
|
-
###
|
|
265
|
+
### Single-server via .env
|
|
190
266
|
|
|
191
|
-
For a single remote server,
|
|
267
|
+
For a quick single remote server, use `.env` or CLI flags:
|
|
192
268
|
|
|
193
269
|
```bash
|
|
194
270
|
termainer --host ec2-54-123-45-67.us-east-1.compute.amazonaws.com \
|
|
@@ -223,93 +299,41 @@ termainer --provider openshift
|
|
|
223
299
|
|
|
224
300
|
### Remote (SSH)
|
|
225
301
|
|
|
226
|
-
Connect to remote servers running Docker or Kubernetes:
|
|
227
|
-
|
|
228
302
|
```bash
|
|
229
|
-
# Using
|
|
303
|
+
# Using --host flag (single server)
|
|
230
304
|
termainer --host ec2-54-123-45-67.us-east-1.compute.amazonaws.com \
|
|
231
305
|
--ssh-user ubuntu \
|
|
232
306
|
--ssh-key ~/.ssh/production.pem \
|
|
233
307
|
--provider docker
|
|
234
308
|
|
|
235
|
-
# Using .env (recommended for frequent use)
|
|
309
|
+
# Using .env (recommended for frequent single-server use)
|
|
236
310
|
cp .env.example .env
|
|
237
311
|
# Edit .env with your server details
|
|
238
312
|
termainer
|
|
239
313
|
```
|
|
240
314
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
Termainer automatically discovers servers from your **`~/.ssh/config`** file. This is the **recommended approach** for multi-server setups—no `.env` file needed!
|
|
244
|
-
|
|
245
|
-
Simply define your remote servers in `~/.ssh/config`:
|
|
246
|
-
|
|
247
|
-
```
|
|
248
|
-
Host prod-web
|
|
249
|
-
HostName ec2-54-123-45-67.us-east-1.compute.amazonaws.com
|
|
250
|
-
User ubuntu
|
|
251
|
-
IdentityFile ~/.ssh/production.pem
|
|
252
|
-
|
|
253
|
-
Host staging-k8s
|
|
254
|
-
HostName k8s-staging.example.com
|
|
255
|
-
User admin
|
|
256
|
-
IdentityFile ~/.ssh/staging-key
|
|
257
|
-
|
|
258
|
-
Host dev-local
|
|
259
|
-
HostName 192.168.1.100
|
|
260
|
-
User devuser
|
|
261
|
-
IdentityFile ~/.ssh/dev-key
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Then launch Termainer:
|
|
265
|
-
|
|
266
|
-
```bash
|
|
267
|
-
termainer
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
You'll see all servers from your SSH config automatically loaded in the server dropdown. No configuration files needed!
|
|
315
|
+
For multi-server setups, use `~/.ssh/config` entries (see [Configuration](#ssh-config-auto-discovery-recommended)).
|
|
271
316
|
|
|
272
|
-
|
|
273
|
-
> daemon socket accessible **without `sudo`**. The user connecting via SSH must be
|
|
274
|
-
> a member of the `docker` group (or equivalent) so that `docker ps`, `docker inspect`,
|
|
275
|
-
> etc. work without privilege escalation. Termainer uses SSH port forwarding to
|
|
276
|
-
> tunnel the remote Docker socket to a local Unix socket, so all Docker commands
|
|
277
|
-
> run locally against the forwarded socket — this avoids issues with remote Docker
|
|
278
|
-
> versions, PATH, and `--format` compatibility.
|
|
317
|
+
### Server Selection & Caching
|
|
279
318
|
|
|
280
|
-
|
|
319
|
+
When you click a technology card (e.g. "Kubernetes") and don't have it locally, Termainer shows:
|
|
281
320
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
321
|
+
1. **Server selection modal** — checkboxes for all SSH servers (`~/.ssh/config` + app-added)
|
|
322
|
+
- Pre-selected based on **cached preferences** (`~/.config/termainer/provider_servers.json`)
|
|
323
|
+
- "Manage servers" button to add/remove servers via the UI
|
|
324
|
+
2. **Parallel probing** — selected servers are probed simultaneously for the chosen provider
|
|
325
|
+
3. **Auto-cache** — successful server-provider mappings are saved for next launch
|
|
286
326
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
```bash
|
|
290
|
-
# Debian/Ubuntu
|
|
291
|
-
sudo apt install sshpass
|
|
292
|
-
|
|
293
|
-
# RHEL/CentOS/Fedora
|
|
294
|
-
sudo yum install sshpass
|
|
295
|
-
```
|
|
327
|
+
Within a dashboard, the server dropdown lets you switch servers. Switching only probes the **current provider** on the target server — no unnecessary Docker/K8s/Podman probes.
|
|
296
328
|
|
|
297
329
|
### Multi-Server Dashboard
|
|
298
330
|
|
|
299
|
-
|
|
331
|
+
When multiple servers are connected (via parallel selection or pre-configured connections), each technology dashboard aggregates its related servers. You can:
|
|
300
332
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
- **Server dropdown** at the top of the dashboard to select a specific server
|
|
304
|
-
- **"Todos" (All)** option to see resources from all servers for that technology
|
|
305
|
-
- **Switch servers anytime** using the server dropdown in the sidebar
|
|
333
|
+
- Use the **server dropdown** to select a specific server
|
|
334
|
+
- **Switch servers anytime** — only the current provider is probed
|
|
306
335
|
- Each container shows its **server name** prefix when in multi-server mode
|
|
307
336
|
|
|
308
|
-
The sidebar dropdown automatically populates with:
|
|
309
|
-
- **Local** (your machine)
|
|
310
|
-
- SSH servers from `~/.ssh/config`
|
|
311
|
-
- Servers from `config.yaml` (if configured)
|
|
312
|
-
|
|
313
337
|
### Keyboard Shortcuts
|
|
314
338
|
|
|
315
339
|
#### Technology selection screen
|
|
@@ -364,6 +388,7 @@ CLI (termainer)
|
|
|
364
388
|
├── server_manager.py ← Multi-server connection manager
|
|
365
389
|
├── remote/ ← Remote connection module
|
|
366
390
|
│ └── ssh.py ← SSH via subprocess (key + password)
|
|
391
|
+
├── storage.py ← Persistent cache & user servers (JSON files)
|
|
367
392
|
├── config.py ← .env parser and SSH builder
|
|
368
393
|
├── providers/ ← Multi-provider abstraction layer
|
|
369
394
|
│ ├── base.py ← Abstract Protocol
|
|
@@ -406,6 +431,19 @@ CLI (termainer)
|
|
|
406
431
|
|
|
407
432
|
---
|
|
408
433
|
|
|
434
|
+
## Troubleshooting
|
|
435
|
+
|
|
436
|
+
| Problem | Solution |
|
|
437
|
+
|---------|----------|
|
|
438
|
+
| **"Not installed" on all technology cards** | No local runtime detected and no SSH servers configured. Add entries to `~/.ssh/config` or use the "Manage servers" button in the app |
|
|
439
|
+
| **SSH server shows in dropdown but fails to connect** | Verify the host is reachable (`ssh <alias>`) and the container runtime is installed on the remote host |
|
|
440
|
+
| **Docker SSH tunnel fails** | The remote user must be in the `docker` group. Try `ssh <host> docker info` manually |
|
|
441
|
+
| **Kubernetes/Podman/OpenShift over SSH not detected** | Ensure the CLI binary (`kubectl`, `podman`, `oc`) is in the remote user's `PATH` |
|
|
442
|
+
| **Server selection modal is empty** | No SSH servers configured in `~/.ssh/config` or the app's server manager. Use "Manage servers" to add one |
|
|
443
|
+
| **App crashes with "no runtime"** | This was fixed in v0.4.1+. Update to the latest version or just configure SSH servers |
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
409
447
|
## License
|
|
410
448
|
|
|
411
449
|
MIT — [Alan Emanuel Stefanov](https://github.com/AlanStefanov)
|
|
@@ -9,6 +9,7 @@ Building scalable platforms, leading teams, and optimizing cloud operations.
|
|
|
9
9
|
[](https://www.linkedin.com/in/alanstefanov/)
|
|
10
10
|
[](mailto:alan.emanuel.stefanov@gmail.com)
|
|
11
11
|
[](https://github.com/AlanStefanov)
|
|
12
|
+
[](https://astefanov.com)
|
|
12
13
|
|
|
13
14
|
</div>
|
|
14
15
|
|
|
@@ -137,9 +138,84 @@ Depending on how you installed Termainer:
|
|
|
137
138
|
|
|
138
139
|
---
|
|
139
140
|
|
|
141
|
+
## Quick Start
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Just run it — Termainer auto-detects local Docker, Podman, kubectl, or oc
|
|
145
|
+
termainer
|
|
146
|
+
|
|
147
|
+
# If no local runtime is found, configure remote servers via ~/.ssh/config
|
|
148
|
+
# (see below) and restart, or click any technology card to probe SSH servers
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Termainer works **with zero configuration** for local environments. For remote servers, just add entries to `~/.ssh/config` — no config file needed.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
140
155
|
## Configuration
|
|
141
156
|
|
|
142
|
-
|
|
157
|
+
### SSH Config Auto-Discovery (Recommended)
|
|
158
|
+
|
|
159
|
+
Termainer automatically discovers servers from your **`~/.ssh/config`** file. This is the **recommended approach** — no YAML or `.env` file needed.
|
|
160
|
+
|
|
161
|
+
Simply define your remote servers in `~/.ssh/config`:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Host prod-web
|
|
165
|
+
HostName ec2-54-123-45-67.us-east-1.compute.amazonaws.com
|
|
166
|
+
User ubuntu
|
|
167
|
+
IdentityFile ~/.ssh/production.pem
|
|
168
|
+
|
|
169
|
+
Host staging-k8s
|
|
170
|
+
HostName k8s-staging.example.com
|
|
171
|
+
User admin
|
|
172
|
+
IdentityFile ~/.ssh/staging-key
|
|
173
|
+
|
|
174
|
+
Host dev-docker
|
|
175
|
+
HostName 192.168.1.100
|
|
176
|
+
User devuser
|
|
177
|
+
IdentityFile ~/.ssh/dev-key
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Then launch Termainer — all SSH entries appear in the server dropdown automatically:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
termainer
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
When you select a technology card (e.g. "Kubernetes"), Termainer shows a **server selection dialog** with checkboxes for all configured SSH servers. Selected servers are probed **in parallel** for the chosen provider, and successful connections open the dashboard. Server preferences are cached in `~/.config/termainer/provider_servers.json`.
|
|
187
|
+
|
|
188
|
+
You can also **add and remove servers directly from the UI** via the "Manage servers" button. Custom servers are persisted in `~/.config/termainer/servers.json`.
|
|
189
|
+
|
|
190
|
+
> **⚠️ Docker/Swarm note:** For Docker and Swarm over SSH, Termainer uses SSH port
|
|
191
|
+
> forwarding to tunnel the remote Docker socket to a local Unix socket. The remote
|
|
192
|
+
> user must have access to the Docker daemon (be in the `docker` group) without `sudo`.
|
|
193
|
+
> Kubernetes, Podman, and OpenShift commands run directly over SSH, no tunnel needed.
|
|
194
|
+
|
|
195
|
+
### Dashboard server switching
|
|
196
|
+
|
|
197
|
+
Within a dashboard, switching servers only probes the **current provider** on the target server (no multi-probe). If the server doesn't have that provider, a notification is shown and the dashboard stays on the current server.
|
|
198
|
+
|
|
199
|
+
#### SSH Authentication Methods
|
|
200
|
+
|
|
201
|
+
| Method | How to use |
|
|
202
|
+
|---|---|
|
|
203
|
+
| **Key (`.pem`)** | Set `IdentityFile` in `~/.ssh/config` |
|
|
204
|
+
| **Password** | `--ssh-password 'mypass'` or `TERMAINER_REMOTE_PASSWORD` in `.env` (requires `sshpass`) |
|
|
205
|
+
|
|
206
|
+
For password-based auth, install `sshpass`:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Debian/Ubuntu
|
|
210
|
+
sudo apt install sshpass
|
|
211
|
+
|
|
212
|
+
# RHEL/CentOS/Fedora
|
|
213
|
+
sudo yum install sshpass
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Multi-Server via config.yaml (Advanced)
|
|
217
|
+
|
|
218
|
+
For advanced setups, create `~/.config/termainer/config.yaml`:
|
|
143
219
|
|
|
144
220
|
```yaml
|
|
145
221
|
lang: en
|
|
@@ -164,9 +240,9 @@ servers:
|
|
|
164
240
|
|
|
165
241
|
See the full [Configuration Reference](guide/configuration-reference.md) for all options.
|
|
166
242
|
|
|
167
|
-
###
|
|
243
|
+
### Single-server via .env
|
|
168
244
|
|
|
169
|
-
For a single remote server,
|
|
245
|
+
For a quick single remote server, use `.env` or CLI flags:
|
|
170
246
|
|
|
171
247
|
```bash
|
|
172
248
|
termainer --host ec2-54-123-45-67.us-east-1.compute.amazonaws.com \
|
|
@@ -201,93 +277,41 @@ termainer --provider openshift
|
|
|
201
277
|
|
|
202
278
|
### Remote (SSH)
|
|
203
279
|
|
|
204
|
-
Connect to remote servers running Docker or Kubernetes:
|
|
205
|
-
|
|
206
280
|
```bash
|
|
207
|
-
# Using
|
|
281
|
+
# Using --host flag (single server)
|
|
208
282
|
termainer --host ec2-54-123-45-67.us-east-1.compute.amazonaws.com \
|
|
209
283
|
--ssh-user ubuntu \
|
|
210
284
|
--ssh-key ~/.ssh/production.pem \
|
|
211
285
|
--provider docker
|
|
212
286
|
|
|
213
|
-
# Using .env (recommended for frequent use)
|
|
287
|
+
# Using .env (recommended for frequent single-server use)
|
|
214
288
|
cp .env.example .env
|
|
215
289
|
# Edit .env with your server details
|
|
216
290
|
termainer
|
|
217
291
|
```
|
|
218
292
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Termainer automatically discovers servers from your **`~/.ssh/config`** file. This is the **recommended approach** for multi-server setups—no `.env` file needed!
|
|
222
|
-
|
|
223
|
-
Simply define your remote servers in `~/.ssh/config`:
|
|
224
|
-
|
|
225
|
-
```
|
|
226
|
-
Host prod-web
|
|
227
|
-
HostName ec2-54-123-45-67.us-east-1.compute.amazonaws.com
|
|
228
|
-
User ubuntu
|
|
229
|
-
IdentityFile ~/.ssh/production.pem
|
|
230
|
-
|
|
231
|
-
Host staging-k8s
|
|
232
|
-
HostName k8s-staging.example.com
|
|
233
|
-
User admin
|
|
234
|
-
IdentityFile ~/.ssh/staging-key
|
|
235
|
-
|
|
236
|
-
Host dev-local
|
|
237
|
-
HostName 192.168.1.100
|
|
238
|
-
User devuser
|
|
239
|
-
IdentityFile ~/.ssh/dev-key
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Then launch Termainer:
|
|
243
|
-
|
|
244
|
-
```bash
|
|
245
|
-
termainer
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
You'll see all servers from your SSH config automatically loaded in the server dropdown. No configuration files needed!
|
|
293
|
+
For multi-server setups, use `~/.ssh/config` entries (see [Configuration](#ssh-config-auto-discovery-recommended)).
|
|
249
294
|
|
|
250
|
-
|
|
251
|
-
> daemon socket accessible **without `sudo`**. The user connecting via SSH must be
|
|
252
|
-
> a member of the `docker` group (or equivalent) so that `docker ps`, `docker inspect`,
|
|
253
|
-
> etc. work without privilege escalation. Termainer uses SSH port forwarding to
|
|
254
|
-
> tunnel the remote Docker socket to a local Unix socket, so all Docker commands
|
|
255
|
-
> run locally against the forwarded socket — this avoids issues with remote Docker
|
|
256
|
-
> versions, PATH, and `--format` compatibility.
|
|
295
|
+
### Server Selection & Caching
|
|
257
296
|
|
|
258
|
-
|
|
297
|
+
When you click a technology card (e.g. "Kubernetes") and don't have it locally, Termainer shows:
|
|
259
298
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
299
|
+
1. **Server selection modal** — checkboxes for all SSH servers (`~/.ssh/config` + app-added)
|
|
300
|
+
- Pre-selected based on **cached preferences** (`~/.config/termainer/provider_servers.json`)
|
|
301
|
+
- "Manage servers" button to add/remove servers via the UI
|
|
302
|
+
2. **Parallel probing** — selected servers are probed simultaneously for the chosen provider
|
|
303
|
+
3. **Auto-cache** — successful server-provider mappings are saved for next launch
|
|
264
304
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
```bash
|
|
268
|
-
# Debian/Ubuntu
|
|
269
|
-
sudo apt install sshpass
|
|
270
|
-
|
|
271
|
-
# RHEL/CentOS/Fedora
|
|
272
|
-
sudo yum install sshpass
|
|
273
|
-
```
|
|
305
|
+
Within a dashboard, the server dropdown lets you switch servers. Switching only probes the **current provider** on the target server — no unnecessary Docker/K8s/Podman probes.
|
|
274
306
|
|
|
275
307
|
### Multi-Server Dashboard
|
|
276
308
|
|
|
277
|
-
|
|
309
|
+
When multiple servers are connected (via parallel selection or pre-configured connections), each technology dashboard aggregates its related servers. You can:
|
|
278
310
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
- **Server dropdown** at the top of the dashboard to select a specific server
|
|
282
|
-
- **"Todos" (All)** option to see resources from all servers for that technology
|
|
283
|
-
- **Switch servers anytime** using the server dropdown in the sidebar
|
|
311
|
+
- Use the **server dropdown** to select a specific server
|
|
312
|
+
- **Switch servers anytime** — only the current provider is probed
|
|
284
313
|
- Each container shows its **server name** prefix when in multi-server mode
|
|
285
314
|
|
|
286
|
-
The sidebar dropdown automatically populates with:
|
|
287
|
-
- **Local** (your machine)
|
|
288
|
-
- SSH servers from `~/.ssh/config`
|
|
289
|
-
- Servers from `config.yaml` (if configured)
|
|
290
|
-
|
|
291
315
|
### Keyboard Shortcuts
|
|
292
316
|
|
|
293
317
|
#### Technology selection screen
|
|
@@ -342,6 +366,7 @@ CLI (termainer)
|
|
|
342
366
|
├── server_manager.py ← Multi-server connection manager
|
|
343
367
|
├── remote/ ← Remote connection module
|
|
344
368
|
│ └── ssh.py ← SSH via subprocess (key + password)
|
|
369
|
+
├── storage.py ← Persistent cache & user servers (JSON files)
|
|
345
370
|
├── config.py ← .env parser and SSH builder
|
|
346
371
|
├── providers/ ← Multi-provider abstraction layer
|
|
347
372
|
│ ├── base.py ← Abstract Protocol
|
|
@@ -384,6 +409,19 @@ CLI (termainer)
|
|
|
384
409
|
|
|
385
410
|
---
|
|
386
411
|
|
|
412
|
+
## Troubleshooting
|
|
413
|
+
|
|
414
|
+
| Problem | Solution |
|
|
415
|
+
|---------|----------|
|
|
416
|
+
| **"Not installed" on all technology cards** | No local runtime detected and no SSH servers configured. Add entries to `~/.ssh/config` or use the "Manage servers" button in the app |
|
|
417
|
+
| **SSH server shows in dropdown but fails to connect** | Verify the host is reachable (`ssh <alias>`) and the container runtime is installed on the remote host |
|
|
418
|
+
| **Docker SSH tunnel fails** | The remote user must be in the `docker` group. Try `ssh <host> docker info` manually |
|
|
419
|
+
| **Kubernetes/Podman/OpenShift over SSH not detected** | Ensure the CLI binary (`kubectl`, `podman`, `oc`) is in the remote user's `PATH` |
|
|
420
|
+
| **Server selection modal is empty** | No SSH servers configured in `~/.ssh/config` or the app's server manager. Use "Manage servers" to add one |
|
|
421
|
+
| **App crashes with "no runtime"** | This was fixed in v0.4.1+. Update to the latest version or just configure SSH servers |
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
387
425
|
## License
|
|
388
426
|
|
|
389
427
|
MIT — [Alan Emanuel Stefanov](https://github.com/AlanStefanov)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "termainer"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "Container observability and operations directly from your terminal"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -38,6 +38,11 @@ dev = [
|
|
|
38
38
|
termainer = "termainer.app:main"
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
[tool.setuptools]
|
|
44
|
+
include-package-data = true
|
|
45
|
+
|
|
41
46
|
[tool.setuptools.packages.find]
|
|
42
47
|
where = ["src"]
|
|
43
48
|
|
|
@@ -48,7 +53,7 @@ branch = "main"
|
|
|
48
53
|
commit_message = "chore(release): {version}"
|
|
49
54
|
major_on_zero = false
|
|
50
55
|
upload_to_pypi = false
|
|
51
|
-
upload_to_release =
|
|
56
|
+
upload_to_release = true
|
|
52
57
|
build_command = ""
|
|
53
58
|
|
|
54
59
|
[tool.semantic_release.changelog]
|
|
@@ -3,6 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import argparse
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from importlib.resources import files
|
|
10
|
+
from pathlib import Path
|
|
6
11
|
from typing import List, Optional, Type
|
|
7
12
|
|
|
8
13
|
from textual.app import App
|
|
@@ -22,9 +27,96 @@ from .ui.splash import BootScreen
|
|
|
22
27
|
from .version import VERSION
|
|
23
28
|
|
|
24
29
|
|
|
30
|
+
def _check_command(command: list[str]) -> bool:
|
|
31
|
+
try:
|
|
32
|
+
subprocess.run(
|
|
33
|
+
command,
|
|
34
|
+
stdout=subprocess.DEVNULL,
|
|
35
|
+
stderr=subprocess.DEVNULL,
|
|
36
|
+
check=True,
|
|
37
|
+
)
|
|
38
|
+
return True
|
|
39
|
+
except (OSError, subprocess.CalledProcessError):
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _print_doctor_line(ok: bool, label: str, detail: str = "") -> None:
|
|
44
|
+
mark = "✓" if ok else "✗"
|
|
45
|
+
suffix = f" {detail}" if detail else ""
|
|
46
|
+
print(f"{mark} {label}{suffix}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_doctor() -> int:
|
|
50
|
+
print("Termainer Doctor")
|
|
51
|
+
print("────────────────────────────────────")
|
|
52
|
+
print()
|
|
53
|
+
|
|
54
|
+
checks: list[bool] = []
|
|
55
|
+
core_checks: list[bool] = []
|
|
56
|
+
|
|
57
|
+
python_ok = sys.version_info >= (3, 10)
|
|
58
|
+
checks.append(python_ok)
|
|
59
|
+
core_checks.append(python_ok)
|
|
60
|
+
_print_doctor_line(python_ok, f"Python {sys.version_info.major}.{sys.version_info.minor}")
|
|
61
|
+
|
|
62
|
+
docker_cli = shutil.which("docker") is not None
|
|
63
|
+
checks.append(docker_cli)
|
|
64
|
+
_print_doctor_line(docker_cli, "Docker CLI detected")
|
|
65
|
+
|
|
66
|
+
docker_api = docker_cli and _check_command(["docker", "info"])
|
|
67
|
+
checks.append(docker_api)
|
|
68
|
+
_print_doctor_line(docker_api, "Docker API available")
|
|
69
|
+
|
|
70
|
+
ssh_ok = shutil.which("ssh") is not None
|
|
71
|
+
checks.append(ssh_ok)
|
|
72
|
+
_print_doctor_line(ssh_ok, "SSH support enabled")
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
import textual # noqa: F401
|
|
76
|
+
|
|
77
|
+
textual_ok = True
|
|
78
|
+
except ImportError:
|
|
79
|
+
textual_ok = False
|
|
80
|
+
checks.append(textual_ok)
|
|
81
|
+
core_checks.append(textual_ok)
|
|
82
|
+
_print_doctor_line(textual_ok, "Textual installed")
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
import rich # noqa: F401
|
|
86
|
+
|
|
87
|
+
rich_ok = True
|
|
88
|
+
except ImportError:
|
|
89
|
+
rich_ok = False
|
|
90
|
+
checks.append(rich_ok)
|
|
91
|
+
core_checks.append(rich_ok)
|
|
92
|
+
_print_doctor_line(rich_ok, "Rich installed")
|
|
93
|
+
|
|
94
|
+
config_dir = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "termainer"
|
|
95
|
+
config_ok = config_dir.exists() or os.access(config_dir.parent, os.W_OK)
|
|
96
|
+
checks.append(config_ok)
|
|
97
|
+
core_checks.append(config_ok)
|
|
98
|
+
_print_doctor_line(config_ok, "Configuration directory", str(config_dir))
|
|
99
|
+
|
|
100
|
+
checks.append(True)
|
|
101
|
+
core_checks.append(True)
|
|
102
|
+
_print_doctor_line(True, "Version", VERSION)
|
|
103
|
+
|
|
104
|
+
print()
|
|
105
|
+
if all(checks):
|
|
106
|
+
print("Everything looks good.")
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
if all(core_checks):
|
|
110
|
+
print("Core checks passed. Some optional runtime checks failed.")
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
print("Some checks failed.")
|
|
114
|
+
return 1
|
|
115
|
+
|
|
116
|
+
|
|
25
117
|
class TermainerApp(App):
|
|
26
118
|
TITLE = "Termainer"
|
|
27
|
-
CSS_PATH = "ui
|
|
119
|
+
CSS_PATH = files("termainer.ui").joinpath("styles.tcss")
|
|
28
120
|
|
|
29
121
|
def __init__(self, server_manager: ServerManager) -> None:
|
|
30
122
|
super().__init__()
|
|
@@ -126,7 +218,7 @@ async def build_server_manager(
|
|
|
126
218
|
# Local auto-detect mode: solo proveedores locales, sin conexiones SSH al startup
|
|
127
219
|
available = await detect_available_providers(ssh=None)
|
|
128
220
|
if not available:
|
|
129
|
-
|
|
221
|
+
return ServerManager([])
|
|
130
222
|
for provider in available:
|
|
131
223
|
label = _("app.server.local_label", provider=provider.name.capitalize())
|
|
132
224
|
connections.append(ServerConnection(label=label, provider=provider, ssh=None))
|
|
@@ -143,6 +235,12 @@ def parse_args() -> argparse.Namespace:
|
|
|
143
235
|
parser = argparse.ArgumentParser(
|
|
144
236
|
description="Termainer — Container observability and operations from your terminal"
|
|
145
237
|
)
|
|
238
|
+
parser.add_argument(
|
|
239
|
+
"command",
|
|
240
|
+
nargs="?",
|
|
241
|
+
choices=("doctor",),
|
|
242
|
+
help="Run a non-interactive environment diagnostic",
|
|
243
|
+
)
|
|
146
244
|
parser.add_argument(
|
|
147
245
|
"--provider",
|
|
148
246
|
choices=("auto", "docker", "swarm", "podman", "kubernetes", "k8s", "openshift"),
|
|
@@ -202,6 +300,9 @@ def main() -> None:
|
|
|
202
300
|
locale_init()
|
|
203
301
|
args = parse_args()
|
|
204
302
|
|
|
303
|
+
if args.command == "doctor":
|
|
304
|
+
raise SystemExit(run_doctor())
|
|
305
|
+
|
|
205
306
|
env_file = args.env_file
|
|
206
307
|
env = load_env_file(env_file) if os.path.isfile(env_file) else {}
|
|
207
308
|
locale_init(env)
|