Some checks failed
Smoke Test / smoke (pull_request) Failing after 23s
Architecture Lint / Linter Tests (pull_request) Successful in 26s
Validate Config / YAML Lint (pull_request) Failing after 15s
Validate Config / JSON Validate (pull_request) Successful in 19s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 1m1s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Shell Script Lint (pull_request) Failing after 1m4s
Validate Config / Cron Syntax Check (pull_request) Successful in 13s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 13s
Validate Config / Playbook Schema Validation (pull_request) Successful in 25s
Architecture Lint / Lint Repository (pull_request) Failing after 22s
PR Checklist / pr-checklist (pull_request) Successful in 5m0s
Add fleet.inventory and fleet.path_contracts to config.yaml: - Central source of truth for IPs, ports, roles, remote paths - Introduce get_config_path(), load_fleet_inventory(), get_path_contract() - Updated fleet_llama.py, self_healing.py, telemetry.py, agent_dispatch.py, skill_installer.py to read from config instead of hard-coded dicts/paths - Documented inventory contract and override mechanism in scripts/README.md Scripts retain forward-compatible fallback defaults for backwards compatibility. Closes #433
119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
[OPS] Sovereign Skill Installer
|
|
Part of the Gemini Sovereign Infrastructure Suite.
|
|
|
|
Packages and installs Hermes skills onto remote wizard nodes.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
#!/usr/bin/env python3
|
|
"""
|
|
[OPS] Sovereign Skill Installer
|
|
Part of the Gemini Sovereign Infrastructure Suite.
|
|
|
|
Packages and installs Hermes skills onto remote wizard nodes.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import subprocess
|
|
from pathlib import Path
|
|
import yaml
|
|
|
|
# --- CONFIGURATION ---
|
|
# Load fleet inventory and path contracts from central timmy-config
|
|
|
|
def get_config_path():
|
|
return os.environ.get('TIMMY_CONFIG') or os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)), '..', 'config.yaml'
|
|
)
|
|
|
|
def load_fleet_inventory():
|
|
"""Return dict of {{host: {{ip, port, role, remote_root, capabilities}}}}"""
|
|
try:
|
|
with open(get_config_path(), 'r') as f:
|
|
cfg = yaml.safe_load(f)
|
|
inv = cfg.get('fleet', {}).get('inventory', {})
|
|
return inv if inv else {}
|
|
except Exception:
|
|
return {}
|
|
|
|
def get_path_contract(key, default):
|
|
"""Return path contract value from config."""
|
|
try:
|
|
with open(get_config_path(), 'r') as f:
|
|
cfg = yaml.safe_load(f)
|
|
return cfg.get('fleet', {}).get('path_contracts', {}).get(key, default)
|
|
except Exception:
|
|
return default
|
|
|
|
FLEET = load_fleet_inventory()
|
|
HERMES_ROOT = get_path_contract('hermes_agent_local', '../hermes-agent')
|
|
REMOTE_ROOT = get_path_contract('hermes_remote', '/opt/hermes')
|
|
SKILLS_DIR = "skills"
|
|
|
|
|
|
class SkillInstaller:
|
|
def __init__(self, host: str, ip: str):
|
|
self.host = host
|
|
self.ip = ip
|
|
self.hermes_path = Path(HERMES_ROOT).expanduser().resolve()
|
|
|
|
def log(self, message: str):
|
|
print(f"[*] {message}")
|
|
|
|
def error(self, message: str):
|
|
print(f"[!] ERROR: {message}")
|
|
sys.exit(1)
|
|
|
|
def install_skill(self, skill_name: str):
|
|
self.log(f"Installing skill '{skill_name}' to {self.host} ({self.ip})...")
|
|
|
|
skill_path = self.hermes_path / SKILLS_DIR / skill_name
|
|
if not skill_path.exists():
|
|
self.error(f"Skill '{skill_name}' not found in {skill_path}")
|
|
|
|
# 1. Compress skill
|
|
self.log("Compressing skill...")
|
|
tar_file = f"{skill_name}.tar.gz"
|
|
subprocess.run(["tar", "-czf", tar_file, "-C", str(skill_path.parent), skill_name])
|
|
|
|
# 2. Upload to remote
|
|
self.log("Uploading to remote...")
|
|
remote_path = f"{REMOTE_ROOT}/skills/{skill_name}"
|
|
subprocess.run(["ssh", f"root@{self.ip}", f"mkdir -p {REMOTE_ROOT}/skills"])
|
|
subprocess.run(["scp", tar_file, f"root@{self.ip}:/tmp/"])
|
|
|
|
# 3. Extract and register
|
|
self.log("Extracting and registering...")
|
|
extract_cmd = f"tar -xzf /tmp/{tar_file} -C {REMOTE_ROOT}/skills/ && rm /tmp/{tar_file}"
|
|
subprocess.run(["ssh", f"root@{self.ip}", extract_cmd])
|
|
|
|
# Registration logic (simplified)
|
|
# In a real scenario, we'd update the wizard's config.yaml
|
|
self.log(f"[SUCCESS] Skill '{skill_name}' installed on {self.host}")
|
|
|
|
# Cleanup local tar
|
|
os.remove(tar_file)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Gemini Skill Installer")
|
|
parser.add_argument("host", help="Target host name")
|
|
parser.add_argument("ip", help="Target host IP")
|
|
parser.add_argument("skill", help="Skill name to install")
|
|
|
|
args = parser.parse_args()
|
|
|
|
installer = SkillInstaller(args.host, args.ip)
|
|
installer.install_skill(args.skill)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|