termainer 0.4.0__tar.gz → 0.5.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.
Files changed (48) hide show
  1. termainer-0.5.0/MANIFEST.in +1 -0
  2. {termainer-0.4.0 → termainer-0.5.0}/PKG-INFO +108 -70
  3. {termainer-0.4.0 → termainer-0.5.0}/README.md +107 -69
  4. {termainer-0.4.0 → termainer-0.5.0}/pyproject.toml +7 -2
  5. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/app.py +103 -2
  6. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/config.py +6 -4
  7. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/locale.py +94 -0
  8. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/docker.py +85 -55
  9. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/kubernetes.py +57 -26
  10. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/openshift.py +33 -12
  11. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/podman.py +54 -24
  12. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/swarm.py +42 -25
  13. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/remote/ssh.py +30 -13
  14. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/ssh_config.py +2 -0
  15. termainer-0.5.0/src/termainer/storage.py +110 -0
  16. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/ui/dashboard.py +208 -65
  17. termainer-0.5.0/src/termainer/ui/environment.py +770 -0
  18. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/ui/home.py +8 -3
  19. termainer-0.5.0/src/termainer/ui/logo.png +0 -0
  20. termainer-0.5.0/src/termainer/ui/styles.tcss +1239 -0
  21. termainer-0.5.0/src/termainer/version.py +1 -0
  22. {termainer-0.4.0 → termainer-0.5.0}/src/termainer.egg-info/PKG-INFO +108 -70
  23. {termainer-0.4.0 → termainer-0.5.0}/src/termainer.egg-info/SOURCES.txt +4 -0
  24. {termainer-0.4.0 → termainer-0.5.0}/tests/test_app.py +13 -4
  25. {termainer-0.4.0 → termainer-0.5.0}/tests/test_providers.py +4 -1
  26. termainer-0.4.0/src/termainer/ui/environment.py +0 -300
  27. termainer-0.4.0/src/termainer/version.py +0 -1
  28. {termainer-0.4.0 → termainer-0.5.0}/setup.cfg +0 -0
  29. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/__init__.py +0 -0
  30. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/config_manager.py +0 -0
  31. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/__init__.py +0 -0
  32. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/providers/base.py +0 -0
  33. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/remote/__init__.py +0 -0
  34. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/server_manager.py +0 -0
  35. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/ui/__init__.py +0 -0
  36. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/ui/splash.py +0 -0
  37. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/ui/widgets.py +0 -0
  38. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/utils/__init__.py +0 -0
  39. {termainer-0.4.0 → termainer-0.5.0}/src/termainer/utils/helpers.py +0 -0
  40. {termainer-0.4.0 → termainer-0.5.0}/src/termainer.egg-info/dependency_links.txt +0 -0
  41. {termainer-0.4.0 → termainer-0.5.0}/src/termainer.egg-info/entry_points.txt +0 -0
  42. {termainer-0.4.0 → termainer-0.5.0}/src/termainer.egg-info/requires.txt +0 -0
  43. {termainer-0.4.0 → termainer-0.5.0}/src/termainer.egg-info/top_level.txt +0 -0
  44. {termainer-0.4.0 → termainer-0.5.0}/tests/test_config.py +0 -0
  45. {termainer-0.4.0 → termainer-0.5.0}/tests/test_config_manager.py +0 -0
  46. {termainer-0.4.0 → termainer-0.5.0}/tests/test_server_manager.py +0 -0
  47. {termainer-0.4.0 → termainer-0.5.0}/tests/test_ssh_config.py +0 -0
  48. {termainer-0.4.0 → termainer-0.5.0}/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.4.0
