import upstream GUKO 2026-06-23
This commit is contained in:
86
guko.py
Executable file
86
guko.py
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse, os, subprocess, sys, shlex, json
|
||||
from pathlib import Path
|
||||
|
||||
from auth import resolve_ssh, ssh_args, ssh_display
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
INV = Path(os.environ.get('GUKO_INV') or os.environ.get('VPSPILOT_INV') or ROOT / 'servers.json')
|
||||
|
||||
|
||||
def load_inventory():
|
||||
if not INV.exists():
|
||||
return {'servers': []}
|
||||
return json.loads(INV.read_text() or '{}')
|
||||
|
||||
|
||||
def ssh_env_for(s, inv):
|
||||
cfg = resolve_ssh(s, inv)
|
||||
if cfg.get('auth') == 'password' and cfg.get('password'):
|
||||
env = os.environ.copy()
|
||||
env['SSHPASS'] = str(cfg['password'])
|
||||
return env
|
||||
return None
|
||||
|
||||
|
||||
def health_cmd():
|
||||
return r'''set -e
|
||||
printf 'host='; hostname
|
||||
printf 'uptime='; uptime -p || true
|
||||
printf 'load='; awk '{print $1,$2,$3}' /proc/loadavg
|
||||
printf 'mem='; free -h | awk '/Mem:/ {print $3 "/" $2}'
|
||||
printf 'disk='; df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}'
|
||||
printf 'kernel='; uname -r
|
||||
printf 'ssh='; ss -ltnp 2>/dev/null | grep -E ':(22|53580) ' || true
|
||||
systemctl is-active nezha-agent >/dev/null 2>&1 && echo 'nezha=active' || echo 'nezha=inactive-or-missing'
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(prog='guko', description='GUKO server manager')
|
||||
sub = p.add_subparsers(dest='cmd', required=True)
|
||||
sub.add_parser('list')
|
||||
sub.add_parser('health')
|
||||
runp = sub.add_parser('run'); runp.add_argument('name'); runp.add_argument('command', nargs=argparse.REMAINDER)
|
||||
args = p.parse_args()
|
||||
inv = load_inventory()
|
||||
servers = inv.get('servers') or []
|
||||
if args.cmd == 'list':
|
||||
if not servers:
|
||||
print('no servers in servers.json')
|
||||
for s in servers:
|
||||
extra = f" nezha_id={s.get('nezha_id')}" if s.get('nezha_id') is not None else ''
|
||||
try:
|
||||
endpoint = ssh_display(s, inv)
|
||||
except Exception:
|
||||
endpoint = '<no-host>'
|
||||
print(f"{s.get('name','?')} {endpoint} {s.get('role','')}{extra}")
|
||||
elif args.cmd == 'health':
|
||||
if not servers:
|
||||
print('no servers in servers.json')
|
||||
return
|
||||
for s in servers:
|
||||
if not s.get('host') and not (s.get('ssh') or {}).get('host'):
|
||||
print(f"\n== {s.get('name','?')} (<no-host>) ==")
|
||||
print('skipped: no ssh host in inventory')
|
||||
continue
|
||||
print(f"\n== {s.get('name',s.get('host'))} ({ssh_display(s, inv)}) ==")
|
||||
r = subprocess.run(ssh_args(s, health_cmd(), inv=inv), text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30, env=ssh_env_for(s, inv))
|
||||
print(r.stdout.rstrip())
|
||||
elif args.cmd == 'run':
|
||||
target = next((s for s in servers if s.get('name') == args.name or s.get('host') == args.name or str(s.get('nezha_id')) == args.name), None)
|
||||
if not target:
|
||||
sys.exit(f'not found: {args.name}')
|
||||
if not target.get('host') and not (target.get('ssh') or {}).get('host'):
|
||||
sys.exit(f"no ssh host for: {args.name}")
|
||||
if not args.command:
|
||||
sys.exit('missing command')
|
||||
# Support both styles:
|
||||
# guko.py run host 'systemctl status nginx --no-pager'
|
||||
# guko.py run host systemctl status nginx --no-pager
|
||||
remote = args.command[0] if len(args.command) == 1 else ' '.join(shlex.quote(x) for x in args.command)
|
||||
subprocess.run(ssh_args(target, remote, inv=inv), check=False, env=ssh_env_for(target, inv))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user