Compare commits

...

5 Commits

Author SHA1 Message Date
Codex
136d1605c0 docs: refresh Claude Codex and Gemini integration guides 2026-03-11 19:32:25 +00:00
Codex
9da3125c34 feat: add Gemini skill integration and multi-user AI setup 2026-03-11 19:32:25 +00:00
chrome-storm-c442
daa11ca440 v1.9.44: sync with Codex integration from Linux
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:35:10 -05:00
IPGO Developer
e2bdffb41e v1.9.40: add Codex integration — skill setup, deploy, GUI buttons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:28:11 +00:00
chrome-storm-c442
ddd6951610 v1.9.43: replace Memory quick button with Goroutines (available on all Go targets)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:37:52 -05:00
31 changed files with 2334 additions and 317 deletions

View File

@@ -0,0 +1,88 @@
---
name: server-manager
description: Use ServerManager's shared local server inventory and ssh.py utility to manage configured SSH, Telnet, SQL, Redis, S3/MinIO, Grafana, Prometheus, and WinRM endpoints by alias without exposing credentials. Use when the user asks to operate on servers managed by ServerManager or when editing ServerManager's Claude/Codex/Gemini integration.
metadata:
short-description: Safe remote ops through ServerManager aliases
---
# Server Manager
Use this skill for two cases:
1. The user wants work done on a server or service already configured in ServerManager.
2. The user wants to modify ServerManager's CLI/integration layer so Claude/Codex/Gemini can use it safely.
## First Step
Before any server operation:
```bash
$HOME/.server-connections/codex-ssh --list
```
Read the `Type` column before choosing commands. Do not guess the server type.
If the wrapper is missing, run the doctor script for your platform:
```bash
$HOME/.codex/skills/server-manager/scripts/server-manager-doctor.sh
```
On Windows, use:
```bat
%USERPROFILE%\.codex\skills\server-manager\scripts\server-manager-doctor.cmd
```
## Hard Rules
- Never read `~/.server-connections/servers.json`, `settings.json`, or `encryption.py` directly.
- Never use `--list-full`.
- Never use raw `ssh`, `scp`, `rsync`, `redis-cli`, `mysql`, `psql`, `mc`, `aws s3`, or similar tools unless the user explicitly asks to bypass ServerManager.
- Maximum one connection attempt per action. If it times out or fails, report it and stop.
- `ALIAS "command"` is only for `ssh` and `telnet`.
- `rdp` and `vnc` are GUI-only. Do not invent CLI access.
- For S3/MinIO, list buckets and paths before upload, delete, or URL generation.
- Ask for confirmation before destructive actions if the user's intent is not explicit.
## Preferred Entry Points
Use the shared wrapper:
```bash
$HOME/.server-connections/codex-ssh ...
```
It delegates to the installed `ssh.py` backend without requiring a `python` alias.
Safe discovery commands:
```bash
$HOME/.server-connections/codex-ssh --list
$HOME/.server-connections/codex-ssh --info ALIAS
$HOME/.server-connections/codex-ssh --status
```
Read [references/command-matrix.md](references/command-matrix.md) when you need the per-type command matrix.
## Server Operation Workflow
1. Run `--list`.
2. Match the alias using notes/type, not credentials.
3. Pick commands strictly from the server type.
4. Execute exactly one action.
5. Report the result without exposing IPs, logins, passwords, ports, or secrets.
## Working On ServerManager Itself
Read [references/project.md](references/project.md) before changing integration code.
Source-of-truth files:
- `tools/ssh.py`: local CLI used by Claude/Codex
- `tools/skill-ssh.md`: current Claude `/ssh` instructions
- `core/claude_setup.py`: Claude installer logic
- `build.py`: auto-deploys shared CLI files after build
- `README.md` and `CLAUDE.md`: project-level rules and architecture
If you change command semantics in `tools/ssh.py`, update the user-facing instructions alongside it.

View File

@@ -0,0 +1,91 @@
# Command Matrix
Always identify the server type first with:
```bash
$HOME/.server-connections/codex-ssh --list
```
## Type To Command Map
| Type | Use | Do Not Use |
| --- | --- | --- |
| `ssh` | `ALIAS "command"`, `--upload`, `--download`, `--ping`, `--install-key` | n/a |
| `telnet` | `ALIAS "command"` | `--upload`, `--download`, `--install-key` |
| `mariadb`, `mssql`, `postgresql` | `--sql`, `--sql-databases`, `--sql-tables` | `ALIAS "command"` |
| `redis` | `--redis`, `--redis-info`, `--redis-keys` | `ALIAS "command"` |
| `s3` | `--s3-buckets`, `--s3-ls`, `--s3-upload`, `--s3-download`, `--s3-delete`, `--s3-url`, `--s3-create-bucket` | `ALIAS "command"`, SSH/SFTP commands |
| `grafana` | `--grafana-dashboards`, `--grafana-alerts` | `ALIAS "command"` |
| `prometheus` | `--prom-query`, `--prom-targets`, `--prom-alerts` | `ALIAS "command"` |
| `winrm` | `--ps`, `--cmd` | `ALIAS "command"` |
| `rdp`, `vnc` | GUI only | all CLI actions |
## Common Safe Commands
```bash
$HOME/.server-connections/codex-ssh --list
$HOME/.server-connections/codex-ssh --info ALIAS
$HOME/.server-connections/codex-ssh --status
$HOME/.server-connections/codex-ssh --set-note ALIAS "description"
```
## SSH And Telnet
```bash
$HOME/.server-connections/codex-ssh ALIAS "command"
$HOME/.server-connections/codex-ssh ALIAS --no-sudo "command"
$HOME/.server-connections/codex-ssh ALIAS --upload "local" //remote/path
$HOME/.server-connections/codex-ssh ALIAS --download //remote/path "local"
$HOME/.server-connections/codex-ssh ALIAS --ping
```
Use double slashes for remote SSH/SFTP paths when working from Git Bash style environments.
## SQL
```bash
$HOME/.server-connections/codex-ssh --sql ALIAS "SELECT * FROM table LIMIT 10"
$HOME/.server-connections/codex-ssh --sql-databases ALIAS
$HOME/.server-connections/codex-ssh --sql-tables ALIAS [database]
```
## Redis
```bash
$HOME/.server-connections/codex-ssh --redis ALIAS "GET key"
$HOME/.server-connections/codex-ssh --redis-info ALIAS
$HOME/.server-connections/codex-ssh --redis-keys ALIAS "pattern:*"
```
## S3 / MinIO
Before modifying objects:
```bash
$HOME/.server-connections/codex-ssh --s3-buckets ALIAS
$HOME/.server-connections/codex-ssh --s3-ls ALIAS bucket/prefix/
```
Then act:
```bash
$HOME/.server-connections/codex-ssh --s3-upload ALIAS "local" bucket/key
$HOME/.server-connections/codex-ssh --s3-download ALIAS bucket/key "local"
$HOME/.server-connections/codex-ssh --s3-delete ALIAS bucket/key
$HOME/.server-connections/codex-ssh --s3-url ALIAS bucket/key [seconds]
$HOME/.server-connections/codex-ssh --s3-create-bucket ALIAS bucket-name
```
Do not treat S3 as a shell filesystem.
## Grafana / Prometheus / WinRM
```bash
$HOME/.server-connections/codex-ssh --grafana-dashboards ALIAS
$HOME/.server-connections/codex-ssh --grafana-alerts ALIAS
$HOME/.server-connections/codex-ssh --prom-query ALIAS "up"
$HOME/.server-connections/codex-ssh --prom-targets ALIAS
$HOME/.server-connections/codex-ssh --prom-alerts ALIAS
$HOME/.server-connections/codex-ssh --ps ALIAS "Get-Process"
$HOME/.server-connections/codex-ssh --cmd ALIAS "dir"
```

View File

@@ -0,0 +1,68 @@
# Project Notes
This skill is based on `/home/code/Desktop/CODING/server-manager`.
## What ServerManager Is
ServerManager is a cross-platform desktop GUI built with CustomTkinter. It manages multiple remote endpoint types through one local encrypted inventory:
- SSH / Telnet
- MariaDB / MSSQL / PostgreSQL
- Redis
- S3 / MinIO
- Grafana
- Prometheus
- WinRM
- RDP / VNC launchers
## Core Integration Model
The GUI and CLI share one local backend:
```text
ServerManager GUI <-> ~/.server-connections/servers.json <-> ~/.server-connections/ssh.py
```
The AI never needs raw credentials. It only uses aliases and the local CLI.
## Important Files
- `README.md`: product overview and install flow
- `CLAUDE.md`: project rules, architecture, security, workflow
- `tools/ssh.py`: CLI entry point used by AI tools
- `tools/skill-ssh.md`: current Claude `/ssh` instructions
- `core/claude_setup.py`: installer for shared CLI files plus Claude/Codex/Gemini skills
- `build.py`: auto-deploys `ssh.py`, `encryption.py`, Claude skill, Codex skill, and Gemini skill after builds
## Architectural Shape
- `core/server_store.py`: encrypted storage, CRUD, observers, backups
- `core/connection_factory.py`: type-to-client factory with lazy imports
- `core/*_client.py`: protocol-specific backends
- `gui/app.py`: tab registry, conditional tabs by server type
- `gui/tabs/`: protocol-specific GUI surfaces
## Existing Local Agent Integration
Current setup installs:
- `~/.server-connections/ssh.py`
- `~/.server-connections/encryption.py`
- `~/.claude/commands/ssh.md`
- `~/.codex/skills/server-manager/`
- `~/.server-connections/codex-ssh` or `codex-ssh.cmd`
- a `~/.claude/CLAUDE.md` guidance block
The Codex skill mirrors the same safety model:
- use aliases only
- use the shared local CLI
- never read credentials directly
- choose commands by server type
## Local Findings
- `ssh.py` is executable and uses a `python3` shebang, so Codex does not need a `python` alias.
- `ssh.py` has no `--help`; use `--list`, `--info`, and `--status` for safe discovery.
- The Unix wrapper path covers both Linux and macOS through `codex-ssh-wrapper.sh`.
- Windows-native Codex wrapper support exists through `codex-ssh-wrapper.cmd`.

View File

@@ -0,0 +1,28 @@
@echo off
setlocal
set "SHARED_DIR=%SERVER_MANAGER_SHARED_DIR%"
if "%SHARED_DIR%"=="" set "SHARED_DIR=%USERPROFILE%\.server-connections"
set "SSH_SCRIPT=%SHARED_DIR%\ssh.py"
if not exist "%SSH_SCRIPT%" (
echo error: missing ssh.py at %SSH_SCRIPT% 1>&2
echo hint: install ServerManager shared CLI files first 1>&2
exit /b 1
)
where py >nul 2>&1
if not errorlevel 1 (
py -3 "%SSH_SCRIPT%" %*
exit /b %errorlevel%
)
where python >nul 2>&1
if not errorlevel 1 (
python "%SSH_SCRIPT%" %*
exit /b %errorlevel%
)
echo error: neither py nor python is available in PATH 1>&2
echo hint: install Python launcher or use ServerManager Setup on a machine with Python present 1>&2
exit /b 1

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
shared_dir="${SERVER_MANAGER_SHARED_DIR:-$HOME/.server-connections}"
ssh_script="${shared_dir}/ssh.py"
if [[ ! -x "$ssh_script" ]]; then
echo "error: missing executable ssh.py at ${ssh_script}" >&2
echo "hint: install ServerManager's shared CLI files first" >&2
exit 1
fi
exec "$ssh_script" "$@"

View File

@@ -0,0 +1,41 @@
@echo off
setlocal
set "SHARED_DIR=%SERVER_MANAGER_SHARED_DIR%"
if "%SHARED_DIR%"=="" set "SHARED_DIR=%USERPROFILE%\.server-connections"
set "CODEX_HOME=%USERPROFILE%\.codex\skills\server-manager"
set "FAILED=0"
if exist "%SHARED_DIR%\ssh.py" (
echo ok: found %SHARED_DIR%\ssh.py
) else (
echo error: missing %SHARED_DIR%\ssh.py 1>&2
set "FAILED=1"
)
if exist "%SHARED_DIR%\encryption.py" (
echo ok: found %SHARED_DIR%\encryption.py
) else (
echo error: missing %SHARED_DIR%\encryption.py 1>&2
set "FAILED=1"
)
if exist "%SHARED_DIR%\codex-ssh.cmd" (
echo ok: found %SHARED_DIR%\codex-ssh.cmd
) else (
echo error: missing %SHARED_DIR%\codex-ssh.cmd 1>&2
set "FAILED=1"
)
if exist "%CODEX_HOME%\SKILL.md" (
echo ok: found %CODEX_HOME%\SKILL.md
) else (
echo error: missing %CODEX_HOME%\SKILL.md 1>&2
set "FAILED=1"
)
if "%FAILED%"=="1" exit /b 1
echo ok: ServerManager Codex integration looks installed
exit /b 0

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
shared_dir="${SERVER_MANAGER_SHARED_DIR:-$HOME/.server-connections}"
ssh_script="${shared_dir}/ssh.py"
encryption_module="${shared_dir}/encryption.py"
wrapper="${shared_dir}/codex-ssh"
status=0
check_file() {
local path="$1"
if [[ -f "$path" ]]; then
printf '[ok] file %s\n' "$path"
else
printf '[missing] file %s\n' "$path" >&2
status=1
fi
}
check_exec() {
local path="$1"
if [[ -x "$path" ]]; then
printf '[ok] executable %s\n' "$path"
else
printf '[missing] executable %s\n' "$path" >&2
status=1
fi
}
check_file "$encryption_module"
check_exec "$ssh_script"
check_exec "$wrapper"
if [[ -d "/home/code/CODING/server-manager" ]]; then
printf '[ok] source repo /home/code/CODING/server-manager\n'
else
printf '[warn] source repo /home/code/CODING/server-manager not found\n'
fi
exit "$status"

8
.gemini/settings.json Normal file
View File

@@ -0,0 +1,8 @@
{
"context": {
"fileName": "GEMINI.md"
},
"experimental": {
"enableAgents": true
}
}

View File

@@ -0,0 +1,84 @@
---
name: server-manager
description: Use ServerManager's shared local server inventory and ssh.py utility to manage configured SSH, Telnet, SQL, Redis, S3/MinIO, Grafana, Prometheus, and WinRM endpoints by alias without exposing credentials. Use when the user asks to operate on servers managed by ServerManager or when editing ServerManager's Claude/Codex/Gemini integration.
---
# Server Manager
Use this skill for two cases:
1. The user wants work done on a server or service already configured in ServerManager.
2. The user wants to modify ServerManager's CLI/integration layer so Claude/Codex/Gemini can use it safely.
## First Step
Before any server operation:
```bash
$HOME/.server-connections/gemini-ssh --list
```
Read the `Type` column before choosing commands. Do not guess the server type.
If the wrapper is missing, run the doctor script for your platform:
```bash
$HOME/.gemini/skills/server-manager/scripts/server-manager-gemini-doctor.sh
```
On Windows, use:
```bat
%USERPROFILE%\.gemini\skills\server-manager\scripts\server-manager-gemini-doctor.cmd
```
## Hard Rules
- Never read `~/.server-connections/servers.json`, `settings.json`, or `encryption.py` directly.
- Never use `--list-full`.
- Never use raw `ssh`, `scp`, `rsync`, `redis-cli`, `mysql`, `psql`, `mc`, `aws s3`, or similar tools unless the user explicitly asks to bypass ServerManager.
- Maximum one connection attempt per action. If it times out or fails, report it and stop.
- `ALIAS "command"` is only for `ssh` and `telnet`.
- `rdp` and `vnc` are GUI-only. Do not invent CLI access.
- For S3/MinIO, list buckets and paths before upload, delete, or URL generation.
- Ask for confirmation before destructive actions if the user's intent is not explicit.
## Preferred Entry Points
Use the shared wrapper:
```bash
$HOME/.server-connections/gemini-ssh ...
```
Safe discovery commands:
```bash
$HOME/.server-connections/gemini-ssh --list
$HOME/.server-connections/gemini-ssh --info ALIAS
$HOME/.server-connections/gemini-ssh --status
```
Read [references/command-matrix.md](references/command-matrix.md) when you need the per-type command matrix.
## Server Operation Workflow
1. Run `--list`.
2. Match the alias using notes/type, not credentials.
3. Pick commands strictly from the server type.
4. Execute exactly one action.
5. Report the result without exposing IPs, logins, passwords, ports, or secrets.
## Working On ServerManager Itself
Read [references/project.md](references/project.md) before changing integration code.
Source-of-truth files:
- `tools/ssh.py`: local CLI used by AI tools
- `tools/skill-ssh.md`: current Claude `/ssh` instructions
- `core/claude_setup.py`: installer for shared CLI files and AI skills
- `build.py`: auto-deploys `ssh.py`, `encryption.py`, Claude/Codex/Gemini skills after builds
- `README.md`, `CLAUDE.md`, and `GEMINI.md`: project-level rules and architecture
If you change command semantics in `tools/ssh.py`, update the user-facing instructions alongside it.