3
+ Version: 0.5.0
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
  [![LinkedIn](https://img.shields.io/badge/LinkedIn-Alan_Stefanov-blue?style=for-the-badge&logo=linkedin)](https://www.linkedin.com/in/alanstefanov/)
32
32
  [![Email](https://img.shields.io/badge/Email-alan.emanuel.stefanov@gmail.com-red?style=for-the-badge&logo=gmail)](mailto:alan.emanuel.stefanov@gmail.com)
33
33
  [![GitHub](https://img.shields.io/badge/GitHub-AlanStefanov-black?style=for-the-badge&logo=github)](https://github.com/AlanStefanov)
34
+ [![Web](https://img.shields.io/badge/Web-astefanov.com-10b981?style=for-the-badge&logo=vercel)](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
- Termainer uses a **YAML config file** to manage multiple servers. Create `~/.config/termainer/config.yaml`:
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
- ### Quick single-server (.env)
265
+ ### Single-server via .env
190
266
 
191
- For a single remote server, you can use `.env` or CLI flags instead:
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 CLI flags
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
- #### SSH Config Auto-Discovery (Recommended)
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
- > **⚠️ Important:** Remote servers must have the Docker CLI installed and the Docker
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
- #### SSH Authentication Methods
319
+ When you click a technology card (e.g. "Kubernetes") and don't have it locally, Termainer shows:
281
320
 
282
- | Method | How to use |
283
- |---|---|
284
- | **Key (`.pem`)** | `--ssh-key ~/.ssh/key.pem` or `TERMAINER_REMOTE_KEY_PATH` in `.env` |
285
- | **Password** | `--ssh-password 'mypass'` or `TERMAINER_REMOTE_PASSWORD` in `.env` (requires `sshpass`) |
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
- For password-based auth, install `sshpass`:
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
- The environment screen is technology-first (`Docker`, `Swarm`, `Kubernetes`, `Podman`, `OpenShift`).
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
- When you configure multiple servers (via `config.yaml` or `~/.ssh/config`), each technology dashboard can aggregate its related servers. You can:
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
  [![LinkedIn](https://img.shields.io/badge/LinkedIn-Alan_Stefanov-blue?style=for-the-badge&logo=linkedin)](https://www.linkedin.com/in/alanstefanov/)
10
10
  [![Email](https://img.shields.io/badge/Email-alan.emanuel.stefanov@gmail.com-red?style=for-the-badge&logo=gmail)](mailto:alan.emanuel.stefanov@gmail.com)
11
11
  [![GitHub](https://img.shields.io/badge/GitHub-AlanStefanov-black?style=for-the-badge&logo=github)](https://github.com/AlanStefanov)
12
+ [![Web](https://img.shields.io/badge/Web-astefanov.com-10b981?style=for-the-badge&logo=vercel)](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
- Termainer uses a **YAML config file** to manage multiple servers. Create `~/.config/termainer/config.yaml`:
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
- ### Quick single-server (.env)
243
+ ### Single-server via .env
168
244
 
169
- For a single remote server, you can use `.env` or CLI flags instead:
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 CLI flags
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
- #### SSH Config Auto-Discovery (Recommended)
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
- > **⚠️ Important:** Remote servers must have the Docker CLI installed and the Docker
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
- #### SSH Authentication Methods
297
+ When you click a technology card (e.g. "Kubernetes") and don't have it locally, Termainer shows:
259
298
 
260
- | Method | How to use |
261
- |---|---|
262
- | **Key (`.pem`)** | `--ssh-key ~/.ssh/key.pem` or `TERMAINER_REMOTE_KEY_PATH` in `.env` |
263
- | **Password** | `--ssh-password 'mypass'` or `TERMAINER_REMOTE_PASSWORD` in `.env` (requires `sshpass`) |
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
- For password-based auth, install `sshpass`:
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
- The environment screen is technology-first (`Docker`, `Swarm`, `Kubernetes`, `Podman`, `OpenShift`).
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
- When you configure multiple servers (via `config.yaml` or `~/.ssh/config`), each technology dashboard can aggregate its related servers. You can:
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.4.0"
7
+ version = "0.5.0"
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 = false
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/styles.tcss"
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
- raise RuntimeError(_("app.error.no_runtime"))
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)