mirror of
https://github.com/andrey271192/ssh-manage.git
synced 2026-09-20 11:55:37 +00:00
feat: PCA SSH Manager v1.1
Portable SSH client with auto-save sessions. - Session groups (drag-and-drop between groups) - 100+ quick commands for Linux and Keenetic routers - Built-in command suggestion agent - Dark theme (Catppuccin), PCA branding - Builds to .exe (Windows) or .app (macOS) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
build/
|
||||||
|
dist/
|
||||||
|
__pycache__/
|
||||||
|
*.spec
|
||||||
|
*.pyc
|
||||||
|
sessions.json
|
||||||
23
build.bat
Normal file
23
build.bat
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
@echo off
|
||||||
|
echo === KU SSH Manager — Build ===
|
||||||
|
|
||||||
|
where python >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Python not found! Install from https://python.org/downloads/
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Installing dependencies...
|
||||||
|
pip install paramiko pyinstaller
|
||||||
|
|
||||||
|
echo Building .exe...
|
||||||
|
pyinstaller --onefile --noconsole --name "KU SSH Manager" ^
|
||||||
|
--add-data "sessions.json;." ^
|
||||||
|
--icon NONE ^
|
||||||
|
ssh_manager.py
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Done! EXE: dist\KU SSH Manager.exe
|
||||||
|
echo Copy it anywhere — sessions.json saves next to .exe
|
||||||
|
pause
|
||||||
94
full_install.ps1
Normal file
94
full_install.ps1
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
Write-Host "=== PCA SSH — Private Control Administration ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Find Python — check common install paths too
|
||||||
|
$python = $null
|
||||||
|
$tryPaths = @(
|
||||||
|
"python",
|
||||||
|
"python3",
|
||||||
|
"py",
|
||||||
|
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
|
||||||
|
"$env:LOCALAPPDATA\Programs\Python\Python311\python.exe",
|
||||||
|
"C:\Python312\python.exe",
|
||||||
|
"C:\Python311\python.exe",
|
||||||
|
"$env:APPDATA\Python\Python312\Scripts\..\python.exe"
|
||||||
|
)
|
||||||
|
foreach ($cmd in $tryPaths) {
|
||||||
|
try {
|
||||||
|
$ver = & $cmd --version 2>$null
|
||||||
|
if ($ver -match "Python 3") {
|
||||||
|
$python = $cmd
|
||||||
|
Write-Host "Python: $ver" -ForegroundColor Green
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $python) {
|
||||||
|
Write-Host "Python not found. Downloading..." -ForegroundColor Yellow
|
||||||
|
$pyUrl = "https://www.python.org/ftp/python/3.12.7/python-3.12.7-amd64.exe"
|
||||||
|
$pyInst = Join-Path $env:TEMP "python-installer.exe"
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
Invoke-WebRequest -Uri $pyUrl -OutFile $pyInst -UseBasicParsing
|
||||||
|
Write-Host "Installing Python..." -ForegroundColor Yellow
|
||||||
|
Start-Process -FilePath $pyInst -ArgumentList "/quiet","InstallAllUsers=0","PrependPath=1","Include_pip=1" -Wait
|
||||||
|
Remove-Item $pyInst -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$searchPaths = @(
|
||||||
|
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
|
||||||
|
"C:\Python312\python.exe",
|
||||||
|
"C:\Program Files\Python312\python.exe"
|
||||||
|
)
|
||||||
|
foreach ($p in $searchPaths) {
|
||||||
|
if (Test-Path $p) {
|
||||||
|
$python = $p
|
||||||
|
Write-Host "Python at: $p" -ForegroundColor Green
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $python) {
|
||||||
|
Write-Host "Cannot find python after install!" -ForegroundColor Red
|
||||||
|
Read-Host "Press Enter"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Installing deps..." -ForegroundColor Yellow
|
||||||
|
& $python -m pip install --upgrade pip 2>&1 | ForEach-Object { if ($_ -notmatch "WARNING") { $_ } }
|
||||||
|
& $python -m pip install paramiko pillow pyinstaller 2>&1 | ForEach-Object { if ($_ -notmatch "WARNING") { $_ } }
|
||||||
|
|
||||||
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
if (-not $scriptDir) { $scriptDir = Get-Location }
|
||||||
|
|
||||||
|
$localDir = Join-Path $env:TEMP "pca-ssh-build"
|
||||||
|
if (Test-Path $localDir) { Remove-Item $localDir -Recurse -Force }
|
||||||
|
New-Item -ItemType Directory -Path $localDir | Out-Null
|
||||||
|
Copy-Item (Join-Path $scriptDir "ssh_manager.py") $localDir
|
||||||
|
Copy-Item (Join-Path $scriptDir "gen_icon.py") $localDir
|
||||||
|
|
||||||
|
Set-Location $localDir
|
||||||
|
|
||||||
|
# Generate .ico
|
||||||
|
Write-Host "Generating icon..." -ForegroundColor Yellow
|
||||||
|
& $python gen_icon.py 2>&1 | ForEach-Object { Write-Host $_ }
|
||||||
|
|
||||||
|
$icoFile = Join-Path $localDir "pca_ssh.ico"
|
||||||
|
if (Test-Path $icoFile) {
|
||||||
|
$iconArg = "--icon=pca_ssh.ico"
|
||||||
|
} else {
|
||||||
|
$iconArg = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Building PCA_SSH.exe..." -ForegroundColor Yellow
|
||||||
|
& $python -m PyInstaller --onefile --noconsole --name "PCA_SSH" $iconArg ssh_manager.py 2>&1 | ForEach-Object { Write-Host $_ }
|
||||||
|
|
||||||
|
$exe = Join-Path $localDir "dist\PCA_SSH.exe"
|
||||||
|
if (Test-Path $exe) {
|
||||||
|
$desktop = [Environment]::GetFolderPath("Desktop")
|
||||||
|
Copy-Item $exe (Join-Path $desktop "PCA_SSH.exe") -Force
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "DONE! PCA_SSH.exe on Desktop" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Build FAILED" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
Read-Host "Press Enter"
|
||||||
100
gen_icon.py
Normal file
100
gen_icon.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""Generate PCA SSH icon in .ico (Windows) and .icns (macOS) formats."""
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
import struct, os, sys
|
||||||
|
|
||||||
|
SIZE = 512
|
||||||
|
|
||||||
|
def draw_icon(size=SIZE):
|
||||||
|
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Dark rounded-rect background
|
||||||
|
r = size // 6
|
||||||
|
draw.rounded_rectangle([4, 4, size - 4, size - 4], radius=r, fill="#1e1e2e", outline="#3b3b5c", width=3)
|
||||||
|
|
||||||
|
# "PCA" text
|
||||||
|
font_size = size // 4
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype("arial.ttf", font_size)
|
||||||
|
except OSError:
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
|
||||||
|
except OSError:
|
||||||
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
|
bbox = draw.textbbox((0, 0), "PCA", font=font)
|
||||||
|
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||||
|
tx = (size - tw) // 2
|
||||||
|
ty = size // 4 - th // 4
|
||||||
|
draw.text((tx, ty), "PCA", fill="#6c8fff", font=font)
|
||||||
|
|
||||||
|
# Blue dots pattern (like water drops)
|
||||||
|
cx = size // 2
|
||||||
|
cy = int(size * 0.62)
|
||||||
|
dot_r = size // 30
|
||||||
|
|
||||||
|
dot_positions = [
|
||||||
|
(0, 0),
|
||||||
|
(-3 * dot_r, -2 * dot_r),
|
||||||
|
(3 * dot_r, -2 * dot_r),
|
||||||
|
(-1.5 * dot_r, 2 * dot_r),
|
||||||
|
(1.5 * dot_r, 2 * dot_r),
|
||||||
|
(0, 4 * dot_r),
|
||||||
|
(-4.5 * dot_r, 1 * dot_r),
|
||||||
|
(4.5 * dot_r, 1 * dot_r),
|
||||||
|
]
|
||||||
|
|
||||||
|
for dx, dy in dot_positions:
|
||||||
|
x = cx + dx
|
||||||
|
y = cy + dy
|
||||||
|
draw.ellipse([x - dot_r, y - dot_r, x + dot_r, y + dot_r], fill="#6c8fff")
|
||||||
|
|
||||||
|
# Subtle connecting lines between dots
|
||||||
|
line_color = "#3d5abf"
|
||||||
|
connections = [(0, 1), (0, 2), (1, 3), (2, 4), (3, 5), (4, 5), (1, 6), (2, 7)]
|
||||||
|
abs_positions = [(cx + dx, cy + dy) for dx, dy in dot_positions]
|
||||||
|
for a, b in connections:
|
||||||
|
draw.line([abs_positions[a], abs_positions[b]], fill=line_color, width=max(1, size // 200))
|
||||||
|
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def save_ico(img, path):
|
||||||
|
sizes = [16, 24, 32, 48, 64, 128, 256]
|
||||||
|
icons = []
|
||||||
|
for s in sizes:
|
||||||
|
icons.append(img.resize((s, s), Image.LANCZOS))
|
||||||
|
icons[0].save(path, format="ICO", sizes=[(s, s) for s in sizes], append_images=icons[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def save_icns(img, path):
|
||||||
|
# macOS .icns via iconutil
|
||||||
|
import tempfile, subprocess
|
||||||
|
iconset = tempfile.mkdtemp(suffix=".iconset")
|
||||||
|
for size in [16, 32, 64, 128, 256, 512]:
|
||||||
|
resized = img.resize((size, size), Image.LANCZOS)
|
||||||
|
resized.save(os.path.join(iconset, f"icon_{size}x{size}.png"))
|
||||||
|
if size <= 256:
|
||||||
|
resized2x = img.resize((size * 2, size * 2), Image.LANCZOS)
|
||||||
|
resized2x.save(os.path.join(iconset, f"icon_{size}x{size}@2x.png"))
|
||||||
|
subprocess.run(["iconutil", "-c", "icns", iconset, "-o", path], check=True)
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(iconset)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
out_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
img = draw_icon(512)
|
||||||
|
|
||||||
|
ico_path = os.path.join(out_dir, "pca_ssh.ico")
|
||||||
|
save_ico(img, ico_path)
|
||||||
|
print(f"ICO: {ico_path}")
|
||||||
|
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
icns_path = os.path.join(out_dir, "pca_ssh.icns")
|
||||||
|
save_icns(img, icns_path)
|
||||||
|
print(f"ICNS: {icns_path}")
|
||||||
|
|
||||||
|
png_path = os.path.join(out_dir, "pca_ssh.png")
|
||||||
|
img.save(png_path)
|
||||||
|
print(f"PNG: {png_path}")
|
||||||
17
install.bat
Normal file
17
install.bat
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@echo off
|
||||||
|
echo === KU SSH Manager — Install and Run ===
|
||||||
|
|
||||||
|
where python >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Python not found!
|
||||||
|
echo Download: https://python.org/downloads/
|
||||||
|
echo Check "Add Python to PATH" during install!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Installing dependencies...
|
||||||
|
pip install paramiko
|
||||||
|
|
||||||
|
echo Starting SSH Manager...
|
||||||
|
python ssh_manager.py
|
||||||
BIN
pca_ssh.icns
Normal file
BIN
pca_ssh.icns
Normal file
Binary file not shown.
BIN
pca_ssh.ico
Normal file
BIN
pca_ssh.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 646 B |
BIN
pca_ssh.png
Normal file
BIN
pca_ssh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
paramiko>=3.0
|
||||||
|
pyinstaller>=6.0
|
||||||
1469
ssh_manager.py
Normal file
1469
ssh_manager.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user