View File

@@ -0,0 +1,91 @@
# Command Matrix
Always identify the server type first with:
```bash
$HOME/.server-connections/gemini-ssh --list
```
## Type To Command Map
| Type | Use | Do Not Use |
| --- | --- | --- |
| `ssh` | `ALIAS "command"`, `--upload`, `--download`, `--ping`, `--install-key` | n/a |
| `telnet` | `ALIAS "command"` | `--upload`, `--download`, `--install-key` |
| `mariadb`, `mssql`, `postgresql` | `--sql`, `--sql-databases`, `--sql-tables` | `ALIAS "command"` |
| `redis` | `--redis`, `--redis-info`, `--redis-keys` | `ALIAS "command"` |
| `s3` | `--s3-buckets`, `--s3-ls`, `--s3-upload`, `--s3-download`, `--s3-delete`, `--s3-url`, `--s3-create-bucket` | `ALIAS "command"`, SSH/SFTP commands |
| `grafana` | `--grafana-dashboards`, `--grafana-alerts` | `ALIAS "command"` |
| `prometheus` | `--prom-query`, `--prom-targets`, `--prom-alerts` | `ALIAS "command"` |
| `winrm` | `--ps`, `--cmd` | `ALIAS "command"` |
| `rdp`, `vnc` | GUI only | all CLI actions |
## Common Safe Commands
```bash
$HOME/.server-connections/gemini-ssh --list
$HOME/.server-connections/gemini-ssh --info ALIAS
$HOME/.server-connections/gemini-ssh --status
$HOME/.server-connections/gemini-ssh --set-note ALIAS "description"
```
## SSH And Telnet
```bash
$HOME/.server-connections/gemini-ssh ALIAS "command"
$HOME/.server-connections/gemini-ssh ALIAS --no-sudo "command"
$HOME/.server-connections/gemini-ssh ALIAS --upload "local" //remote/path
$HOME/.server-connections/gemini-ssh ALIAS --download //remote/path "local"
$HOME/.server-connections/gemini-ssh ALIAS --ping
```
Use double slashes for remote SSH/SFTP paths when working from Git Bash style environments.
## SQL
```bash
$HOME/.server-connections/gemini-ssh --sql ALIAS "SELECT * FROM table LIMIT 10"
$HOME/.server-connections/gemini-ssh --sql-databases ALIAS
$HOME/.server-connections/gemini-ssh --sql-tables ALIAS [database]
```
## Redis
```bash
$HOME/.server-connections/gemini-ssh --redis ALIAS "GET key"
$HOME/.server-connections/gemini-ssh --redis-info ALIAS
$HOME/.server-connections/gemini-ssh --redis-keys ALIAS "pattern:*"
```
## S3 / MinIO
Before modifying objects:
```bash
$HOME/.server-connections/gemini-ssh --s3-buckets ALIAS
$HOME/.server-connections/gemini-ssh --s3-ls ALIAS bucket/prefix/
```
Then act:
```bash
$HOME/.server-connections/gemini-ssh --s3-upload ALIAS "local" bucket/key
$HOME/.server-connections/gemini-ssh --s3-download ALIAS bucket/key "local"
$HOME/.server-connections/gemini-ssh --s3-delete ALIAS bucket/key
$HOME/.server-connections/gemini-ssh --s3-url ALIAS bucket/key [seconds]
$HOME/.server-connections/gemini-ssh --s3-create-bucket ALIAS bucket-name
```
Do not treat S3 as a shell filesystem.
## Grafana / Prometheus / WinRM
```bash
$HOME/.server-connections/gemini-ssh --grafana-dashboards ALIAS
$HOME/.server-connections/gemini-ssh --grafana-alerts ALIAS
$HOME/.server-connections/gemini-ssh --prom-query ALIAS "up"
$HOME/.server-connections/gemini-ssh --prom-targets ALIAS
$HOME/.server-connections/gemini-ssh --prom-alerts ALIAS
$HOME/.server-connections/gemini-ssh --ps ALIAS "Get-Process"
$HOME/.server-connections/gemini-ssh --cmd ALIAS "dir"
```

View File

@@ -0,0 +1,73 @@
# Project Notes
This skill is based on `/home/code/Desktop/CODING/server-manager`.
## What ServerManager Is
ServerManager is a cross-platform desktop GUI built with CustomTkinter. It manages multiple remote endpoint types through one local encrypted inventory:
- SSH / Telnet
- MariaDB / MSSQL / PostgreSQL
- Redis
- S3 / MinIO
- Grafana
- Prometheus
- WinRM
- RDP / VNC launchers
## Core Integration Model
The GUI and CLI share one local backend:
```text
ServerManager GUI <-> ~/.server-connections/servers.json <-> ~/.server-connections/ssh.py
```
The AI never needs raw credentials. It only uses aliases and the local CLI.
## Important Files
- `README.md`: product overview and install flow
- `CLAUDE.md`: project rules, architecture, security, workflow
- `GEMINI.md`: Gemini-native project contract
- `tools/ssh.py`: CLI entry point used by AI tools
- `tools/skill-ssh.md`: current Claude `/ssh` instructions
- `core/claude_setup.py`: installer for shared CLI files plus Claude/Codex/Gemini skill deployment
- `build.py`: auto-deploys `ssh.py`, `encryption.py`, Claude skill, Codex skill, and Gemini skill after builds
## Architectural Shape
- `core/server_store.py`: encrypted storage, CRUD, observers, backups
- `core/connection_factory.py`: type-to-client factory with lazy imports
- `core/*_client.py`: protocol-specific backends
- `gui/app.py`: tab registry, conditional tabs by server type
- `gui/tabs/`: protocol-specific GUI surfaces
## Existing Local Agent Integration
Current setup installs:
- `~/.server-connections/ssh.py`
- `~/.server-connections/encryption.py`
- `~/.claude/commands/ssh.md`
- `~/.codex/skills/server-manager/`
- `~/.gemini/skills/server-manager/`
- `~/.agents/skills/server-manager/` (cross-tool mirror)
- `~/.server-connections/codex-ssh` or `codex-ssh.cmd`
- `~/.server-connections/gemini-ssh` or `gemini-ssh.cmd`
- a `~/.claude/CLAUDE.md` guidance block
- a `~/.gemini/GEMINI.md` guidance block
The Gemini skill mirrors the same safety model:
- use aliases only
- use the shared local CLI
- never read credentials directly
- choose commands by server type
## Local Findings
- `ssh.py` is executable and uses a `python3` shebang, so Gemini does not need a `python` alias.
- `ssh.py` has no `--help`; use `--list`, `--info`, and `--status` for safe discovery.
- The Unix wrapper path covers both Linux and macOS through `gemini-ssh-wrapper.sh`.
- Windows-native Gemini wrapper support exists through `gemini-ssh-wrapper.cmd`.

View File

@@ -0,0 +1,11 @@
@echo off
setlocal
set SHARED_DIR=%SERVER_MANAGER_SHARED_DIR%
if "%SHARED_DIR%"=="" set SHARED_DIR=%USERPROFILE%\.server-connections
set SSH_SCRIPT=%SHARED_DIR%\ssh.py
if not exist "%SSH_SCRIPT%" (
echo error: missing executable ssh.py at %SSH_SCRIPT% 1>&2
echo hint: install ServerManager's shared CLI files first 1>&2
exit /b 1
)
"%SSH_SCRIPT%" %*

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
shared_dir="${SERVER_MANAGER_SHARED_DIR:-$HOME/.server-connections}"
ssh_script="${shared_dir}/ssh.py"
if [[ ! -x "$ssh_script" ]]; then
echo "error: missing executable ssh.py at ${ssh_script}" >&2
echo "hint: install ServerManager's shared CLI files first" >&2
exit 1
fi
exec "$ssh_script" "$@"

View File

@@ -0,0 +1,39 @@
@echo off
setlocal
set SHARED_DIR=%SERVER_MANAGER_SHARED_DIR%
if "%SHARED_DIR%"=="" set SHARED_DIR=%USERPROFILE%\.server-connections
set SSH_SCRIPT=%SHARED_DIR%\ssh.py
set ENCRYPTION=%SHARED_DIR%\encryption.py
set WRAPPER=%SHARED_DIR%\gemini-ssh.cmd
set SKILL=%USERPROFILE%\.gemini\skills\server-manager\SKILL.md
set STATUS=0
if exist "%ENCRYPTION%" (
echo [ok] file %ENCRYPTION%
) else (
echo [missing] file %ENCRYPTION% 1>&2
set STATUS=1
)
if exist "%SSH_SCRIPT%" (
echo [ok] file %SSH_SCRIPT%
) else (
echo [missing] file %SSH_SCRIPT% 1>&2
set STATUS=1
)
if exist "%WRAPPER%" (
echo [ok] file %WRAPPER%
) else (
echo [missing] file %WRAPPER% 1>&2
set STATUS=1
)
if exist "%SKILL%" (
echo [ok] file %SKILL%
) else (
echo [missing] file %SKILL% 1>&2
set STATUS=1
)
exit /b %STATUS%

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
shared_dir="${SERVER_MANAGER_SHARED_DIR:-$HOME/.server-connections}"
ssh_script="${shared_dir}/ssh.py"
encryption_module="${shared_dir}/encryption.py"
wrapper="${shared_dir}/gemini-ssh"
skill_dir="$HOME/.gemini/skills/server-manager"
status=0
check_file() {
local path="$1"
if [[ -f "$path" ]]; then
printf '[ok] file %s\n' "$path"
else
printf '[missing] file %s\n' "$path" >&2
status=1
fi
}
check_exec() {
local path="$1"
if [[ -x "$path" ]]; then
printf '[ok] executable %s\n' "$path"
else
printf '[missing] executable %s\n' "$path" >&2
status=1
fi
}
check_file "$encryption_module"
check_exec "$ssh_script"
check_exec "$wrapper"
check_file "$skill_dir/SKILL.md"
exit "$status"

View File

@@ -1,5 +1,15 @@
# Changelog
## [Unreleased]
### Added
- Add Codex integration to Setup tab with dedicated install buttons and status rows
- Add packaged Codex skill deployment and local `codex-ssh` wrapper installation
- Add Windows `.cmd` wrappers for Codex skill installation/runtime
### Changed
- Extend `build.py` auto-deploy to sync Codex artifacts alongside Claude artifacts
- Update docs and i18n strings to describe Claude Code + Codex setup flow
## [1.8.24] - 2026-02-24

409
CODEX_SKILL_SETUP.md Normal file
View File

@@ -0,0 +1,409 @@
# Развёртывание Codex Skill Для ServerManager
Этот документ описывает текущее состояние интеграции `ServerManager -> Codex`, автоматическое и ручное развёртывание, проверку и все известные edge cases.
Поддерживаемый deployment target для этой интеграции:
- Linux
- macOS
- Windows
## Что именно разворачивается
Интеграция для Codex состоит из трёх слоёв:
1. Общий локальный backend:
- `~/.server-connections/ssh.py`
- `~/.server-connections/encryption.py`
- `~/.server-connections/servers.json`
2. Codex skill package:
- `~/.codex/skills/server-manager/`
3. Безопасный wrapper для вызова backend из Codex:
- `~/.server-connections/codex-ssh` на Linux/macOS
- `~/.server-connections/codex-ssh.cmd` на Windows
В репозитории исходники skill лежат здесь:
- [SKILL.md](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/SKILL.md)
- [command-matrix.md](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/references/command-matrix.md)
- [project.md](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/references/project.md)
- [server-manager-doctor.sh](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/scripts/server-manager-doctor.sh)
- [server-manager-doctor.cmd](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/scripts/server-manager-doctor.cmd)
- [codex-ssh-wrapper.sh](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/scripts/codex-ssh-wrapper.sh)
- [codex-ssh-wrapper.cmd](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/scripts/codex-ssh-wrapper.cmd)
## Как это работает
Модель безопасности та же, что и у Claude integration:
```text
Codex skill -> ~/.server-connections/codex-ssh -> ~/.server-connections/ssh.py -> encrypted servers.json
```
Ключевая идея:
- Codex видит только алиасы и безопасные результаты команд.
- `ssh.py` сам читает credentials из локального зашифрованного хранилища.
- Codex не должен читать `servers.json`, `settings.json` или `encryption.py` напрямую.
## Что уже автоматизировано
Теперь Codex integration встроена в продуктовый setup flow:
- `core/claude_setup.py` ставит `ssh.py`, `encryption.py`, `~/.claude/commands/ssh.md`, `~/.codex/skills/server-manager/`, wrapper `~/.server-connections/codex-ssh` и блок в `~/.claude/CLAUDE.md`
- вкладка `Setup` в GUI показывает отдельные статусы для Claude skill, Codex skill и Codex wrapper
- `build.py` после сборки автоматически синхронизирует Claude- и Codex-артефакты в локальный runtime
Платформенный split такой:
- Linux/macOS: используются `codex-ssh-wrapper.sh` и `server-manager-doctor.sh`
- Windows: используются `codex-ssh-wrapper.cmd` и `server-manager-doctor.cmd`
Ручная установка всё ещё полезна как fallback path, если нужен точечный repair или offline debugging.
## Предварительные условия
Перед установкой Codex skill должны уже существовать или быть установлены через `Setup`:
1. `~/.server-connections/ssh.py`
2. `~/.server-connections/encryption.py`
3. `~/.server-connections/servers.json`
4. `codex` CLI
Проверка:
```bash
ls -la ~/.server-connections
codex --help
```
Если `~/.server-connections/ssh.py` отсутствует:
1. Открыть ServerManager GUI
2. Перейти в `Setup`
3. Нажать `Install Everything`
Это поставит backend, Claude skill и Codex skill целиком.
## Рекомендуемый путь: установка через GUI
1. Открыть ServerManager
2. Перейти в `Setup`
3. Нажать `Install Everything`
4. Проверить, что зелёные статусы появились у:
- `ssh.py`
- `Encryption module`
- `Claude /ssh skill`
- `Codex skill`
- `Codex wrapper`
- `SSH key`
Для точечного ремонта можно использовать отдельные кнопки `Claude skill` и `Codex skill` в той же вкладке.
## Ручная установка Codex Skill
### 1. Скопировать skill package в глобальный Codex home
```bash
mkdir -p ~/.codex/skills
cp -R .codex/skills/server-manager ~/.codex/skills/server-manager
```
### 2. Установить wrapper в shared runtime directory
Linux/macOS:
```bash
install -m 755 .codex/skills/server-manager/scripts/codex-ssh-wrapper.sh ~/.server-connections/codex-ssh
```
Windows:
```bat
copy .codex\skills\server-manager\scripts\codex-ssh-wrapper.cmd %USERPROFILE%\.server-connections\codex-ssh.cmd
```
### 3. Проверить doctor script
Linux/macOS:
```bash
~/.codex/skills/server-manager/scripts/server-manager-doctor.sh
```
Windows:
```bat
%USERPROFILE%\.codex\skills\server-manager\scripts\server-manager-doctor.cmd
```
Ожидается:
- `ssh.py` найден
- `encryption.py` найден
- `codex-ssh` executable
### 4. Проверить wrapper без раскрытия credentials
Linux/macOS:
```bash
~/.server-connections/codex-ssh --list
```
Windows:
```bat
%USERPROFILE%\.server-connections\codex-ssh.cmd --list
```
Это безопасная базовая проверка. Она должна вывести список алиасов и типов серверов.
### 5. Перезапустить Codex
Если у вас уже была открыта интерактивная Codex session, её нужно перезапустить. Новый skill обычно подхватывается новым процессом Codex, а не уже живой сессией.
## Как проверить, что Codex реально видит skill
Самый надёжный способ:
```bash
codex exec --skip-git-repo-check -s read-only -C /tmp \
"A user asks: Using the locally installed ServerManager integration, what is the safest first command to enumerate configured servers? Reply with only the command."
```
Если skill подхватился корректно, Codex должен сам прочитать `~/.codex/skills/server-manager/SKILL.md` и ответить:
```bash
$HOME/.server-connections/codex-ssh --list
```
## Что Codex должен делать через skill
Правильный workflow для любой server operation:
1. Сначала `--list`
2. Прочитать колонку `Type`
3. Выбрать команду строго по типу сервера
4. Выполнить ровно одно подключение/одно действие
5. Вернуть результат без IP/логинов/паролей/портов
Безопасные discovery-команды:
```bash
$HOME/.server-connections/codex-ssh --list
$HOME/.server-connections/codex-ssh --info ALIAS
$HOME/.server-connections/codex-ssh --status
```
## Источники истины по интеграции
Если меняется поведение интеграции, проверять нужно в таком порядке:
1. [tools/ssh.py](/home/code/Desktop/CODING/server-manager/tools/ssh.py)
2. [tools/skill-ssh.md](/home/code/Desktop/CODING/server-manager/tools/skill-ssh.md)
3. [core/claude_setup.py](/home/code/Desktop/CODING/server-manager/core/claude_setup.py)
4. [build.py](/home/code/Desktop/CODING/server-manager/build.py)
5. [SKILL.md](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/SKILL.md)
6. [command-matrix.md](/home/code/Desktop/CODING/server-manager/.codex/skills/server-manager/references/command-matrix.md)
Если меняется семантика `ssh.py`, нужно обновлять и Claude skill, и Codex skill.
## Edge Cases
### 1. `python` alias отсутствует
В этой среде `python` отсутствует, но `ssh.py` имеет shebang `#!/usr/bin/env python3` и executable bit.
Поэтому wrapper вызывает `ssh.py` напрямую:
```bash
~/.server-connections/codex-ssh --list
```
Это намеренно лучше, чем завязка на `python ~/.server-connections/ssh.py`.
### 2. `ssh.py --help` не поддерживается
`ssh.py` не имеет полноценного `--help`. Попытка вызвать `--help` возвращает список доступных alias'ов, а не usage.
Поэтому для безопасной проверки используются:
- `--list`
- `--info ALIAS`
- `--status`
### 3. Skill установлен в repo, но не установлен глобально
Наличие `.codex/skills/server-manager/` внутри репозитория полезно как source of truth, но новый Codex процесс по умолчанию ищет глобальные skills в `~/.codex/skills`.
Если skill есть только в repo:
- документация в проекте будет на месте
- глобальный Codex может его не увидеть
Для надёжности нужен именно глобальный install в `~/.codex/skills/server-manager`.
### 4. Wrapper отсутствует, а skill уже установлен
В этом случае Codex прочитает skill, но не сможет выполнить рекомендуемую команду `$HOME/.server-connections/codex-ssh ...`.
Проверка:
```bash
~/.codex/skills/server-manager/scripts/server-manager-doctor.sh
```
Исправление:
```bash
install -m 755 .codex/skills/server-manager/scripts/codex-ssh-wrapper.sh ~/.server-connections/codex-ssh
```
### 5. Backend отсутствует
Если нет `~/.server-connections/ssh.py` или `encryption.py`, skill бесполезен: он знает workflow, но не имеет локального transport layer.
Исправление:
1. Запустить ServerManager
2. `Setup -> Install Everything`
### 6. Интерактивный Codex уже был запущен до установки skill
Новая интерактивная сессия обычно увидит skill, старая может не увидеть.
Исправление:
- закрыть старую Codex session
- запустить новый процесс `codex`
### 7. `codex exec` не может проверить skill из-за sandbox/network policy
Во время non-interactive проверки Codex может упереться не в skill, а в сетевую политику среды:
- websocket backend заблокирован
- sandbox запрещает соединение
Симптом:
```text
failed to connect to websocket ... Operation not permitted
```
Это не означает, что skill неверный. Это означает, что проверка упёрлась в runtime policy Codex backend.
### 8. Повторная установка поверх существующего skill
GUI installer и `install_codex_skill()` синхронизируют дерево skill поверх существующей директории без полного удаления. Это безопасно для обычных обновлений.
Но при ручном `cp -R` возможен stale state, если какие-то файлы были удалены из repo, а старая глобальная копия осталась.
Полная ручная пересинхронизация нужна только если вы осознанно хотите очистить старые файлы:
1. удалить старую копию осознанно
2. снова скопировать skill целиком
Пример:
```bash
rm -rf ~/.codex/skills/server-manager
cp -R .codex/skills/server-manager ~/.codex/skills/server-manager
```
Делать это только если вы уверены, что хотите полностью пересобрать глобальную копию.
### 9. Изменился `tools/ssh.py`, но глобальная установка осталась старой
Это самый вероятный operational drift.
Что может устареть:
- `~/.server-connections/ssh.py`
- `~/.codex/skills/server-manager/*`
- `~/.server-connections/codex-ssh`
После изменения `tools/ssh.py` или skill docs нужно заново синхронизировать:
```bash
cp tools/ssh.py ~/.server-connections/ssh.py
cp core/encryption.py ~/.server-connections/encryption.py
rm -rf ~/.codex/skills/server-manager
cp -R .codex/skills/server-manager ~/.codex/skills/server-manager
install -m 755 .codex/skills/server-manager/scripts/codex-ssh-wrapper.sh ~/.server-connections/codex-ssh
```
### 10. Windows / macOS / Linux split
Сейчас runtime path intentionally разделён по платформам:
- Linux/macOS: shell wrapper `codex-ssh-wrapper.sh`
- Windows: native wrapper `codex-ssh-wrapper.cmd`
Installer на Windows кладёт wrapper как:
- `~/.server-connections/codex-ssh.cmd`
Installer на Linux/macOS кладёт wrapper как:
- `~/.server-connections/codex-ssh`
Что это закрывает:
- Linux/macOS path без platform-specific разветвления в skill
- запуск через `cmd.exe`
- запуск из PowerShell
- отсутствие bash-зависимости для стандартного Windows deployment path
Ограничение остаётся одно: в текущей среде я прогнал end-to-end smoke только на Linux. macOS и Windows path подготовлены в installer/docs, но не smoke-tested здесь из-за отсутствия соответствующих runner'ов.
### 11. `ssh.py` intentionally не должен читать secrets в контекст AI
Это не баг. Даже если кажется проще открыть `servers.json`, делать этого нельзя.
Skill намеренно запрещает:
- `cat ~/.server-connections/servers.json`
- `cat ~/.server-connections/settings.json`
- `python -c "...read servers.json..."`
- `--list-full`
### 12. fail2ban / anti-bruteforce edge case
Повторные неудачные подключения опасны. Поэтому skill зафиксирован как:
- максимум 1 попытка на действие
- при timeout/ошибке остановиться и сообщить пользователю
Это обязательное правило, а не рекомендация.
## Рекомендуемый Update Workflow
После любых изменений, затрагивающих Codex integration:
1. Обновить исходники в repo:
- `tools/ssh.py`
- `.codex/skills/server-manager/*`
- этот документ
2. Синхронизировать глобальную установку
3. Прогнать doctor
4. Прогнать `~/.server-connections/codex-ssh --list`
5. Прогнать свежий `codex exec` smoke test
## Минимальный Smoke Test
```bash
~/.codex/skills/server-manager/scripts/server-manager-doctor.sh
~/.server-connections/codex-ssh --list
codex exec --skip-git-repo-check -s read-only -C /tmp \
"A user asks: Using the locally installed ServerManager integration, what is the safest first command to enumerate configured servers? Reply with only the command."
```
## Что ещё желательно автоматизировать позже
Чтобы интеграция стала production-complete, в проект ещё полезно добавить:
1. отдельный smoke test script внутри репозитория для проверки именно Codex integration
2. e2e smoke test на Windows runner
3. e2e smoke test на macOS runner
4. optional PowerShell-native wrapper, если понадобится richer Windows logging

52
GEMINI.md Normal file
View File

@@ -0,0 +1,52 @@
# Gemini Project Contract
This repository is **ServerManager** — a cross-platform desktop GUI for managing remote servers and services through one encrypted local inventory.
Use this file as the native Gemini contract for sessions started inside this repository.
## First Read
Read these files first when relevant:
- `README.md`
- `CLAUDE.md`
- `CHANGELOG.md`
- `core/claude_setup.py`
- `tools/ssh.py`
## Default Role
- Gemini is a secondary implementation and review helper, not the owner of the project state.
- Prefer minimal safe changes that preserve Claude and Codex integration behavior.
- When changing AI integration code, keep Claude `/ssh`, Codex `server-manager`, and Gemini `server-manager` behavior aligned.
## Project-Specific Rules
- Never read or print secrets from `~/.server-connections/servers.json`, `settings.json`, or `encryption.py`.
- Treat `tools/ssh.py` as the shared transport layer for Claude, Codex, and Gemini.
- Keep cross-platform behavior explicit for Linux, macOS, and Windows.
- Prefer shared installer logic in `core/claude_setup.py` over duplicated per-tool logic.
- If command semantics change in `tools/ssh.py`, update all relevant user-facing skill docs.
## Native Gemini Entry Points
- Project contract: `GEMINI.md`
- Gemini settings: `.gemini/settings.json`
- Workspace skill: `.gemini/skills/server-manager/`
## Safe Server Workflow
When the user asks to operate on a server already configured in ServerManager:
1. Use the installed ServerManager Gemini skill.
2. First enumerate aliases safely.
3. Determine endpoint type before choosing a command.
4. Use the shared CLI wrapper, not raw credentials.
Preferred discovery commands:
```bash
$HOME/.server-connections/gemini-ssh --list
$HOME/.server-connections/gemini-ssh --info ALIAS
$HOME/.server-connections/gemini-ssh --status
```

86
GEMINI_SKILL_SETUP.md Normal file
View File

@@ -0,0 +1,86 @@
# Развёртывание Gemini Skill Для ServerManager
Этот документ описывает, как ServerManager интегрируется с Gemini CLI.
## Что устанавливается
Для каждого target home устанавливаются:
1. Общий backend:
- `~/.server-connections/ssh.py`
- `~/.server-connections/encryption.py`
2. Gemini skill package:
- `~/.gemini/skills/server-manager/`
3. Безопасный runtime wrapper:
- `~/.server-connections/gemini-ssh`
4. Глобальный Gemini context:
- `~/.gemini/GEMINI.md`
## Skill workflow
Gemini должен начинать discovery так:
```bash
$HOME/.server-connections/gemini-ssh --list
```
Далее:
- определить `Type`
- выбрать команду строго по типу
- выполнить ровно одно действие
- не раскрывать IP, логины, пароли, порты
## Рекомендуемая установка
### Через GUI
Вкладка `Setup` теперь умеет ставить:
- Claude skill
- Codex skill
- Gemini skill
- shared backend и wrappers
### Через Python installer
```bash
python3 tools/install_ai_integrations.py
python3 tools/install_ai_integrations.py --target-home /root
python3 tools/install_ai_integrations.py --all-users
```
### Через shell installer (Linux/macOS)
```bash
bash tools/install.sh --source-dir /path/to/server-manager
bash tools/install.sh --source-dir /path/to/server-manager --target-home /root
bash tools/install.sh --source-dir /path/to/server-manager --all-users
```
## Проверка
### 1. Проверить skill discovery
```bash
gemini skills list
```
### 2. Проверить wrapper
```bash
$HOME/.server-connections/gemini-ssh --list
```
### 3. Проверить doctor script
```bash
$HOME/.gemini/skills/server-manager/scripts/server-manager-gemini-doctor.sh
```
## Важные замечания
- `servers.json` не размножается автоматически в `--all-users` режиме — это сделано намеренно, чтобы не копировать credentials между пользователями.
- Для root / service accounts используйте отдельную установку в нужный `target home`.
- Gemini skill source в репозитории лежит в `.gemini/skills/server-manager/`.
- При необходимости можно дополнительно ставить mirror в `~/.agents/skills/server-manager/`, но по умолчанию это отключено, чтобы Gemini не ругался на duplicate skill conflict.

View File

@@ -2,7 +2,7 @@
<p align="center">
<strong>Desktop GUI for managing remote servers</strong><br>
CustomTkinter + Paramiko | Dark Theme | Claude Code Integration
CustomTkinter + Paramiko | Dark Theme | Claude Code + Codex + Gemini Integration
</p>
<p align="center">
@@ -22,7 +22,7 @@
- **SFTP Transfer** — upload/download files with progress bar
- **SSH Keys** — generate ed25519, install on server, copy to clipboard
- **Status Monitor** — background check every 60 sec (online/offline badges)
- **Claude Code Integration** — one-click setup, shared config with `/ssh` skill
- **Claude Code + Codex + Gemini Integration** — one-click setup, shared config with `/ssh`, Codex skill, and Gemini skill
- **TOTP / 2FA** — Google Authenticator compatible codes with live countdown, one-click copy
- **Encryption** — servers.json encrypted with Fernet (passwords never stored in plaintext)
- **Backups** — manual and automatic backups with one-click restore
@@ -62,23 +62,25 @@ Output goes to `releases/ServerManager-vX.Y.Z-{platform}.exe`
3. **Terminal** — select server → Terminal tab → type command → Run
4. **Files** — select server → Files tab → set paths → Upload/Download
5. **Keys** — Keys tab → Generate Key → Install on Server
6. **Setup** — Setup tab → "Install Everything" → Claude Code ready
6. **Setup** — Setup tab → "Install Everything" → Claude Code, Codex, and Gemini ready
7. Status badges update automatically (green = online, red = offline)
### Claude Code Integration
### Claude Code + Codex + Gemini Integration
ServerManager and Claude Code share the same config file: `~/.server-connections/servers.json`
ServerManager, Claude Code, and Codex share the same config file: `~/.server-connections/servers.json`
For Codex deployment and operational edge cases, see [`CODEX_SKILL_SETUP.md`](CODEX_SKILL_SETUP.md).
**How it works:**
```
ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py (Claude Code)
ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py backend
↕ ↕
Add/edit/delete /ssh skill
servers in GUI executes commands
Add/edit/delete Claude /ssh + Codex + Gemini skill
servers in GUI execute commands
```
- Add a server in GUI → Claude Code sees it immediately via `/ssh list`
- Both use the same `ssh.py` + `servers.json`
- Add a server in GUI → Claude Code, Codex, and Gemini see it immediately
- Both agents use the same `ssh.py` + `servers.json`
- Passwords **never** pass through the AI API
**New SSH Commands:**
@@ -93,12 +95,17 @@ ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py (C
**Setup on a new machine:**
1. Install ServerManager (clone repo or download binary)
2. Open Setup tab → click "Install Everything"
3. Done. Claude Code now has `/ssh` skill and access to your servers
3. Done. Claude Code now has `/ssh`, Codex now has the `server-manager` skill, and Gemini now has the `server-manager` skill with access to your servers
The Setup tab installs:
- `ssh.py``~/.server-connections/` (SSH utility)
- `encryption.py``~/.server-connections/` (encryption module for CLI)
- `/ssh` skill → `~/.claude/commands/ssh.md` (Claude Code skill)
- `server-manager` skill → `~/.codex/skills/server-manager/` (Codex skill package)
- `server-manager` skill → `~/.gemini/skills/server-manager/` (Gemini skill package)
- optional mirror → `~/.agents/skills/server-manager/` (off by default to avoid Gemini duplicate-skill warnings)
- `codex-ssh` wrapper → `~/.server-connections/` (safe Codex entry point)
- `gemini-ssh` wrapper → `~/.server-connections/` (safe Gemini entry point)
- SSH key (ed25519) — if not exists
- Checks for duplicates — safe to run multiple times
@@ -140,7 +147,7 @@ App executes: sudo -S -p '' bash -c 'systemctl restart nginx'
- Passwords stored locally only, **never sent to any AI/API**
- SSH keys (ed25519) — recommended auth method
- sudo password sent via stdin (not visible in process list)
- When used with Claude Code: only alias + command are passed through the AI API, passwords stay in the local encrypted file
- When used with Claude Code or Codex: only alias + command are passed through the AI API, passwords stay in the local encrypted file
- Automatic pre-encryption backup on first migration
### Project Structure
@@ -154,7 +161,7 @@ ServerManager/
│ ├── server_store.py # CRUD + encrypted JSON + observer + backups
│ ├── encryption.py # Fernet encryption module
│ ├── ssh_client.py # Paramiko SSH/SFTP wrapper
│ ├── claude_setup.py # Claude Code integration installer
│ ├── claude_setup.py # Claude Code + Codex + Gemini integration installer
│ ├── status_checker.py # Background monitoring
│ ├── totp.py # TOTP/2FA module (pyotp)
│ ├── logger.py # Rotating file logger
@@ -166,7 +173,7 @@ ServerManager/
│ ├── tabs/ # Terminal, Files, Info, Keys, Setup
│ └── widgets/ # StatusBadge
├── tools/ # CLI tools (installed to ~/.server-connections/)
│ ├── ssh.py # SSH utility for Claude Code
│ ├── ssh.py # SSH utility for Claude Code / Codex
│ └── skill-ssh.md # /ssh skill template
├── config/ # Example configs
├── releases/ # Built executables
@@ -187,7 +194,7 @@ pip install -r requirements.txt
python main.py
# → Setup tab → Install Everything
# → Add your servers via + Add
# → Done! Both GUI and Claude Code are ready
# → Done! GUI, Claude Code, and Codex are ready
```
---
@@ -201,7 +208,7 @@ python main.py
- **SFTP** — загрузка и скачивание файлов с прогресс-баром
- **SSH-ключи** — генерация ed25519, установка на сервер, копирование
- **Мониторинг** — фоновая проверка каждые 60 сек (бейджи online/offline)
- **Интеграция с Claude Code** — установка в один клик, общий конфиг со скиллом `/ssh`
- **Интеграция с Claude Code + Codex + Gemini** — установка в один клик, общий конфиг со скиллом `/ssh`, Codex skill и Gemini skill
- **TOTP / 2FA** — коды Google Authenticator с обратным отсчётом, копирование в один клик
- **Шифрование** — servers.json зашифрован Fernet (пароли не хранятся в открытом виде)
- **Бэкапы** — ручные и автоматические с восстановлением в один клик
@@ -241,23 +248,23 @@ python build.py
3. **Терминал** — выберите сервер → вкладка Terminal → введите команду → Run
4. **Файлы** — выберите сервер → вкладка Files → укажите пути → Upload/Download
5. **Ключи** — вкладка Keys → Generate Key → Install on Server
6. **Настройка Claude** — вкладка Setup → "Install Everything" → Claude Code готов
6. **Настройка** — вкладка Setup → "Install Everything" → Claude Code, Codex и Gemini готовы
7. Бейджи статуса обновляются автоматически (зелёный = online, красный = offline)
### Интеграция с Claude Code
### Интеграция с Claude Code + Codex + Gemini
ServerManager и Claude Code используют **один и тот же файл конфигурации**: `~/.server-connections/servers.json`
ServerManager, Claude Code, Codex и Gemini используют **один и тот же файл конфигурации**: `~/.server-connections/servers.json`
**Как это работает:**
```
ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py (Claude Code)
ServerManager GUI ←→ ~/.server-connections/servers.json ←→ backend ssh.py
↕ ↕
Добавил/изменил скилл /ssh
сервер в GUI выполняет команды
Добавил/изменил Claude /ssh + Codex + Gemini skill
серверы в GUI выполняют команды
```
- Добавил сервер в GUI → Claude Code сразу видит его через `/ssh list`
- Оба используют один `ssh.py` + `servers.json`
- Добавил сервер в GUI → Claude Code, Codex и Gemini сразу видят его
- Оба агента используют один `ssh.py` + `servers.json`
- Пароли **никогда** не проходят через API нейронки
**Новые SSH команды:**
@@ -272,12 +279,16 @@ ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py (C
**Настройка на новой машине:**
1. Установить ServerManager (клонировать репо или скачать бинарник)
2. Открыть вкладку Setup → нажать "Install Everything"
3. Готово. Claude Code теперь имеет скилл `/ssh` и доступ к серверам
3. Готово. Claude Code получает скилл `/ssh`, а Codex и Gemini получают skill `server-manager` и доступ к серверам
Вкладка Setup устанавливает:
- `ssh.py``~/.server-connections/` (SSH-утилита)
- `encryption.py``~/.server-connections/` (модуль шифрования для CLI)
- скилл `/ssh``~/.claude/commands/ssh.md` (скилл Claude Code)
- skill `server-manager``~/.codex/skills/server-manager/` (скилл Codex)
- skill `server-manager``~/.gemini/skills/server-manager/` (скилл Gemini)
- wrapper `codex-ssh``~/.server-connections/` (безопасная точка входа для Codex)
- wrapper `gemini-ssh``~/.server-connections/` (безопасная точка входа для Gemini)
- SSH-ключ (ed25519) — если ещё не создан
- Проверяет дубли — безопасно запускать повторно
@@ -319,7 +330,7 @@ ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py (C
- Пароли хранятся только локально, **никогда не передаются в AI/API**
- SSH-ключи (ed25519) — рекомендуемый метод аутентификации
- sudo-пароль передаётся через stdin (не виден в списке процессов)
- При использовании с Claude Code: через API нейронки проходят только alias + команда, пароли остаются в зашифрованном локальном файле
- При использовании с Claude Code или Codex: через API нейронки проходят только alias + команда, пароли остаются в зашифрованном локальном файле
- Автоматический пред-шифровальный бэкап при первой миграции
### Развёртывание на новой машине
@@ -336,7 +347,7 @@ pip install -r requirements.txt
python main.py
# → Вкладка Setup → Install Everything
# → Добавить серверы через + Add
# → Готово! GUI и Claude Code работают с одним конфигом
# → Готово! GUI, Claude Code, Codex и Gemini работают с одним конфигом
```
---
@@ -350,7 +361,7 @@ python main.py
- **SFTP传输** — 带进度条的文件上传/下载
- **SSH密钥** — 生成ed25519、安装到服务器、复制到剪贴板
- **状态监控** — 每60秒后台检查在线/离线徽标)
- **Claude Code集成** — 一键设置,与`/ssh`技能共享配置
- **Claude Code + Codex + Gemini 集成** — 一键设置,与 `/ssh` 技能、Codex skill 和 Gemini skill 共享配置
- **TOTP / 2FA** — 兼容Google Authenticator的验证码实时倒计时一键复制
- **加密** — servers.json使用Fernet加密密码不再以明文存储
- **备份** — 手动和自动备份,一键恢复
@@ -390,34 +401,36 @@ python build.py
3. **终端** — 选择服务器 → Terminal标签 → 输入命令 → Run
4. **文件** — 选择服务器 → Files标签 → 设置路径 → Upload/Download
5. **密钥** — Keys标签 → Generate Key → Install on Server
6. **设置Claude** — Setup标签 → "Install Everything" → Claude Code就绪
6. **设置** — Setup标签 → "Install Everything" → Claude Code 和 Codex 就绪
7. 状态徽标自动更新(绿色 = 在线,红色 = 离线)
### Claude Code集成
### Claude Code + Codex + Gemini 集成
ServerManagerClaude Code共享**同一个配置文件**`~/.server-connections/servers.json`
ServerManagerClaude Code 和 Codex 共享**同一个配置文件**`~/.server-connections/servers.json`
**工作原理:**
```
ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py (Claude Code)
ServerManager GUI ←→ ~/.server-connections/servers.json ←→ ssh.py 后端
↕ ↕
在GUI中添加/编辑 /ssh技能
在GUI中添加/编辑 Claude /ssh + Codex + Gemini skill
服务器 执行命令
```
- 在GUI中添加服务器 → Claude Code立即通过 `/ssh list` 看到
- 两者使用相同的 `ssh.py` + `servers.json`
- 在GUI中添加服务器 → Claude Code 和 Codex 都会立即看到
- Claude Code、Codex 和 Gemini 都使用同一个 `ssh.py` + `servers.json`
- 密码**绝不**通过AI API传递
**在新机器上设置:**
1. 安装ServerManager克隆仓库或下载二进制文件
2. 打开Setup标签 → 点击 "Install Everything"
3. 完成。Claude Code现在拥有 `/ssh` 技能并可访问您的服务器
3. 完成。Claude Code 现在拥有 `/ssh` 技能Codex 现在拥有 `server-manager` 技能并可访问您的服务器
Setup标签安装
- `ssh.py``~/.server-connections/`SSH工具
- `encryption.py``~/.server-connections/`CLI加密模块
- `/ssh` 技能 → `~/.claude/commands/ssh.md`Claude Code技能
- `server-manager` 技能 → `~/.codex/skills/server-manager/`Codex技能包
- `codex-ssh` 包装器 → `~/.server-connections/`Codex安全入口
- SSH密钥ed25519— 如果不存在
- 检查重复 — 可安全重复运行
@@ -459,7 +472,7 @@ Setup标签安装
- 密码仅存储在本地,**绝不发送到任何AI/API**
- SSH密钥ed25519— 推荐的认证方式
- sudo密码通过stdin传递在进程列表中不可见
- 与Claude Code配合使用时只有别名和命令通过AI API传递密码保留在本地加密文件中
- Claude Code 或 Codex 配合使用时:只有别名和命令通过 AI API 传递,密码保留在本地加密文件中
- 首次迁移时自动创建加密前备份
### 在新机器上部署
@@ -476,7 +489,7 @@ pip install -r requirements.txt
python main.py
# → Setup标签 → Install Everything
# → 通过 + Add 添加服务器
# → 完成GUIClaude Code使用同一个配置
# → 完成GUIClaude Code 和 Codex 使用同一个配置
```
---

View File

@@ -114,7 +114,12 @@ def build():
"--add-data", f"config/servers.example.json{os.pathsep}config",
"--add-data", f"tools/ssh.py{os.pathsep}tools",
"--add-data", f"tools/skill-ssh.md{os.pathsep}tools",
"--add-data", f"tools/install_ai_integrations.py{os.pathsep}tools",
"--add-data", f"core/encryption.py{os.pathsep}core",
"--add-data", f".codex/skills/server-manager{os.pathsep}.codex/skills/server-manager",
"--add-data", f".gemini/skills/server-manager{os.pathsep}.gemini/skills/server-manager",
"--add-data", f".gemini/settings.json{os.pathsep}.gemini",
"--add-data", f"GEMINI.md{os.pathsep}.",
]
# PNG icons for GUI (Material Design)
@@ -350,34 +355,34 @@ def cleanup_old_releases():
def deploy_shared_files():
"""Auto-deploy ssh.py, encryption.py, skill to shared dirs after build.
"""Auto-deploy shared CLI files and local agent integrations after build."""
from core.claude_setup import (
install_claude_skill,
install_codex_skill,
install_gemini_skill,
install_ssh_script,
)
Ensures Claude Code /ssh skill always uses the latest version.
Without this, editing tools/ssh.py updates the exe but NOT the live
~/.server-connections/ssh.py that Claude Code actually calls.
"""
shared_dir = os.path.expanduser("~/.server-connections")
skill_dir = os.path.expanduser("~/.claude/commands")
deploy_map = [
(os.path.join(PROJECT_DIR, "tools", "ssh.py"),
os.path.join(shared_dir, "ssh.py")),
(os.path.join(PROJECT_DIR, "core", "encryption.py"),
os.path.join(shared_dir, "encryption.py")),
(os.path.join(PROJECT_DIR, "tools", "skill-ssh.md"),
os.path.join(skill_dir, "ssh.md")),
deploy_steps = [
install_ssh_script,
install_claude_skill,
install_codex_skill,
install_gemini_skill,
]
deployed = []
for src, dst in deploy_map:
if not os.path.exists(src):
continue
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(src, dst)
deployed.append(os.path.basename(dst))
for step in deploy_steps:
try:
result = step()
if result:
deployed.append(result.replace("\n", "; "))
except Exception as exc:
print(f"WARNING: auto-deploy step failed ({step.__name__}): {exc}")
if deployed:
print(f"Auto-deployed to local: {', '.join(deployed)}")
print("Auto-deployed to local:")
for item in deployed:
print(f"- {item}")
if __name__ == "__main__":

View File

@@ -1,15 +1,16 @@
"""
Claude Code integration setup.
Installs ssh.py, encryption.py, /ssh skill, SSH key — everything needed
for Claude Code to manage servers via the shared servers.json.
Local AI agent integration setup.
Installs the shared ssh.py/encryption.py backend, Claude /ssh command,
Codex/Gemini skill packages, platform-specific wrappers, and SSH key material.
"""
import os
import sys
import re
import shutil
from core.logger import log
import subprocess
import sys
SHARED_DIR = os.path.expanduser("~/.server-connections")
from core.logger import log
# PyInstaller: bundled data is in sys._MEIPASS; otherwise use project dir
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
@@ -19,15 +20,20 @@ else:
SSH_SCRIPT_SRC = os.path.join(_BASE_DIR, "tools", "ssh.py")
ENCRYPTION_SRC = os.path.join(_BASE_DIR, "core", "encryption.py")
SKILL_SRC = os.path.join(_BASE_DIR, "tools", "skill-ssh.md")
CLAUDE_SKILL_SRC = os.path.join(_BASE_DIR, "tools", "skill-ssh.md")
GEMINI_CONTRACT_SRC = os.path.join(_BASE_DIR, "GEMINI.md")
SKILL_DST_DIR = os.path.expanduser("~/.claude/commands")
SKILL_DST = os.path.join(SKILL_DST_DIR, "ssh.md")
SSH_KEY_PATH = os.path.expanduser("~/.ssh/id_ed25519")
GLOBAL_CLAUDE_MD = os.path.expanduser("~/.claude/CLAUDE.md")
CODEX_SKILL_SRC_DIR = os.path.join(_BASE_DIR, ".codex", "skills", "server-manager")
CODEX_WRAPPER_SRC_SH = os.path.join(CODEX_SKILL_SRC_DIR, "scripts", "codex-ssh-wrapper.sh")
CODEX_WRAPPER_SRC_CMD = os.path.join(CODEX_SKILL_SRC_DIR, "scripts", "codex-ssh-wrapper.cmd")
GEMINI_SKILL_SRC_DIR = os.path.join(_BASE_DIR, ".gemini", "skills", "server-manager")
GEMINI_WRAPPER_SRC_SH = os.path.join(GEMINI_SKILL_SRC_DIR, "scripts", "gemini-ssh-wrapper.sh")
GEMINI_WRAPPER_SRC_CMD = os.path.join(GEMINI_SKILL_SRC_DIR, "scripts", "gemini-ssh-wrapper.cmd")
_BLOCK_START = "<!-- server-manager:start -->"
_BLOCK_END = "<!-- server-manager:end -->"
_GEMINI_BLOCK_START = "<!-- server-manager-gemini:start -->"
_GEMINI_BLOCK_END = "<!-- server-manager-gemini:end -->"
GLOBAL_CLAUDE_MD_BLOCK = f"""{_BLOCK_START}
## Серверы — ТОЛЬКО через /ssh
@@ -66,29 +72,224 @@ python ~/.server-connections/ssh.py --status # online/offline
{_BLOCK_END}
"""
GLOBAL_GEMINI_MD_BLOCK = f"""{_GEMINI_BLOCK_START}
## ServerManager — use the installed skill
When a user asks about a server managed by ServerManager, use the installed `server-manager` skill first.
Preferred discovery commands:
```bash
$HOME/.server-connections/gemini-ssh --list
$HOME/.server-connections/gemini-ssh --info ALIAS
$HOME/.server-connections/gemini-ssh --status
```
Rules:
- Never read `~/.server-connections/servers.json`, `settings.json`, or `encryption.py` directly.
- Never use `--list-full`.
- Never use raw `ssh`, `scp`, `redis-cli`, `psql`, `mysql`, `mc`, or cloud CLIs unless the user explicitly asks to bypass ServerManager.
- Choose commands strictly by the endpoint type reported by `--list`.
- Use exactly one connection attempt per action and stop on timeout/failure.
{_GEMINI_BLOCK_END}
"""
def _target_home() -> str:
override = os.environ.get("SERVER_MANAGER_TARGET_HOME", "").strip()
if override:
return os.path.abspath(os.path.expanduser(override))
return os.path.expanduser("~")
def _shared_dir() -> str:
return os.path.join(_target_home(), ".server-connections")
def _gemini_dir() -> str:
return os.path.join(_target_home(), ".gemini")
def _claude_skill_dst_dir() -> str:
return os.path.join(_target_home(), ".claude", "commands")
def _claude_skill_dst() -> str:
return os.path.join(_claude_skill_dst_dir(), "ssh.md")
def _ssh_key_path() -> str:
return os.path.join(_target_home(), ".ssh", "id_ed25519")
def _global_claude_md() -> str:
return os.path.join(_target_home(), ".claude", "CLAUDE.md")
def _global_gemini_md() -> str:
return os.path.join(_gemini_dir(), "GEMINI.md")
def _codex_skill_dst_root() -> str:
return os.path.join(_target_home(), ".codex", "skills")
def _codex_skill_dst_dir() -> str:
return os.path.join(_codex_skill_dst_root(), "server-manager")
def _codex_skill_entry() -> str:
return os.path.join(_codex_skill_dst_dir(), "SKILL.md")
def _codex_wrapper_dst() -> str:
return os.path.join(
_shared_dir(),
"codex-ssh.cmd" if sys.platform == "win32" else "codex-ssh",
)
def _gemini_skill_dst_root() -> str:
return os.path.join(_gemini_dir(), "skills")
def _gemini_skill_dst_dir() -> str:
return os.path.join(_gemini_skill_dst_root(), "server-manager")
def _gemini_skill_entry() -> str:
return os.path.join(_gemini_skill_dst_dir(), "SKILL.md")
def _agents_skill_dst_root() -> str:
return os.path.join(_target_home(), ".agents", "skills")
def _agents_skill_dst_dir() -> str:
return os.path.join(_agents_skill_dst_root(), "server-manager")
def _gemini_wrapper_dst() -> str:
return os.path.join(
_shared_dir(),
"gemini-ssh.cmd" if sys.platform == "win32" else "gemini-ssh",
)
def _ensure_executable(path: str):
if sys.platform == "win32" or not os.path.exists(path):
return
mode = os.stat(path).st_mode
os.chmod(path, mode | 0o755)
def _copy_file(src: str, dst: str, executable: bool = False) -> str:
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(src, dst)
if executable:
_ensure_executable(dst)
return dst
def _copy_tree(src: str, dst: str) -> str:
os.makedirs(dst, exist_ok=True)
shutil.copytree(src, dst, dirs_exist_ok=True)
return dst
def _install_wrapper(src: str, dst: str) -> str:
return _copy_file(src, dst, executable=(sys.platform != "win32"))
def _skill_script_names() -> list[str]:
if sys.platform == "win32":
return [
os.path.join("scripts", "server-manager-doctor.cmd"),
os.path.join("scripts", "codex-ssh-wrapper.cmd"),
os.path.join("scripts", "server-manager-gemini-doctor.cmd"),
os.path.join("scripts", "gemini-ssh-wrapper.cmd"),
]
return [
os.path.join("scripts", "server-manager-doctor.sh"),
os.path.join("scripts", "codex-ssh-wrapper.sh"),
os.path.join("scripts", "server-manager-gemini-doctor.sh"),
os.path.join("scripts", "gemini-ssh-wrapper.sh"),
]
def _ensure_skill_scripts(skill_dir: str):
for rel_path in _skill_script_names():
_ensure_executable(os.path.join(skill_dir, rel_path))
def _iter_all_user_homes() -> list[str]:
homes: list[str] = []
def add(path: str):
expanded = os.path.abspath(os.path.expanduser(path))
if os.path.isdir(expanded) and expanded not in homes:
homes.append(expanded)
add(_target_home())
if sys.platform == "win32":
users_root = os.path.join(os.environ.get("SystemDrive", "C:"), "Users")
skip = {"public", "default", "default user", "all users"}
if os.path.isdir(users_root):
for name in sorted(os.listdir(users_root)):
if name.lower() in skip:
continue
add(os.path.join(users_root, name))
elif sys.platform == "darwin":
add("/var/root")
users_root = "/Users"
if os.path.isdir(users_root):
for name in sorted(os.listdir(users_root)):
if name.startswith("."):
continue
add(os.path.join(users_root, name))
else:
add("/root")
users_root = "/home"
if os.path.isdir(users_root):
for name in sorted(os.listdir(users_root)):
if name.startswith("."):
continue
add(os.path.join(users_root, name))
return homes
def check_status() -> dict:
"""Check what's installed and what's missing."""
shared_dir = _shared_dir()
ssh_key_path = _ssh_key_path()
return {
"shared_dir": os.path.exists(SHARED_DIR),
"servers_json": os.path.exists(os.path.join(SHARED_DIR, "servers.json")),
"ssh_script": os.path.exists(os.path.join(SHARED_DIR, "ssh.py")),
"encryption": os.path.exists(os.path.join(SHARED_DIR, "encryption.py")),
"skill_installed": os.path.exists(SKILL_DST),
"ssh_key_exists": os.path.exists(SSH_KEY_PATH),
"ssh_key_pub": os.path.exists(SSH_KEY_PATH + ".pub"),
"target_home": _target_home(),
"shared_dir": os.path.exists(shared_dir),
"servers_json": os.path.exists(os.path.join(shared_dir, "servers.json")),
"ssh_script": os.path.exists(os.path.join(shared_dir, "ssh.py")),
"encryption": os.path.exists(os.path.join(shared_dir, "encryption.py")),
"claude_skill_installed": os.path.exists(_claude_skill_dst()),
"codex_skill_installed": os.path.exists(_codex_skill_entry()),
"codex_wrapper_installed": os.path.exists(_codex_wrapper_dst()),
"gemini_skill_installed": os.path.exists(_gemini_skill_entry()),
"gemini_wrapper_installed": os.path.exists(_gemini_wrapper_dst()),
"ssh_key_exists": os.path.exists(ssh_key_path),
"ssh_key_pub": os.path.exists(ssh_key_path + ".pub"),
}
def install_ssh_script() -> str:
"""Copy ssh.py and encryption.py to shared dir."""
os.makedirs(SHARED_DIR, exist_ok=True)
shared_dir = _shared_dir()
os.makedirs(shared_dir, exist_ok=True)
results = []
# Copy ssh.py
dst = os.path.join(SHARED_DIR, "ssh.py")
dst = os.path.join(shared_dir, "ssh.py")
if os.path.exists(SSH_SCRIPT_SRC):
shutil.copy2(SSH_SCRIPT_SRC, dst)
_copy_file(SSH_SCRIPT_SRC, dst, executable=True)
log.info(f"ssh.py installed: {dst}")
results.append(f"ssh.py installed: {dst}")
elif os.path.exists(dst):
@@ -96,10 +297,9 @@ def install_ssh_script() -> str:
else:
results.append("ERROR: ssh.py source not found")
# Copy encryption.py
enc_dst = os.path.join(SHARED_DIR, "encryption.py")
enc_dst = os.path.join(shared_dir, "encryption.py")
if os.path.exists(ENCRYPTION_SRC):
shutil.copy2(ENCRYPTION_SRC, enc_dst)
_copy_file(ENCRYPTION_SRC, enc_dst)
log.info(f"encryption.py installed: {enc_dst}")
results.append(f"encryption.py installed: {enc_dst}")
elif os.path.exists(enc_dst):
@@ -110,40 +310,125 @@ def install_ssh_script() -> str:
return "\n".join(results)
def install_skill() -> str:
def install_claude_skill() -> str:
"""Install /ssh skill for Claude Code."""
os.makedirs(SKILL_DST_DIR, exist_ok=True)
if os.path.exists(SKILL_SRC):
shutil.copy2(SKILL_SRC, SKILL_DST)
log.info(f"Skill installed: {SKILL_DST}")
return f"Skill installed: {SKILL_DST}"
# Fallback: check existing
if os.path.exists(SKILL_DST):
return f"Skill already exists: {SKILL_DST}"
# Generate minimal skill
claude_skill_dst_dir = _claude_skill_dst_dir()
claude_skill_dst = _claude_skill_dst()
os.makedirs(claude_skill_dst_dir, exist_ok=True)
if os.path.exists(CLAUDE_SKILL_SRC):
_copy_file(CLAUDE_SKILL_SRC, claude_skill_dst)
log.info(f"Claude skill installed: {claude_skill_dst}")
return f"Claude skill installed: {claude_skill_dst}"
if os.path.exists(claude_skill_dst):
return f"Claude skill already exists: {claude_skill_dst}"
skill_content = _generate_skill_content()
with open(SKILL_DST, "w", encoding="utf-8") as f:
with open(claude_skill_dst, "w", encoding="utf-8") as f:
f.write(skill_content)
log.info(f"Skill generated: {SKILL_DST}")
return f"Skill generated: {SKILL_DST}"
log.info(f"Claude skill generated: {claude_skill_dst}")
return f"Claude skill generated: {claude_skill_dst}"
def install_codex_skill() -> str:
"""Install ServerManager skill package for Codex and the local wrapper."""
results = []
codex_skill_dst_dir = _codex_skill_dst_dir()
codex_skill_entry = _codex_skill_entry()
codex_wrapper_dst = _codex_wrapper_dst()
if os.path.isdir(CODEX_SKILL_SRC_DIR):
_copy_tree(CODEX_SKILL_SRC_DIR, codex_skill_dst_dir)
_ensure_skill_scripts(codex_skill_dst_dir)
log.info(f"Codex skill installed: {codex_skill_dst_dir}")
results.append(f"Codex skill installed: {codex_skill_dst_dir}")
elif os.path.exists(codex_skill_entry):
results.append(f"Codex skill already exists: {codex_skill_dst_dir}")
else:
results.append("ERROR: Codex skill source not found")
wrapper_src = CODEX_WRAPPER_SRC_CMD if sys.platform == "win32" else CODEX_WRAPPER_SRC_SH
if os.path.exists(wrapper_src):
_copy_file(wrapper_src, codex_wrapper_dst, executable=(sys.platform != "win32"))
log.info(f"Codex wrapper installed: {codex_wrapper_dst}")
results.append(f"Codex wrapper installed: {codex_wrapper_dst}")
elif os.path.exists(codex_wrapper_dst):
results.append(f"Codex wrapper already exists: {codex_wrapper_dst}")
else:
results.append("ERROR: Codex wrapper source not found")
return "\n".join(results)
def install_gemini_skill() -> str:
"""Install ServerManager skill package for Gemini."""
results = []
gemini_skill_dst_dir = _gemini_skill_dst_dir()
gemini_skill_entry = _gemini_skill_entry()
agents_skill_dst_dir = _agents_skill_dst_dir()
gemini_wrapper_dst = _gemini_wrapper_dst()
install_generic_mirror = os.environ.get(
"SERVER_MANAGER_INSTALL_GENERIC_SKILL_MIRROR", ""
).strip() == "1"
if os.path.isdir(GEMINI_SKILL_SRC_DIR):
_copy_tree(GEMINI_SKILL_SRC_DIR, gemini_skill_dst_dir)
_ensure_skill_scripts(gemini_skill_dst_dir)
log.info(f"Gemini skill installed: {gemini_skill_dst_dir}")
results.append(f"Gemini skill installed: {gemini_skill_dst_dir}")
if install_generic_mirror:
_copy_tree(GEMINI_SKILL_SRC_DIR, agents_skill_dst_dir)
_ensure_skill_scripts(agents_skill_dst_dir)
log.info(f"Generic agents skill mirror installed: {agents_skill_dst_dir}")
results.append(f"Generic agents skill mirror installed: {agents_skill_dst_dir}")
elif os.path.exists(agents_skill_dst_dir):
shutil.rmtree(agents_skill_dst_dir, ignore_errors=True)
log.info(f"Removed generic agents skill mirror to avoid Gemini conflicts: {agents_skill_dst_dir}")
results.append(
f"Removed generic agents skill mirror to avoid Gemini conflicts: {agents_skill_dst_dir}"
)
elif os.path.exists(gemini_skill_entry):
results.append(f"Gemini skill already exists: {gemini_skill_dst_dir}")
else:
results.append("ERROR: Gemini skill source not found")
wrapper_src = GEMINI_WRAPPER_SRC_CMD if sys.platform == "win32" else GEMINI_WRAPPER_SRC_SH
if not os.path.exists(wrapper_src):
wrapper_src = CODEX_WRAPPER_SRC_CMD if sys.platform == "win32" else CODEX_WRAPPER_SRC_SH
if os.path.exists(wrapper_src):
_install_wrapper(wrapper_src, gemini_wrapper_dst)
log.info(f"Gemini wrapper installed: {gemini_wrapper_dst}")
results.append(f"Gemini wrapper installed: {gemini_wrapper_dst}")
elif os.path.exists(gemini_wrapper_dst):
results.append(f"Gemini wrapper already exists: {gemini_wrapper_dst}")
else:
results.append("ERROR: Gemini wrapper source not found")
return "\n".join(results)
def install_skill() -> str:
"""Backward-compatible alias for the Claude /ssh skill installer."""
return install_claude_skill()
def generate_ssh_key() -> str:
"""Generate ed25519 SSH key if not exists."""
if os.path.exists(SSH_KEY_PATH):
return f"Key already exists: {SSH_KEY_PATH}"
ssh_key_path = _ssh_key_path()
if os.path.exists(ssh_key_path):
return f"Key already exists: {ssh_key_path}"
os.makedirs(os.path.dirname(SSH_KEY_PATH), exist_ok=True)
os.makedirs(os.path.dirname(ssh_key_path), exist_ok=True)
import subprocess
try:
subprocess.run(
["ssh-keygen", "-t", "ed25519", "-f", SSH_KEY_PATH,
["ssh-keygen", "-t", "ed25519", "-f", ssh_key_path,
"-N", "", "-C", "server-manager"],
check=True, capture_output=True, timeout=15
)
log.info(f"SSH key generated: {SSH_KEY_PATH}")
return f"Key generated: {SSH_KEY_PATH}"
log.info(f"SSH key generated: {ssh_key_path}")
return f"Key generated: {ssh_key_path}"
except FileNotFoundError:
hint = "enable OpenSSH optional feature" if sys.platform == "win32" else "install openssh-client"
msg = f"ERROR: ssh-keygen not found — {hint}"
@@ -156,16 +441,13 @@ def generate_ssh_key() -> str:
def install_global_claude_md() -> str:
"""Add/update server manager section in global ~/.claude/CLAUDE.md.
Uses start/end markers to safely replace existing block without duplication.
"""
import re
os.makedirs(os.path.dirname(GLOBAL_CLAUDE_MD), exist_ok=True)
"""Add/update server manager section in global ~/.claude/CLAUDE.md."""
global_claude_md = _global_claude_md()
os.makedirs(os.path.dirname(global_claude_md), exist_ok=True)
existing = ""
if os.path.exists(GLOBAL_CLAUDE_MD):
with open(GLOBAL_CLAUDE_MD, encoding="utf-8") as f:
if os.path.exists(global_claude_md):
with open(global_claude_md, encoding="utf-8") as f:
existing = f.read()
pattern = re.compile(
@@ -174,43 +456,97 @@ def install_global_claude_md() -> str:
)
if pattern.search(existing):
# Блок уже есть — заменяем на актуальную версию
updated = pattern.sub(GLOBAL_CLAUDE_MD_BLOCK.strip(), existing)
with open(GLOBAL_CLAUDE_MD, "w", encoding="utf-8") as f:
with open(global_claude_md, "w", encoding="utf-8") as f:
f.write(updated)
log.info(f"Global CLAUDE.md block updated: {GLOBAL_CLAUDE_MD}")
return f"Global CLAUDE.md block updated: {GLOBAL_CLAUDE_MD}"
else:
# Блока нет — добавляем в конец
with open(GLOBAL_CLAUDE_MD, "a", encoding="utf-8") as f:
if existing and not existing.endswith("\n"):
f.write("\n")
f.write("\n" + GLOBAL_CLAUDE_MD_BLOCK)
log.info(f"Global CLAUDE.md block added: {GLOBAL_CLAUDE_MD}")
return f"Global CLAUDE.md block added: {GLOBAL_CLAUDE_MD}"
log.info(f"Global CLAUDE.md block updated: {global_claude_md}")
return f"Global CLAUDE.md block updated: {global_claude_md}"
with open(global_claude_md, "a", encoding="utf-8") as f:
if existing and not existing.endswith("\n"):
f.write("\n")
f.write("\n" + GLOBAL_CLAUDE_MD_BLOCK)
log.info(f"Global CLAUDE.md block added: {global_claude_md}")
return f"Global CLAUDE.md block added: {global_claude_md}"
def install_global_gemini_md() -> str:
"""Add/update server manager section in global ~/.gemini/GEMINI.md."""
global_gemini_md = _global_gemini_md()
os.makedirs(os.path.dirname(global_gemini_md), exist_ok=True)
existing = ""
if os.path.exists(global_gemini_md):
with open(global_gemini_md, encoding="utf-8") as f:
existing = f.read()
pattern = re.compile(
re.escape(_GEMINI_BLOCK_START) + r".*?" + re.escape(_GEMINI_BLOCK_END),
re.DOTALL
)
if pattern.search(existing):
updated = pattern.sub(GLOBAL_GEMINI_MD_BLOCK.strip(), existing)
with open(global_gemini_md, "w", encoding="utf-8") as f:
f.write(updated)
log.info(f"Global GEMINI.md block updated: {global_gemini_md}")
return f"Global GEMINI.md block updated: {global_gemini_md}"
with open(global_gemini_md, "a", encoding="utf-8") as f:
if existing and not existing.endswith("\n"):
f.write("\n")
f.write("\n" + GLOBAL_GEMINI_MD_BLOCK)
log.info(f"Global GEMINI.md block added: {global_gemini_md}")
return f"Global GEMINI.md block added: {global_gemini_md}"
def install_all() -> list[str]:
"""Full setup — install everything."""
results = []
steps = [
"""Full setup — install everything for Claude Code, Codex, and Gemini."""
all_users = os.environ.get("SERVER_MANAGER_INSTALL_ALL_USERS", "").strip() == "1"
base_steps = [
("ssh_script", install_ssh_script),
("skill", install_skill),
("ssh_key", generate_ssh_key),
("claude_skill", install_claude_skill),
("codex_skill", install_codex_skill),
("gemini_skill", install_gemini_skill),
("global_claude_md", install_global_claude_md),
("global_gemini_md", install_global_gemini_md),
]
for name, func in steps:
try:
log.info(f"install_all: running {name}")
result = func()
results.append(result)
except Exception as e:
msg = f"ERROR ({name}): {e}"
log.error(msg)
results.append(msg)
if not all_users:
steps = base_steps[:3] + [("ssh_key", generate_ssh_key)] + base_steps[3:]
results = []
for name, func in steps:
try:
log.info(f"install_all: running {name}")
result = func()
results.append(result)
except Exception as e:
msg = f"ERROR ({name}): {e}"
log.error(msg)
results.append(msg)
return results
results = []
original_target = os.environ.get("SERVER_MANAGER_TARGET_HOME")
for home in _iter_all_user_homes():
os.environ["SERVER_MANAGER_TARGET_HOME"] = home
results.append(f"[target_home] {home}")
for name, func in base_steps:
try:
log.info(f"install_all(all_users): running {name} for {home}")
result = func()
results.append(result)
except Exception as e:
msg = f"ERROR ({name}, {home}): {e}"
log.error(msg)
results.append(msg)
if original_target is None:
os.environ.pop("SERVER_MANAGER_TARGET_HOME", None)
else:
os.environ["SERVER_MANAGER_TARGET_HOME"] = original_target
results.append("INFO: SSH key generation skipped in SERVER_MANAGER_INSTALL_ALL_USERS=1 mode")
return results

View File

@@ -46,7 +46,7 @@ _EN = {
"about_desc": (
"Desktop application for managing remote servers.\n"
"SSH terminal, SFTP file transfer, key management,\n"
"encrypted credentials, and Claude Code integration."
"encrypted credentials, and Claude Code / Codex integration."
),
"about_features_title": "⚡ Features",
"about_features": (
@@ -56,13 +56,13 @@ _EN = {
"• TOTP / 2FA (Google Authenticator)\n"
"• Encrypted credentials (Fernet)\n"
"• Automatic backups\n"
"• Claude Code integration"
"• Claude Code and Codex integration"
),
"about_howto_title": "🚀 Quick Start",
"about_howto": (
"1. Click \"+ Add\" to add a server\n"
"2. Select server → Terminal / Files\n"
"3. Setup tab → Claude Code integration"
"3. Setup tab → Claude Code / Codex integration"
),
"version": "Version",
"author": "Author",
@@ -157,6 +157,12 @@ _EN = {
"no_public_key": "[!] No public key to copy",
# Setup
"agent_integration": "AI Agent Integration",
"agent_desc": (
"Setup everything so Claude Code, Codex, and Gemini can manage your servers via shared local skills.\n"
"ServerManager, Claude Code, Codex, and Gemini share the same servers.json — add a server here,\n"
"all agents see it immediately."
),
"claude_integration": "Claude Code Integration",
"claude_desc": (
"Setup everything so Claude Code can manage your servers via /ssh skill.\n"
@@ -169,11 +175,19 @@ _EN = {
"status_ssh_script": "ssh.py (CLI tool)",
"status_encryption": "Encryption module",
"status_skill": "/ssh skill for Claude Code",
"status_claude_skill": "/ssh skill for Claude Code",
"status_codex_skill": "ServerManager skill for Codex",
"status_codex_wrapper": "Codex wrapper (codex-ssh)",
"status_gemini_skill": "ServerManager skill for Gemini",
"status_gemini_wrapper": "Gemini wrapper (gemini-ssh)",
"status_ssh_key": "SSH key (ed25519)",
"install_everything": "Install Everything",
"installing_all": "Installing...",
"install_ssh_py": "ssh.py",
"install_skill": "/ssh skill",
"install_claude_skill": "Claude skill",
"install_codex_skill": "Codex skill",
"install_gemini_skill": "Gemini skill",
"install_ssh_key": "SSH key",
"refresh": "Refresh",
"configuration": "Configuration",
@@ -183,7 +197,7 @@ _EN = {
"select_backup": "Select backup...",
"no_backups": "No backups",
"restore": "Restore",
"install_done": "Done! Claude Code can now use /ssh to manage your servers.",
"install_done": "Done! Claude Code, Codex, and Gemini can now use ServerManager to manage your servers.",
"config_changed": "Config path changed: {path}",
"backup_created": "Backup created: {name}",
"backup_failed": "Backup failed: {e}",
@@ -603,7 +617,7 @@ _RU = {
"about_desc": (
"Настольное приложение для управления удалёнными серверами.\n"
"SSH-терминал, SFTP-передача файлов, управление ключами,\n"
"шифрование паролей и интеграция с Claude Code."
"шифрование паролей и интеграция с Claude Code / Codex."
),
"about_features_title": "⚡ Возможности",
"about_features": (
@@ -613,13 +627,13 @@ _RU = {
"• TOTP / 2FA (Google Authenticator)\n"
"• Шифрование паролей (Fernet)\n"
"• Автоматические бэкапы\n"
"• Интеграция с Claude Code"
"• Интеграция с Claude Code и Codex"
),
"about_howto_title": "🚀 Быстрый старт",
"about_howto": (
"1. Нажмите \"+ Добавить\" для добавления сервера\n"
"2. Выберите сервер → Терминал / Файлы\n"
"3. Вкладка Настройка → интеграция Claude Code"
"3. Вкладка Настройка → интеграция Claude Code / Codex"
),
"version": "Версия",
"author": "Автор",
@@ -714,23 +728,37 @@ _RU = {
"no_public_key": "[!] Нет публичного ключа",
# Setup
"agent_integration": "Интеграция AI-агентов",
"claude_integration": "Интеграция с Claude Code",
"claude_desc": (
"Настройте всё, чтобы Claude Code мог управлять серверами через скилл /ssh.\n"
"GUI и Claude Code используют один servers.json — добавьте сервер здесь,\n"
"Claude увидит его сразу."
),
"agent_desc": (
"Настройте всё, чтобы Claude Code, Codex и Gemini могли управлять серверами через локальные skills.\n"
"ServerManager, Claude Code, Codex и Gemini используют один и тот же servers.json — добавьте сервер здесь,\n"
"все агенты увидят его сразу."
),
"status": "Статус",
"status_shared_dir": "Общий каталог (~/.server-connections)",
"status_servers_json": "servers.json",
"status_ssh_script": "ssh.py (CLI-утилита)",
"status_encryption": "Модуль шифрования",
"status_skill": "Скилл /ssh для Claude Code",
"status_claude_skill": "Скилл /ssh для Claude Code",
"status_codex_skill": "Скилл ServerManager для Codex",
"status_codex_wrapper": "Обёртка Codex (codex-ssh)",
"status_gemini_skill": "Скилл ServerManager для Gemini",
"status_gemini_wrapper": "Обёртка Gemini (gemini-ssh)",
"status_ssh_key": "SSH-ключ (ed25519)",
"install_everything": "Установить всё",
"installing_all": "Установка...",
"install_ssh_py": "ssh.py",
"install_skill": "Скилл /ssh",
"install_claude_skill": "Скилл Claude",
"install_codex_skill": "Скилл Codex",
"install_gemini_skill": "Скилл Gemini",
"install_ssh_key": "SSH-ключ",
"refresh": "Обновить",
"configuration": "Конфигурация",
@@ -740,7 +768,7 @@ _RU = {
"select_backup": "Выберите бэкап...",
"no_backups": "Нет бэкапов",
"restore": "Восстановить",
"install_done": "Готово! Claude Code теперь может использовать /ssh для управления серверами.",
"install_done": "Готово! Claude Code, Codex и Gemini теперь могут использовать ServerManager для управления серверами.",
"config_changed": "Путь конфига изменён: {path}",
"backup_created": "Бэкап создан: {name}",
"backup_failed": "Ошибка бэкапа: {e}",
@@ -1160,7 +1188,7 @@ _ZH = {
"about_desc": (
"用于管理远程服务器的桌面应用程序。\n"
"SSH终端、SFTP文件传输、密钥管理、\n"
"凭据加密以及Claude Code集成。"
"凭据加密以及Claude Code / Codex集成。"
),
"about_features_title": "⚡ 功能特点",
"about_features": (
@@ -1170,13 +1198,13 @@ _ZH = {
"• TOTP / 2FAGoogle Authenticator\n"
"• 凭据加密Fernet\n"
"• 自动备份\n"
"• Claude Code集成"
"• Claude Code 和 Codex 集成"
),
"about_howto_title": "🚀 快速开始",
"about_howto": (
"1. 点击\"+ 添加\"来添加服务器\n"
"2. 选择服务器 → 终端 / 文件\n"
"3. 设置标签 → Claude Code集成"
"3. 设置标签 → Claude Code / Codex 集成"
),
"version": "版本",
"author": "作者",
@@ -1271,6 +1299,7 @@ _ZH = {
"no_public_key": "[!] 没有公钥可复制",
# Setup
"agent_integration": "AI代理集成",
"claude_integration": "Claude Code集成",
"claude_desc": (
"设置一切以便Claude Code通过/ssh技能管理您的服务器。\n"
@@ -1282,12 +1311,25 @@ _ZH = {
"status_servers_json": "servers.json",
"status_ssh_script": "ssh.pyCLI工具",
"status_encryption": "加密模块",
"agent_desc": (
"完成设置后Claude Code、Codex 和 Gemini 都可以通过共享的本地 skills 管理您的服务器。\n"
"ServerManager、Claude Code、Codex 和 Gemini 共用同一个 servers.json — 在此添加服务器后,\n"
"所有代理都会立即看到。"
),
"status_skill": "Claude Code的/ssh技能",
"status_claude_skill": "Claude Code 的 /ssh 技能",
"status_codex_skill": "Codex 的 ServerManager 技能",
"status_codex_wrapper": "Codex 包装器codex-ssh",
"status_gemini_skill": "Gemini 的 ServerManager 技能",
"status_gemini_wrapper": "Gemini 包装器gemini-ssh",
"status_ssh_key": "SSH密钥ed25519",
"install_everything": "全部安装",
"installing_all": "安装中...",
"install_ssh_py": "ssh.py",
"install_skill": "/ssh技能",
"install_claude_skill": "Claude 技能",
"install_codex_skill": "Codex 技能",
"install_gemini_skill": "Gemini 技能",
"install_ssh_key": "SSH密钥",
"refresh": "刷新",
"configuration": "配置",
@@ -1297,7 +1339,7 @@ _ZH = {
"select_backup": "选择备份...",
"no_backups": "无备份",
"restore": "恢复",
"install_done": "完成Claude Code现在可以使用/ssh来管理您的服务器。",
"install_done": "完成Claude Code、Codex 和 Gemini 现在可以使用 ServerManager 来管理您的服务器。",
"config_changed": "配置路径已更改:{path}",
"backup_created": "备份已创建:{name}",
"backup_failed": "备份失败:{e}",

View File

@@ -46,7 +46,7 @@ class PrometheusTab(ctk.CTkFrame):
quick_frame.pack(fill="x", padx=15, pady=(0, 5))
for label, query in [("up", "up"), ("CPU", "process_cpu_seconds_total"),
("Memory", "node_memory_MemFree_bytes")]:
("Goroutines", "go_goroutines")]:
btn = make_icon_button(quick_frame, "metrics", label, width=80,
fg_color="#6b7280", hover_color="#4b5563",
command=lambda q=query: self._run_quick(q))

View File

@@ -1,5 +1,5 @@
"""
Setup tab — one-click installation for Claude Code integration.
Setup tab — one-click installation for local AI agent integration.
Includes configuration path management and backup/restore.
"""
@@ -8,7 +8,15 @@ import threading
from datetime import datetime
from tkinter import filedialog, messagebox
import customtkinter as ctk
from core.claude_setup import check_status, install_all, install_ssh_script, install_skill, generate_ssh_key
from core.claude_setup import (
check_status,
generate_ssh_key,
install_all,
install_claude_skill,
install_codex_skill,
install_gemini_skill,
install_ssh_script,
)
from core.i18n import t
from core.icons import icon_text, make_icon_button
from core.logger import log
@@ -25,13 +33,13 @@ class SetupTab(ctk.CTkFrame):
# Header
self.header_label = ctk.CTkLabel(
self._scroll, text=t("claude_integration"),
self._scroll, text=t("agent_integration"),
font=ctk.CTkFont(size=20, weight="bold")
)
self.header_label.pack(padx=20, pady=(20, 5))
self.desc_label = ctk.CTkLabel(
self._scroll, text=t("claude_desc"),
self._scroll, text=t("agent_desc"),
text_color="#9ca3af", justify="center"
)
self.desc_label.pack(padx=20, pady=(0, 15))
@@ -53,7 +61,11 @@ class SetupTab(ctk.CTkFrame):
("servers_json", "status_servers_json"),
("ssh_script", "status_ssh_script"),
("encryption", "status_encryption"),
("skill_installed", "status_skill"),
("claude_skill_installed", "status_claude_skill"),
("codex_skill_installed", "status_codex_skill"),
("codex_wrapper_installed", "status_codex_wrapper"),
("gemini_skill_installed", "status_gemini_skill"),
("gemini_wrapper_installed", "status_gemini_wrapper"),
("ssh_key_exists", "status_ssh_key"),
]
for key, i18n_key in status_items:
@@ -82,17 +94,46 @@ class SetupTab(ctk.CTkFrame):
ind_frame = ctk.CTkFrame(btn_frame, fg_color="transparent")
ind_frame.pack(fill="x")
self.ssh_py_btn = make_icon_button(ind_frame, "confirm", t("install_ssh_py"), width=110, fg_color="#6b7280",
command=self._install_script)
top_btn_row = ctk.CTkFrame(ind_frame, fg_color="transparent")
top_btn_row.pack(fill="x", pady=(0, 5))
self.ssh_py_btn = make_icon_button(
top_btn_row, "confirm", t("install_ssh_py"), width=120, fg_color="#6b7280",
command=self._install_script
)
self.ssh_py_btn.pack(side="left", padx=(0, 5))
self.skill_btn = make_icon_button(ind_frame, "confirm", t("install_skill"), width=110, fg_color="#6b7280",
command=self._install_skill)
self.skill_btn.pack(side="left", padx=5)
self.ssh_key_btn = make_icon_button(ind_frame, "confirm", t("install_ssh_key"), width=110, fg_color="#6b7280",
command=self._gen_key)
self.ssh_key_btn.pack(side="left", padx=5)
self.refresh_btn = make_icon_button(ind_frame, "refresh", t("refresh"), width=90, fg_color="#3b82f6",
command=self._refresh_status)
self.claude_skill_btn = make_icon_button(
top_btn_row, "confirm", t("install_claude_skill"), width=130, fg_color="#6b7280",
command=self._install_claude_skill
)
self.claude_skill_btn.pack(side="left", padx=5)
self.codex_skill_btn = make_icon_button(
top_btn_row, "confirm", t("install_codex_skill"), width=130, fg_color="#6b7280",
command=self._install_codex_skill
)
self.codex_skill_btn.pack(side="left", padx=5)
self.gemini_skill_btn = make_icon_button(
top_btn_row, "confirm", t("install_gemini_skill"), width=130, fg_color="#6b7280",
command=self._install_gemini_skill
)
self.gemini_skill_btn.pack(side="left", padx=5)
bottom_btn_row = ctk.CTkFrame(ind_frame, fg_color="transparent")
bottom_btn_row.pack(fill="x")
self.ssh_key_btn = make_icon_button(
bottom_btn_row, "confirm", t("install_ssh_key"), width=120, fg_color="#6b7280",
command=self._gen_key
)
self.ssh_key_btn.pack(side="left", padx=(0, 5))
self.refresh_btn = make_icon_button(
bottom_btn_row, "refresh", t("refresh"), width=90, fg_color="#3b82f6",
command=self._refresh_status
)
self.refresh_btn.pack(side="right")
# ── Monitoring section ─────────────────────────
@@ -328,8 +369,23 @@ class SetupTab(ctk.CTkFrame):
self._log(msg)
self._refresh_status()
def _install_claude_skill(self):
msg = install_claude_skill()
self._log(msg)
self._refresh_status()
def _install_codex_skill(self):
msg = install_codex_skill()
self._log(msg)
self._refresh_status()
def _install_gemini_skill(self):
msg = install_gemini_skill()
self._log(msg)
self._refresh_status()
def _install_skill(self):
msg = install_skill()
msg = install_claude_skill()
self._log(msg)
self._refresh_status()

93
test_ai_setup.py Normal file
View File

@@ -0,0 +1,93 @@
import os
import tempfile
import unittest
from pathlib import Path
from core import claude_setup as cs
class AISetupTests(unittest.TestCase):
def setUp(self):
self._old_target = os.environ.get("SERVER_MANAGER_TARGET_HOME")
self._old_all = os.environ.get("SERVER_MANAGER_INSTALL_ALL_USERS")
self._old_iter = cs._iter_all_user_homes
self._old_platform = cs.sys.platform
def tearDown(self):
if self._old_target is None:
os.environ.pop("SERVER_MANAGER_TARGET_HOME", None)
else:
os.environ["SERVER_MANAGER_TARGET_HOME"] = self._old_target
if self._old_all is None:
os.environ.pop("SERVER_MANAGER_INSTALL_ALL_USERS", None)
else:
os.environ["SERVER_MANAGER_INSTALL_ALL_USERS"] = self._old_all
cs._iter_all_user_homes = self._old_iter
cs.sys.platform = self._old_platform
def test_single_target_installers_create_expected_files(self):
with tempfile.TemporaryDirectory() as tmp:
os.environ["SERVER_MANAGER_TARGET_HOME"] = tmp
cs.install_ssh_script()
cs.install_claude_skill()
cs.install_codex_skill()
cs.install_gemini_skill()
cs.install_global_claude_md()
cs.install_global_gemini_md()
self.assertTrue(Path(tmp, ".server-connections", "ssh.py").exists())
self.assertTrue(Path(tmp, ".server-connections", "encryption.py").exists())
self.assertTrue(Path(tmp, ".claude", "commands", "ssh.md").exists())
self.assertTrue(Path(tmp, ".codex", "skills", "server-manager", "SKILL.md").exists())
self.assertTrue(Path(tmp, ".gemini", "skills", "server-manager", "SKILL.md").exists())
self.assertTrue(Path(tmp, ".server-connections", "codex-ssh").exists())
self.assertTrue(Path(tmp, ".server-connections", "gemini-ssh").exists())
self.assertTrue(Path(tmp, ".claude", "CLAUDE.md").exists())
self.assertTrue(Path(tmp, ".gemini", "GEMINI.md").exists())
self.assertFalse(Path(tmp, ".agents", "skills", "server-manager").exists())
status = cs.check_status()
self.assertTrue(status["claude_skill_installed"])
self.assertTrue(status["codex_skill_installed"])
self.assertTrue(status["gemini_skill_installed"])
self.assertTrue(status["codex_wrapper_installed"])
self.assertTrue(status["gemini_wrapper_installed"])
def test_install_all_users_mode_installs_into_each_home_and_skips_ssh_key(self):
with tempfile.TemporaryDirectory() as base:
home1 = Path(base, "user1")
home2 = Path(base, "user2")
home1.mkdir()
home2.mkdir()
os.environ["SERVER_MANAGER_INSTALL_ALL_USERS"] = "1"
cs._iter_all_user_homes = lambda: [str(home1), str(home2)]
results = cs.install_all()
self.assertIn("INFO: SSH key generation skipped", "\n".join(results))
self.assertTrue(Path(home1, ".codex", "skills", "server-manager", "SKILL.md").exists())
self.assertTrue(Path(home1, ".gemini", "skills", "server-manager", "SKILL.md").exists())
self.assertTrue(Path(home2, ".codex", "skills", "server-manager", "SKILL.md").exists())
self.assertTrue(Path(home2, ".gemini", "skills", "server-manager", "SKILL.md").exists())
self.assertFalse(Path(home1, ".ssh", "id_ed25519").exists())
self.assertFalse(Path(home2, ".ssh", "id_ed25519").exists())
def test_windows_wrapper_names_are_generated_with_cmd_suffix(self):
with tempfile.TemporaryDirectory() as tmp:
os.environ["SERVER_MANAGER_TARGET_HOME"] = tmp
cs.sys.platform = "win32"
cs.install_ssh_script()
cs.install_codex_skill()
cs.install_gemini_skill()
self.assertTrue(Path(tmp, ".server-connections", "codex-ssh.cmd").exists())
self.assertTrue(Path(tmp, ".server-connections", "gemini-ssh.cmd").exists())
if __name__ == "__main__":
unittest.main()

View File

@@ -1,30 +1,40 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────
# ServerManager CLI Installer for Linux (headless / no-GUI)
# ServerManager AI Integration Installer for Linux/macOS (headless / no-GUI)
#
# Устанавливает:
# - ssh.py + encryption.py ~/.server-connections/
# - servers.json + settings.json → ~/.server-connections/ (если есть)
# - CLAUDE.md → ~/.claude/
# - ssh.md (скилл) → ~/.claude/commands/
# - Python-зависимости для CLI (paramiko, cryptography, etc.)
# Installs for each target home:
# - ssh.py + encryption.py -> ~/.server-connections/
# - Claude /ssh skill -> ~/.claude/commands/
# - Codex server-manager skill -> ~/.codex/skills/server-manager/
# - Gemini server-manager skill -> ~/.gemini/skills/server-manager/
# - codex-ssh / gemini-ssh wrappers -> ~/.server-connections/
# - CLAUDE.md / GEMINI.md (if available) -> ~/.claude/ / ~/.gemini/
#
# Запуск:
# curl -sSL https://git.sensey24.ru/aibot777/server-manager/raw/branch/master/tools/install.sh | bash
# или:
# Optional per-target local config copy:
# - servers.json + settings.json -> ~/.server-connections/
#
# Notes:
# - servers.json is NEVER downloaded remotely.
# - --all-users installs code/skills/wrappers for discovered homes, but skips
# copying servers.json to avoid replicating credentials between users.
# - Gemini also supports ~/.agents/skills, but this installer avoids placing
# the same skill in both ~/.gemini/skills and ~/.agents/skills by default
# because Gemini reports that as a duplicate-skill conflict.
#
# Usage:
# bash install.sh
# или с указанием источника файлов:
# bash install.sh /path/to/server-manager/
# bash install.sh /path/to/server-manager
# bash install.sh --source-dir /path/to/server-manager --target-home /root
# bash install.sh --all-users --source-dir /path/to/server-manager
# ─────────────────────────────────────────────────────────────────────
set -euo pipefail
# ── Colors ──
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
@@ -32,33 +42,80 @@ warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; }
step() { echo -e "\n${CYAN}━━━ $* ━━━${NC}"; }
# ── Config ──
CONN_DIR="$HOME/.server-connections"
CLAUDE_DIR="$HOME/.claude"
COMMANDS_DIR="$CLAUDE_DIR/commands"
usage() {
cat <<USAGE
ServerManager AI integration installer
Options:
--source-dir PATH Use local repo as source of files
--target-home PATH Install into a specific user's home
--all-users Install into all discovered user homes on this machine
--install-agents-mirror Also mirror Gemini skill into ~/.agents/skills
-h, --help Show this help
Positional compatibility:
install.sh /path/to/server-manager # same as --source-dir
USAGE
}
GITEA_RAW="https://git.sensey24.ru/aibot777/server-manager/raw/branch/master"
SRC_DIR=""
TARGET_HOME="${SERVER_MANAGER_TARGET_HOME:-${TARGET_HOME:-$HOME}}"
INSTALL_ALL_USERS=0
INSTALL_AGENTS_MIRROR=0
# Source directory (optional argument)
SRC_DIR="${1:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
--source-dir)
SRC_DIR="$2"
shift 2
;;
--target-home)
TARGET_HOME="$2"
shift 2
;;
--all-users)
INSTALL_ALL_USERS=1
shift
;;
--install-agents-mirror)
INSTALL_AGENTS_MIRROR=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
if [[ -z "$SRC_DIR" ]]; then
SRC_DIR="$1"
shift
else
error "Неизвестный аргумент: $1"
usage
exit 2
fi
;;
esac
done
# ── Banner ──
echo -e "${CYAN}"
echo "╔══════════════════════════════════════════════╗"
echo "║ ServerManager CLI Installer for Linux ║"
echo "║ github: git.sensey24.ru/aibot777 ║"
echo "╚══════════════════════════════════════════════╝"
echo "╔══════════════════════════════════════════════════════╗"
echo "║ ServerManager AI Integration Installer (headless) ║"
echo "║ Claude + Codex + Gemini ║"
echo "╚══════════════════════════════════════════════════════╝"
echo -e "${NC}"
# ── Step 1: Check Python ──
step "1/5 Проверка Python"
PYTHON=""
for cmd in python3 python; do
if command -v "$cmd" &>/dev/null; then
ver=$("$cmd" --version 2>&1 | grep -oP '\d+\.\d+')
major=$(echo "$ver" | cut -d. -f1)
minor=$(echo "$ver" | cut -d. -f2)
if [ "$major" -ge 3 ] && [ "$minor" -ge 8 ]; then
if "$cmd" - <<'PY' &>/dev/null
import sys
raise SystemExit(0 if sys.version_info >= (3, 8) else 1)
PY
then
PYTHON="$cmd"
ok "Python найден: $($cmd --version)"
break
@@ -66,14 +123,11 @@ for cmd in python3 python; do
fi
done
if [ -z "$PYTHON" ]; then
error "Python 3.8+ не найден!"
echo " Установите: sudo apt install python3 python3-pip"
echo " или: sudo yum install python3 python3-pip"
if [[ -z "$PYTHON" ]]; then
error "Python 3.8+ не найден"
exit 1
fi
# Check pip
PIP=""
for cmd in pip3 pip; do
if command -v "$cmd" &>/dev/null; then
@@ -81,22 +135,26 @@ for cmd in pip3 pip; do
break
fi
done
if [ -z "$PIP" ]; then
# Try python -m pip
if [[ -z "$PIP" ]]; then
if $PYTHON -m pip --version &>/dev/null; then
PIP="$PYTHON -m pip"
else
error "pip не найден!"
echo " Установите: sudo apt install python3-pip"
error "pip не найден"
exit 1
fi
fi
ok "pip найден: $($PIP --version 2>&1 | head -1)"
# ── Step 2: Install Python dependencies ──
step "2/5 Установка Python-зависимостей"
resolve_home() {
"$PYTHON" - "$1" <<'PY'
import os, sys
print(os.path.abspath(os.path.expanduser(sys.argv[1])))
PY
}
TARGET_HOME="$(resolve_home "$TARGET_HOME")"
step "2/5 Установка Python-зависимостей"
CLI_DEPS=(
"paramiko>=3.4.0"
"cryptography>=41.0.0"
@@ -105,7 +163,6 @@ CLI_DEPS=(
"redis>=5.0.0"
"requests>=2.31.0"
)
for dep in "${CLI_DEPS[@]}"; do
pkg=$(echo "$dep" | sed 's/[>=<].*//')
if $PYTHON -c "import $pkg" 2>/dev/null; then
@@ -120,38 +177,26 @@ for dep in "${CLI_DEPS[@]}"; do
fi
done
# ── Step 3: Create directories ──
step "3/5 Создание директорий"
mkdir -p "$CONN_DIR" "$COMMANDS_DIR"
chmod 700 "$CONN_DIR" 2>/dev/null || true
ok "$CONN_DIR"
ok "$COMMANDS_DIR"
# ── Step 4: Copy/Download files ──
step "4/5 Установка файлов"
copy_or_download() {
local src_relative="$1"
local dst="$2"
local perms="$3"
local desc="$4"
# Try local source first
if [ -n "$SRC_DIR" ] && [ -f "$SRC_DIR/$src_relative" ]; then
mkdir -p "$(dirname "$dst")"
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/$src_relative" ]]; then
cp "$SRC_DIR/$src_relative" "$dst"
chmod "$perms" "$dst"
chmod "$perms" "$dst" 2>/dev/null || true
ok "$desc (из $SRC_DIR)"
return 0
fi
# Try download from Gitea
local url="$GITEA_RAW/$src_relative"
if command -v curl &>/dev/null; then
if curl -sSL -o "$dst" "$url" 2>/dev/null; then
# Verify not empty and not HTML error page
if [ -s "$dst" ] && ! head -1 "$dst" | grep -qi '<!doctype\|<html'; then
chmod "$perms" "$dst"
if curl -fsSL -o "$dst" "$url" 2>/dev/null; then
if [[ -s "$dst" ]] && ! head -1 "$dst" | grep -qi '<!doctype\|<html'; then
chmod "$perms" "$dst" 2>/dev/null || true
ok "$desc (скачан с Gitea)"
return 0
fi
@@ -159,8 +204,8 @@ copy_or_download() {
fi
elif command -v wget &>/dev/null; then
if wget -q -O "$dst" "$url" 2>/dev/null; then
if [ -s "$dst" ] && ! head -1 "$dst" | grep -qi '<!doctype\|<html'; then
chmod "$perms" "$dst"
if [[ -s "$dst" ]] && ! head -1 "$dst" | grep -qi '<!doctype\|<html'; then
chmod "$perms" "$dst" 2>/dev/null || true
ok "$desc (скачан с Gitea)"
return 0
fi
@@ -172,86 +217,180 @@ copy_or_download() {
return 1
}
# Core files (always install)
copy_or_download "tools/ssh.py" "$CONN_DIR/ssh.py" "755" "ssh.py"
copy_or_download "core/encryption.py" "$CONN_DIR/encryption.py" "644" "encryption.py"
install_skill_tree() {
local prefix="$1"
local dst_root="$2"
shift 2
mkdir -p "$dst_root"
local rel
for rel in "$@"; do
copy_or_download "$prefix/$rel" "$dst_root/$rel" 644 "$prefix/$rel" || true
done
find "$dst_root/scripts" -type f -name '*.sh' -exec chmod 755 {} + 2>/dev/null || true
find "$dst_root/scripts" -type f -name '*.cmd' -exec chmod 644 {} + 2>/dev/null || true
}
# Claude Code skill
copy_or_download "tools/skill-ssh.md" "$COMMANDS_DIR/ssh.md" "644" "ssh.md (скилл /ssh)"
discover_homes() {
local homes=()
local uname_s
uname_s="$(uname -s 2>/dev/null || echo Linux)"
# CLAUDE.md
if [ -n "$SRC_DIR" ] && [ -f "$SRC_DIR/CLAUDE.md" ]; then
cp "$SRC_DIR/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
chmod 644 "$CLAUDE_DIR/CLAUDE.md"
ok "CLAUDE.md"
fi
# servers.json — only copy if exists locally, never download (contains encrypted creds)
if [ -n "$SRC_DIR" ] && [ -f "$SRC_DIR/servers.json" ]; then
cp "$SRC_DIR/servers.json" "$CONN_DIR/servers.json"
chmod 600 "$CONN_DIR/servers.json"
ok "servers.json (зашифрованный)"
elif [ ! -f "$CONN_DIR/servers.json" ]; then
warn "servers.json не найден — скопируйте с основной машины:"
echo " scp user@main:~/.server-connections/servers.json $CONN_DIR/"
fi
# settings.json
if [ -n "$SRC_DIR" ] && [ -f "$SRC_DIR/settings.json" ]; then
cp "$SRC_DIR/settings.json" "$CONN_DIR/settings.json"
chmod 600 "$CONN_DIR/settings.json"
ok "settings.json"
elif [ ! -f "$CONN_DIR/settings.json" ]; then
# Create minimal settings
echo '{"language":"en","check_interval":60}' > "$CONN_DIR/settings.json"
chmod 600 "$CONN_DIR/settings.json"
ok "settings.json (создан по умолчанию)"
fi
# ── Step 5: Verify ──
step "5/5 Проверка установки"
ALL_OK=true
if [ -f "$CONN_DIR/ssh.py" ] && [ -x "$CONN_DIR/ssh.py" ]; then
ok "ssh.py — исполняемый"
else
error "ssh.py — не найден или не исполняемый"
ALL_OK=false
fi
if [ -f "$CONN_DIR/encryption.py" ]; then
ok "encryption.py"
else
error "encryption.py — не найден"
ALL_OK=false
fi
if [ -f "$COMMANDS_DIR/ssh.md" ]; then
ok "ssh.md скилл"
else
warn "ssh.md скилл — не найден"
fi
if [ -f "$CONN_DIR/servers.json" ]; then
ok "servers.json"
else
warn "servers.json — отсутствует (нужно скопировать вручную)"
fi
# Test ssh.py
info "Тест ssh.py..."
if $PYTHON "$CONN_DIR/ssh.py" --list &>/dev/null; then
ok "ssh.py --list работает"
else
if [ ! -f "$CONN_DIR/servers.json" ]; then
warn "ssh.py не может запуститься (нет servers.json)"
if [[ "$INSTALL_ALL_USERS" -eq 1 ]]; then
if [[ "$uname_s" == "Darwin" ]]; then
[[ -d /var/root ]] && homes+=("/var/root")
if [[ -d /Users ]]; then
while IFS= read -r -d '' d; do homes+=("$d"); done < <(find /Users -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
fi
else
[[ -d /root ]] && homes+=("/root")
if [[ -d /home ]]; then
while IFS= read -r -d '' d; do homes+=("$d"); done < <(find /home -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
fi
fi
else
warn "ssh.py вернул ошибку — проверьте зависимости"
homes+=("$TARGET_HOME")
fi
printf '%s\n' "${homes[@]}" | awk 'NF && !seen[$0]++'
}
step "3/5 Подготовка директорий"
TARGET_HOMES=()
while IFS= read -r home; do
[[ -n "$home" ]] || continue
TARGET_HOMES+=("$home")
ok "target home: $home"
done < <(discover_homes)
if [[ "${#TARGET_HOMES[@]}" -eq 0 ]]; then
error "Не удалось определить target home"
exit 1
fi
# ── Summary ──
step "4/5 Установка файлов"
CODEX_SKILL_FILES=(
"SKILL.md"
"references/command-matrix.md"
"references/project.md"
"scripts/codex-ssh-wrapper.sh"
"scripts/codex-ssh-wrapper.cmd"
"scripts/server-manager-doctor.sh"
"scripts/server-manager-doctor.cmd"
)
GEMINI_SKILL_FILES=(
"SKILL.md"
"references/command-matrix.md"
"references/project.md"
"scripts/gemini-ssh-wrapper.sh"
"scripts/gemini-ssh-wrapper.cmd"
"scripts/server-manager-gemini-doctor.sh"
"scripts/server-manager-gemini-doctor.cmd"
)
for HOME_DIR in "${TARGET_HOMES[@]}"; do
CONN_DIR="$HOME_DIR/.server-connections"
CLAUDE_DIR="$HOME_DIR/.claude"
COMMANDS_DIR="$CLAUDE_DIR/commands"
CODEX_DIR="$HOME_DIR/.codex/skills/server-manager"
GEMINI_DIR="$HOME_DIR/.gemini"
GEMINI_SKILL_DIR="$GEMINI_DIR/skills/server-manager"
AGENTS_DIR="$HOME_DIR/.agents/skills/server-manager"
mkdir -p "$CONN_DIR" "$COMMANDS_DIR" "$CODEX_DIR" "$GEMINI_SKILL_DIR"
chmod 700 "$CONN_DIR" 2>/dev/null || true
info "Устанавливаю в $HOME_DIR"
copy_or_download "tools/ssh.py" "$CONN_DIR/ssh.py" 755 "ssh.py"
copy_or_download "core/encryption.py" "$CONN_DIR/encryption.py" 644 "encryption.py"
copy_or_download "tools/skill-ssh.md" "$COMMANDS_DIR/ssh.md" 644 "ssh.md (скилл /ssh)"
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/CLAUDE.md" ]]; then
cp "$SRC_DIR/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
chmod 644 "$CLAUDE_DIR/CLAUDE.md"
ok "CLAUDE.md"
elif [[ ! -f "$CLAUDE_DIR/CLAUDE.md" ]]; then
copy_or_download "CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md" 644 "CLAUDE.md" || true
fi
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/GEMINI.md" ]]; then
cp "$SRC_DIR/GEMINI.md" "$GEMINI_DIR/GEMINI.md"
chmod 644 "$GEMINI_DIR/GEMINI.md"
ok "GEMINI.md"
elif [[ ! -f "$GEMINI_DIR/GEMINI.md" ]]; then
copy_or_download "GEMINI.md" "$GEMINI_DIR/GEMINI.md" 644 "GEMINI.md" || true
fi
install_skill_tree ".codex/skills/server-manager" "$CODEX_DIR" "${CODEX_SKILL_FILES[@]}"
install_skill_tree ".gemini/skills/server-manager" "$GEMINI_SKILL_DIR" "${GEMINI_SKILL_FILES[@]}"
if [[ "$INSTALL_AGENTS_MIRROR" -eq 1 ]]; then
mkdir -p "$AGENTS_DIR"
install_skill_tree ".gemini/skills/server-manager" "$AGENTS_DIR" "${GEMINI_SKILL_FILES[@]}"
ok "agents skill mirror"
elif [[ -d "$AGENTS_DIR" ]]; then
rm -rf "$AGENTS_DIR"
ok "removed stale agents skill mirror to avoid Gemini conflict"
fi
if [[ -f "$CODEX_DIR/scripts/codex-ssh-wrapper.sh" ]]; then
cp "$CODEX_DIR/scripts/codex-ssh-wrapper.sh" "$CONN_DIR/codex-ssh"
chmod 755 "$CONN_DIR/codex-ssh"
ok "codex-ssh wrapper"
else
copy_or_download ".codex/skills/server-manager/scripts/codex-ssh-wrapper.sh" "$CONN_DIR/codex-ssh" 755 "codex-ssh wrapper" || true
fi
if [[ -f "$GEMINI_SKILL_DIR/scripts/gemini-ssh-wrapper.sh" ]]; then
cp "$GEMINI_SKILL_DIR/scripts/gemini-ssh-wrapper.sh" "$CONN_DIR/gemini-ssh"
chmod 755 "$CONN_DIR/gemini-ssh"
ok "gemini-ssh wrapper"
else
copy_or_download ".gemini/skills/server-manager/scripts/gemini-ssh-wrapper.sh" "$CONN_DIR/gemini-ssh" 755 "gemini-ssh wrapper" || true
fi
if [[ "$INSTALL_ALL_USERS" -eq 0 ]]; then
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/servers.json" ]]; then
cp "$SRC_DIR/servers.json" "$CONN_DIR/servers.json"
chmod 600 "$CONN_DIR/servers.json"
ok "servers.json (зашифрованный)"
elif [[ ! -f "$CONN_DIR/servers.json" ]]; then
warn "servers.json не найден для $HOME_DIR — скопируйте вручную"
fi
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/settings.json" ]]; then
cp "$SRC_DIR/settings.json" "$CONN_DIR/settings.json"
chmod 600 "$CONN_DIR/settings.json"
ok "settings.json"
elif [[ ! -f "$CONN_DIR/settings.json" ]]; then
echo '{"language":"en","check_interval":60}' > "$CONN_DIR/settings.json"
chmod 600 "$CONN_DIR/settings.json"
ok "settings.json (создан по умолчанию)"
fi
else
warn "all-users mode: servers.json/settings.json не копируются автоматически для $HOME_DIR"
fi
done
step "5/5 Проверка установки"
ALL_OK=true
for HOME_DIR in "${TARGET_HOMES[@]}"; do
CONN_DIR="$HOME_DIR/.server-connections"
COMMANDS_DIR="$HOME_DIR/.claude/commands"
CODEX_DIR="$HOME_DIR/.codex/skills/server-manager"
GEMINI_SKILL_DIR="$HOME_DIR/.gemini/skills/server-manager"
info "Проверка $HOME_DIR"
[[ -x "$CONN_DIR/ssh.py" ]] && ok "ssh.py — исполняемый" || { error "ssh.py — не найден или не исполняемый"; ALL_OK=false; }
[[ -f "$CONN_DIR/encryption.py" ]] && ok "encryption.py" || { error "encryption.py — не найден"; ALL_OK=false; }
[[ -f "$COMMANDS_DIR/ssh.md" ]] && ok "Claude /ssh skill" || warn "Claude /ssh skill — не найден"
[[ -f "$CODEX_DIR/SKILL.md" ]] && ok "Codex skill" || { warn "Codex skill — не найден"; ALL_OK=false; }
[[ -x "$CONN_DIR/codex-ssh" ]] && ok "codex-ssh wrapper" || { warn "codex-ssh wrapper — не найден"; ALL_OK=false; }
[[ -f "$GEMINI_SKILL_DIR/SKILL.md" ]] && ok "Gemini skill" || { warn "Gemini skill — не найден"; ALL_OK=false; }
[[ -x "$CONN_DIR/gemini-ssh" ]] && ok "gemini-ssh wrapper" || { warn "gemini-ssh wrapper — не найден"; ALL_OK=false; }
done
echo ""
echo -e "${CYAN}━━━ Готово ━━━${NC}"
echo ""
@@ -260,17 +399,19 @@ if $ALL_OK; then
else
echo -e "${YELLOW}Установка завершена с предупреждениями.${NC}"
fi
echo ""
echo "Файлы:"
echo " $CONN_DIR/ssh.py — CLI-утилита"
echo " $CONN_DIR/encryption.py — модуль шифрования"
echo " $CONN_DIR/servers.json — серверы (зашифрованные)"
echo " $COMMANDS_DIR/ssh.md — скилл /ssh для Claude Code"
echo "Установлено для home:"
printf ' - %s\n' "${TARGET_HOMES[@]}"
echo ""
echo "Использование:"
echo " python3 ~/.server-connections/ssh.py --list"
echo " python3 ~/.server-connections/ssh.py --info ALIAS"
echo " python3 ~/.server-connections/ssh.py ALIAS \"command\""
echo ""
echo "Claude Code скилл: /ssh"
echo " ~/.server-connections/codex-ssh --list"
echo " ~/.server-connections/gemini-ssh --list"
echo ""
echo "Claude skill: ~/.claude/commands/ssh.md"
echo "Codex skill: ~/.codex/skills/server-manager/"
echo "Gemini skill: ~/.gemini/skills/server-manager/"
if [[ "$INSTALL_AGENTS_MIRROR" -eq 1 ]]; then
echo "Mirror skill: ~/.agents/skills/server-manager/"
fi

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Cross-platform installer for ServerManager AI integrations.
Supports Claude (/ssh), Codex (server-manager), and Gemini (server-manager)
for the current user, a target home, or all discovered user homes.
"""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from core.claude_setup import install_all # noqa: E402
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Install ServerManager AI integrations")
p.add_argument("--target-home", help="Install into this home directory instead of the current user")
p.add_argument("--all-users", action="store_true", help="Install to all discovered user homes on this system")
return p.parse_args()
def main() -> int:
args = parse_args()
if args.target_home and args.all_users:
print("error: --target-home and --all-users are mutually exclusive", file=sys.stderr)
return 2
if args.target_home:
os.environ["SERVER_MANAGER_TARGET_HOME"] = os.path.abspath(os.path.expanduser(args.target_home))
if args.all_users:
os.environ["SERVER_MANAGER_INSTALL_ALL_USERS"] = "1"
for line in install_all():
print(line)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,6 +1,6 @@
"""Version info for ServerManager."""
__version__ = "1.9.42"
__version__ = "1.9.44"
__app_name__ = "ServerManager"
__author__ = "aibot777"
__description__ = "Desktop GUI for managing remote servers"