dayhoff-tools 1.5.7__py3-none-any.whl → 1.5.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dayhoff-tools might be problematic. Click here for more details.
- dayhoff_tools/cli/engine_commands.py +260 -98
- {dayhoff_tools-1.5.7.dist-info → dayhoff_tools-1.5.9.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.5.7.dist-info → dayhoff_tools-1.5.9.dist-info}/RECORD +5 -5
- {dayhoff_tools-1.5.7.dist-info → dayhoff_tools-1.5.9.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.5.7.dist-info → dayhoff_tools-1.5.9.dist-info}/entry_points.txt +0 -0
|
@@ -409,37 +409,35 @@ def update_ssh_config_entry(engine_name: str, instance_id: str, ssh_user: str, i
|
|
|
409
409
|
|
|
410
410
|
# Read existing config
|
|
411
411
|
content = config_path.read_text()
|
|
412
|
+
lines = content.splitlines() if content else []
|
|
412
413
|
|
|
413
|
-
#
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
else:
|
|
438
|
-
# Append new entry
|
|
439
|
-
content = content.rstrip() + "\n" + new_entry
|
|
414
|
+
# Remove any existing entry for this engine
|
|
415
|
+
new_lines = []
|
|
416
|
+
skip_until_next_host = False
|
|
417
|
+
for line in lines:
|
|
418
|
+
# Check if this is our managed host
|
|
419
|
+
if line.strip().startswith(f"Host {engine_name}") and SSH_MANAGED_COMMENT in line:
|
|
420
|
+
skip_until_next_host = True
|
|
421
|
+
elif line.strip().startswith("Host ") and skip_until_next_host:
|
|
422
|
+
skip_until_next_host = False
|
|
423
|
+
# This is a different host entry, keep it
|
|
424
|
+
new_lines.append(line)
|
|
425
|
+
elif not skip_until_next_host:
|
|
426
|
+
new_lines.append(line)
|
|
427
|
+
|
|
428
|
+
# Add the new entry
|
|
429
|
+
if new_lines and new_lines[-1].strip(): # Add blank line if needed
|
|
430
|
+
new_lines.append("")
|
|
431
|
+
|
|
432
|
+
new_lines.extend([
|
|
433
|
+
f"Host {engine_name} {SSH_MANAGED_COMMENT}",
|
|
434
|
+
f" HostName {instance_id}",
|
|
435
|
+
f" User {ssh_user}",
|
|
436
|
+
f" ProxyCommand sh -c \"AWS_SSM_IDLE_TIMEOUT={idle_timeout} aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'\"",
|
|
437
|
+
])
|
|
440
438
|
|
|
441
439
|
# Write back
|
|
442
|
-
config_path.write_text(
|
|
440
|
+
config_path.write_text("\n".join(new_lines))
|
|
443
441
|
config_path.chmod(0o600)
|
|
444
442
|
|
|
445
443
|
|
|
@@ -547,6 +545,9 @@ def list_engines(
|
|
|
547
545
|
stopped_only: bool = typer.Option(
|
|
548
546
|
False, "--stopped", help="Show only stopped engines"
|
|
549
547
|
),
|
|
548
|
+
detailed: bool = typer.Option(
|
|
549
|
+
False, "--detailed", "-d", help="Show detailed status (slower)"
|
|
550
|
+
),
|
|
550
551
|
):
|
|
551
552
|
"""List engines (shows all engines by default)."""
|
|
552
553
|
current_user = check_aws_sso()
|
|
@@ -554,6 +555,8 @@ def list_engines(
|
|
|
554
555
|
params = {}
|
|
555
556
|
if user:
|
|
556
557
|
params["user"] = user
|
|
558
|
+
if detailed:
|
|
559
|
+
params["check_ready"] = "true"
|
|
557
560
|
|
|
558
561
|
response = make_api_request("GET", "/engines", params=params)
|
|
559
562
|
|
|
@@ -571,8 +574,10 @@ def list_engines(
|
|
|
571
574
|
console.print("No engines found.")
|
|
572
575
|
return
|
|
573
576
|
|
|
574
|
-
#
|
|
575
|
-
stages_map =
|
|
577
|
+
# Only fetch detailed info if requested (slow)
|
|
578
|
+
stages_map = {}
|
|
579
|
+
if detailed:
|
|
580
|
+
stages_map = _fetch_init_stages([e["instance_id"] for e in engines])
|
|
576
581
|
|
|
577
582
|
# Create table
|
|
578
583
|
table = Table(title="Engines", box=box.ROUNDED)
|
|
@@ -581,7 +586,8 @@ def list_engines(
|
|
|
581
586
|
table.add_column("Type")
|
|
582
587
|
table.add_column("User")
|
|
583
588
|
table.add_column("Status")
|
|
584
|
-
|
|
589
|
+
if detailed:
|
|
590
|
+
table.add_column("Disk Usage")
|
|
585
591
|
table.add_column("Uptime/Since")
|
|
586
592
|
table.add_column("$/hour", justify="right")
|
|
587
593
|
|
|
@@ -592,24 +598,34 @@ def list_engines(
|
|
|
592
598
|
|
|
593
599
|
if engine["state"].lower() == "running":
|
|
594
600
|
time_str = format_duration(uptime)
|
|
595
|
-
#
|
|
596
|
-
|
|
601
|
+
# Only get disk usage if detailed mode
|
|
602
|
+
if detailed:
|
|
603
|
+
disk_usage = get_disk_usage_via_ssm(engine["instance_id"]) or "-"
|
|
604
|
+
else:
|
|
605
|
+
disk_usage = None
|
|
597
606
|
else:
|
|
598
607
|
time_str = launch_time.strftime("%Y-%m-%d %H:%M")
|
|
599
|
-
disk_usage = "-"
|
|
608
|
+
disk_usage = "-" if detailed else None
|
|
600
609
|
|
|
601
|
-
|
|
610
|
+
row_data = [
|
|
602
611
|
engine["name"],
|
|
603
612
|
engine["instance_id"],
|
|
604
613
|
engine["engine_type"],
|
|
605
614
|
engine["user"],
|
|
606
615
|
format_status(engine["state"], engine.get("ready")),
|
|
607
|
-
|
|
616
|
+
]
|
|
617
|
+
if detailed:
|
|
618
|
+
row_data.append(disk_usage)
|
|
619
|
+
row_data.extend([
|
|
608
620
|
time_str,
|
|
609
621
|
f"${hourly_cost:.2f}",
|
|
610
|
-
)
|
|
622
|
+
])
|
|
623
|
+
|
|
624
|
+
table.add_row(*row_data)
|
|
611
625
|
|
|
612
626
|
console.print(table)
|
|
627
|
+
if not detailed and any(e["state"].lower() == "running" for e in engines):
|
|
628
|
+
console.print("\n[dim]Tip: Use --detailed to see disk usage and bootstrap status (slower)[/dim]")
|
|
613
629
|
else:
|
|
614
630
|
error = response.json().get("error", "Unknown error")
|
|
615
631
|
console.print(f"[red]❌ Failed to list engines: {error}[/red]")
|
|
@@ -1523,32 +1539,60 @@ def create_ami(
|
|
|
1523
1539
|
|
|
1524
1540
|
# Restore the source engine to a normal state
|
|
1525
1541
|
console.print("Restoring source engine state...")
|
|
1542
|
+
|
|
1543
|
+
# Wait for instance to come back after reboot (AMI creation reboots by default)
|
|
1544
|
+
console.print("[dim]Waiting for engine to reboot after snapshot...[/dim]")
|
|
1545
|
+
ec2_waiter = ec2.get_waiter('instance_status_ok')
|
|
1546
|
+
try:
|
|
1547
|
+
ec2_waiter.wait(
|
|
1548
|
+
InstanceIds=[engine["instance_id"]],
|
|
1549
|
+
WaiterConfig={'Delay': 10, 'MaxAttempts': 30} # Wait up to 5 minutes
|
|
1550
|
+
)
|
|
1551
|
+
except Exception as e:
|
|
1552
|
+
console.print(f"[yellow]⚠️ Warning: Engine may still be rebooting: {e}[/yellow]")
|
|
1553
|
+
|
|
1554
|
+
# Now restore the sentinel and restart services
|
|
1526
1555
|
restore_response = ssm.send_command(
|
|
1527
1556
|
InstanceIds=[engine["instance_id"]],
|
|
1528
1557
|
DocumentName="AWS-RunShellScript",
|
|
1529
1558
|
Parameters={
|
|
1530
1559
|
"commands": [
|
|
1560
|
+
# Ensure the directories exist
|
|
1561
|
+
"sudo mkdir -p /opt/dayhoff /opt/dayhoff/state",
|
|
1562
|
+
# Recreate the sentinel file
|
|
1531
1563
|
"sudo touch /opt/dayhoff/first_boot_complete.sentinel",
|
|
1532
|
-
|
|
1564
|
+
# Mark bootstrap as finished
|
|
1565
|
+
"echo 'finished' | sudo tee /opt/dayhoff/state/engine-init.stage > /dev/null",
|
|
1566
|
+
# Restart idle detector if it exists
|
|
1567
|
+
"sudo systemctl restart engine-idle-detector.timer 2>/dev/null || true",
|
|
1568
|
+
# Ensure SSM agent is running
|
|
1569
|
+
"sudo systemctl start amazon-ssm-agent 2>/dev/null || true",
|
|
1533
1570
|
],
|
|
1534
1571
|
"executionTimeout": ["60"],
|
|
1535
1572
|
},
|
|
1536
1573
|
)
|
|
1537
1574
|
|
|
1538
|
-
#
|
|
1539
|
-
time.sleep(1)
|
|
1575
|
+
# Wait for restore command to complete
|
|
1540
1576
|
restore_command_id = restore_response["Command"]["CommandId"]
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1577
|
+
for _ in range(10):
|
|
1578
|
+
time.sleep(2)
|
|
1579
|
+
result = ssm.get_command_invocation(
|
|
1580
|
+
CommandId=restore_command_id,
|
|
1581
|
+
InstanceId=engine["instance_id"],
|
|
1582
|
+
)
|
|
1583
|
+
if result["Status"] in ["Success", "Failed"]:
|
|
1584
|
+
break
|
|
1585
|
+
|
|
1586
|
+
if result["Status"] == "Success":
|
|
1546
1587
|
console.print(
|
|
1547
|
-
"[
|
|
1588
|
+
"[green]✓ Source engine restored to normal operation.[/green]"
|
|
1548
1589
|
)
|
|
1549
1590
|
else:
|
|
1550
1591
|
console.print(
|
|
1551
|
-
"[
|
|
1592
|
+
"[yellow]⚠️ Warning: Engine state restoration incomplete. You may need to run:[/yellow]"
|
|
1593
|
+
)
|
|
1594
|
+
console.print(
|
|
1595
|
+
f"[dim] dh engine repair {engine['name']}[/dim]"
|
|
1552
1596
|
)
|
|
1553
1597
|
|
|
1554
1598
|
console.print(
|
|
@@ -1792,52 +1836,28 @@ def attach_studio(
|
|
|
1792
1836
|
|
|
1793
1837
|
console.print(f"Attaching studio to engine [cyan]{engine['name']}[/cyan]...")
|
|
1794
1838
|
|
|
1795
|
-
# Determine retry strategy
|
|
1796
|
-
max_attempts = 40 if engine_started_now else 3
|
|
1797
|
-
retry_delay = 10 if engine_started_now else 3
|
|
1798
|
-
|
|
1839
|
+
# Determine retry strategy based on whether we just started the engine
|
|
1799
1840
|
if engine_started_now:
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
TimeElapsedColumn(),
|
|
1804
|
-
TextColumn("[progress.description]{task.description}"),
|
|
1805
|
-
transient=True,
|
|
1806
|
-
) as prog:
|
|
1807
|
-
task = prog.add_task(
|
|
1808
|
-
"Attaching studio (engine is still booting)…", total=None
|
|
1809
|
-
)
|
|
1810
|
-
|
|
1811
|
-
for attempt in range(max_attempts):
|
|
1812
|
-
success, error_msg = _attempt_studio_attach(
|
|
1813
|
-
studio, engine, target_user, public_key
|
|
1814
|
-
)
|
|
1815
|
-
|
|
1816
|
-
if success:
|
|
1817
|
-
break # success!
|
|
1818
|
-
|
|
1819
|
-
# Update spinner every 3rd try to avoid log spam
|
|
1820
|
-
if attempt % 3 == 0:
|
|
1821
|
-
prog.update(
|
|
1822
|
-
task,
|
|
1823
|
-
description=f"Attaching studio (engine is still booting)… {attempt+1}/{max_attempts}",
|
|
1824
|
-
)
|
|
1825
|
-
|
|
1826
|
-
if error_msg:
|
|
1827
|
-
console.print(f"[red]❌ Failed to attach studio: {error_msg}[/red]")
|
|
1828
|
-
return
|
|
1829
|
-
|
|
1830
|
-
time.sleep(retry_delay)
|
|
1831
|
-
|
|
1832
|
-
else:
|
|
1833
|
-
console.print(
|
|
1834
|
-
"[yellow]Engine is still starting up – please retry in a minute.[/yellow]"
|
|
1835
|
-
)
|
|
1836
|
-
return
|
|
1841
|
+
max_attempts = 40 # About 7 minutes total with exponential backoff
|
|
1842
|
+
base_delay = 8
|
|
1843
|
+
max_delay = 20
|
|
1837
1844
|
else:
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1845
|
+
max_attempts = 15 # About 2 minutes total with exponential backoff
|
|
1846
|
+
base_delay = 5
|
|
1847
|
+
max_delay = 10
|
|
1848
|
+
|
|
1849
|
+
# Unified retry loop with exponential backoff
|
|
1850
|
+
with Progress(
|
|
1851
|
+
SpinnerColumn(),
|
|
1852
|
+
TimeElapsedColumn(),
|
|
1853
|
+
TextColumn("[progress.description]{task.description}"),
|
|
1854
|
+
transient=True,
|
|
1855
|
+
) as prog:
|
|
1856
|
+
desc = "Attaching studio (engine is still booting)…" if engine_started_now else "Attaching studio…"
|
|
1857
|
+
task = prog.add_task(desc, total=None)
|
|
1858
|
+
|
|
1859
|
+
consecutive_not_ready = 0
|
|
1860
|
+
last_error = None
|
|
1841
1861
|
|
|
1842
1862
|
for attempt in range(max_attempts):
|
|
1843
1863
|
success, error_msg = _attempt_studio_attach(
|
|
@@ -1845,22 +1865,54 @@ def attach_studio(
|
|
|
1845
1865
|
)
|
|
1846
1866
|
|
|
1847
1867
|
if success:
|
|
1848
|
-
break #
|
|
1868
|
+
break # success!
|
|
1849
1869
|
|
|
1850
1870
|
if error_msg:
|
|
1851
|
-
# Fatal – bubble up immediately
|
|
1871
|
+
# Fatal error – bubble up immediately
|
|
1852
1872
|
console.print(f"[red]❌ Failed to attach studio: {error_msg}[/red]")
|
|
1873
|
+
|
|
1874
|
+
# Suggest repair command if engine seems broken
|
|
1875
|
+
if "not ready" in error_msg.lower() and attempt > 5:
|
|
1876
|
+
console.print(f"\n[yellow]Engine may be in a bad state. Try:[/yellow]")
|
|
1877
|
+
console.print(f"[dim] dh engine repair {engine['name']}[/dim]")
|
|
1853
1878
|
return
|
|
1854
1879
|
|
|
1855
|
-
#
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1880
|
+
# Track consecutive "not ready" responses
|
|
1881
|
+
consecutive_not_ready += 1
|
|
1882
|
+
last_error = "Engine not ready"
|
|
1883
|
+
|
|
1884
|
+
# Update progress display
|
|
1885
|
+
if attempt % 3 == 0:
|
|
1886
|
+
prog.update(
|
|
1887
|
+
task,
|
|
1888
|
+
description=f"{desc} attempt {attempt+1}/{max_attempts}",
|
|
1889
|
+
)
|
|
1890
|
+
|
|
1891
|
+
# If engine seems stuck after many attempts, show a hint
|
|
1892
|
+
if consecutive_not_ready > 10 and attempt == 10:
|
|
1893
|
+
console.print(
|
|
1894
|
+
"[yellow]Engine is taking longer than expected to become ready.[/yellow]"
|
|
1895
|
+
)
|
|
1896
|
+
console.print(
|
|
1897
|
+
"[dim]This can happen after GAMI creation or if the engine is still bootstrapping.[/dim]"
|
|
1898
|
+
)
|
|
1899
|
+
|
|
1900
|
+
# Exponential backoff with jitter
|
|
1901
|
+
delay = min(base_delay * (1.5 ** min(attempt, 5)), max_delay)
|
|
1902
|
+
delay += time.time() % 2 # Add 0-2 seconds of jitter
|
|
1903
|
+
time.sleep(delay)
|
|
1859
1904
|
|
|
1860
1905
|
else:
|
|
1906
|
+
# All attempts exhausted
|
|
1861
1907
|
console.print(
|
|
1862
|
-
"[yellow]Engine is
|
|
1908
|
+
f"[yellow]Engine is not becoming ready after {max_attempts} attempts.[/yellow]"
|
|
1863
1909
|
)
|
|
1910
|
+
if last_error:
|
|
1911
|
+
console.print(f"[dim]Last issue: {last_error}[/dim]")
|
|
1912
|
+
console.print("\n[yellow]You can try:[/yellow]")
|
|
1913
|
+
console.print(f" 1. Wait a minute and retry: [cyan]dh studio attach {engine['name']}[/cyan]")
|
|
1914
|
+
console.print(f" 2. Check engine status: [cyan]dh engine status {engine['name']}[/cyan]")
|
|
1915
|
+
console.print(f" 3. Repair the engine: [cyan]dh engine repair {engine['name']}[/cyan]")
|
|
1864
1916
|
return
|
|
1865
1917
|
|
|
1866
1918
|
# Successful attach path
|
|
@@ -2453,3 +2505,113 @@ def debug_engine(
|
|
|
2453
2505
|
|
|
2454
2506
|
except Exception as e:
|
|
2455
2507
|
console.print(f"[cyan]{name}:[/cyan] [red]ERROR: {e}[/red]\n")
|
|
2508
|
+
|
|
2509
|
+
|
|
2510
|
+
@engine_app.command("repair")
|
|
2511
|
+
def repair_engine(
|
|
2512
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
|
2513
|
+
):
|
|
2514
|
+
"""Repair an engine that's stuck in a bad state (e.g., after GAMI creation)."""
|
|
2515
|
+
check_aws_sso()
|
|
2516
|
+
|
|
2517
|
+
# Get all engines to resolve name
|
|
2518
|
+
response = make_api_request("GET", "/engines")
|
|
2519
|
+
if response.status_code != 200:
|
|
2520
|
+
console.print("[red]❌ Failed to fetch engines[/red]")
|
|
2521
|
+
raise typer.Exit(1)
|
|
2522
|
+
|
|
2523
|
+
engines = response.json().get("engines", [])
|
|
2524
|
+
engine = resolve_engine(name_or_id, engines)
|
|
2525
|
+
|
|
2526
|
+
if engine["state"].lower() != "running":
|
|
2527
|
+
console.print(f"[yellow]⚠️ Engine is {engine['state']}. Must be running to repair.[/yellow]")
|
|
2528
|
+
if engine["state"].lower() == "stopped" and Confirm.ask("Start the engine first?"):
|
|
2529
|
+
response = make_api_request("POST", f"/engines/{engine['instance_id']}/start")
|
|
2530
|
+
if response.status_code != 200:
|
|
2531
|
+
console.print("[red]❌ Failed to start engine[/red]")
|
|
2532
|
+
raise typer.Exit(1)
|
|
2533
|
+
console.print("[green]✓ Engine started[/green]")
|
|
2534
|
+
console.print("Waiting for engine to become ready...")
|
|
2535
|
+
time.sleep(30) # Give it time to boot
|
|
2536
|
+
else:
|
|
2537
|
+
raise typer.Exit(1)
|
|
2538
|
+
|
|
2539
|
+
console.print(f"[bold]Repairing engine [cyan]{engine['name']}[/cyan][/bold]")
|
|
2540
|
+
console.print("[dim]This will restore bootstrap state and ensure all services are running[/dim]\n")
|
|
2541
|
+
|
|
2542
|
+
ssm = boto3.client("ssm", region_name="us-east-1")
|
|
2543
|
+
|
|
2544
|
+
# Repair commands
|
|
2545
|
+
repair_commands = [
|
|
2546
|
+
# Create necessary directories
|
|
2547
|
+
"sudo mkdir -p /opt/dayhoff /opt/dayhoff/state /opt/dayhoff/scripts",
|
|
2548
|
+
|
|
2549
|
+
# Download scripts from S3 if missing
|
|
2550
|
+
"source /etc/engine.env && sudo aws s3 sync s3://${VM_SCRIPTS_BUCKET}/ /opt/dayhoff/scripts/ --exclude '*' --include '*.sh' --quiet",
|
|
2551
|
+
"sudo chmod +x /opt/dayhoff/scripts/*.sh 2>/dev/null || true",
|
|
2552
|
+
|
|
2553
|
+
# Restore bootstrap state
|
|
2554
|
+
"sudo touch /opt/dayhoff/first_boot_complete.sentinel",
|
|
2555
|
+
"echo 'finished' | sudo tee /opt/dayhoff/state/engine-init.stage > /dev/null",
|
|
2556
|
+
|
|
2557
|
+
# Ensure SSM agent is running
|
|
2558
|
+
"sudo systemctl restart amazon-ssm-agent 2>/dev/null || true",
|
|
2559
|
+
|
|
2560
|
+
# Restart idle detector
|
|
2561
|
+
"sudo systemctl restart engine-idle-detector.timer 2>/dev/null || true",
|
|
2562
|
+
"sudo systemctl restart engine-idle-detector.service 2>/dev/null || true",
|
|
2563
|
+
|
|
2564
|
+
# Report status
|
|
2565
|
+
"echo '=== Repair Complete ===' && echo 'Sentinel: ' && ls -la /opt/dayhoff/first_boot_complete.sentinel",
|
|
2566
|
+
"echo 'Stage: ' && cat /opt/dayhoff/state/engine-init.stage",
|
|
2567
|
+
"echo 'Scripts: ' && ls /opt/dayhoff/scripts/*.sh 2>/dev/null | wc -l",
|
|
2568
|
+
]
|
|
2569
|
+
|
|
2570
|
+
try:
|
|
2571
|
+
with Progress(
|
|
2572
|
+
SpinnerColumn(),
|
|
2573
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2574
|
+
transient=True,
|
|
2575
|
+
) as progress:
|
|
2576
|
+
task = progress.add_task("Repairing engine...", total=None)
|
|
2577
|
+
|
|
2578
|
+
response = ssm.send_command(
|
|
2579
|
+
InstanceIds=[engine["instance_id"]],
|
|
2580
|
+
DocumentName="AWS-RunShellScript",
|
|
2581
|
+
Parameters={
|
|
2582
|
+
"commands": repair_commands,
|
|
2583
|
+
"executionTimeout": ["60"],
|
|
2584
|
+
},
|
|
2585
|
+
)
|
|
2586
|
+
|
|
2587
|
+
command_id = response["Command"]["CommandId"]
|
|
2588
|
+
|
|
2589
|
+
# Wait for command
|
|
2590
|
+
for _ in range(60):
|
|
2591
|
+
time.sleep(1)
|
|
2592
|
+
result = ssm.get_command_invocation(
|
|
2593
|
+
CommandId=command_id,
|
|
2594
|
+
InstanceId=engine["instance_id"],
|
|
2595
|
+
)
|
|
2596
|
+
if result["Status"] in ["Success", "Failed"]:
|
|
2597
|
+
break
|
|
2598
|
+
|
|
2599
|
+
if result["Status"] == "Success":
|
|
2600
|
+
output = result["StandardOutputContent"]
|
|
2601
|
+
console.print("[green]✓ Engine repaired successfully![/green]\n")
|
|
2602
|
+
|
|
2603
|
+
# Show repair results
|
|
2604
|
+
if "=== Repair Complete ===" in output:
|
|
2605
|
+
repair_section = output.split("=== Repair Complete ===")[1].strip()
|
|
2606
|
+
console.print("[bold]Repair Results:[/bold]")
|
|
2607
|
+
console.print(repair_section)
|
|
2608
|
+
|
|
2609
|
+
console.print("\n[dim]You should now be able to attach studios to this engine.[/dim]")
|
|
2610
|
+
else:
|
|
2611
|
+
console.print(
|
|
2612
|
+
f"[red]❌ Repair failed: {result.get('StandardErrorContent', 'Unknown error')}[/red]"
|
|
2613
|
+
)
|
|
2614
|
+
console.print("\n[yellow]Try running 'dh engine debug' for more information.[/yellow]")
|
|
2615
|
+
|
|
2616
|
+
except Exception as e:
|
|
2617
|
+
console.print(f"[red]❌ Failed to repair engine: {e}[/red]")
|
|
@@ -3,7 +3,7 @@ dayhoff_tools/chemistry/standardizer.py,sha256=uMn7VwHnx02nc404eO6fRuS4rsl4dvSPf
|
|
|
3
3
|
dayhoff_tools/chemistry/utils.py,sha256=jt-7JgF-GeeVC421acX-bobKbLU_X94KNOW24p_P-_M,2257
|
|
4
4
|
dayhoff_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
dayhoff_tools/cli/cloud_commands.py,sha256=33qcWLmq-FwEXMdL3F0OHm-5Stlh2r65CldyEZgQ1no,40904
|
|
6
|
-
dayhoff_tools/cli/engine_commands.py,sha256=
|
|
6
|
+
dayhoff_tools/cli/engine_commands.py,sha256=RSbZK2hN3bTBQrZxAgooqMsqSiw0hnhmzTVkL44xdD4,97147
|
|
7
7
|
dayhoff_tools/cli/main.py,sha256=tRN7WCBHg6uyNp6rA54pKTCoVmBntta2i0Yas3bUpZ4,4853
|
|
8
8
|
dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
|
|
9
9
|
dayhoff_tools/cli/utility_commands.py,sha256=FRZTPrjsG_qmIIqoNxd1Q1vVkS_5w8aY33IrVYVNCLg,18131
|
|
@@ -27,7 +27,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
|
|
|
27
27
|
dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
|
|
28
28
|
dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
|
|
29
29
|
dayhoff_tools/warehouse.py,sha256=heaYc64qplgN3_1WVPFmqj53goStioWwY5NqlWc4c0s,24453
|
|
30
|
-
dayhoff_tools-1.5.
|
|
31
|
-
dayhoff_tools-1.5.
|
|
32
|
-
dayhoff_tools-1.5.
|
|
33
|
-
dayhoff_tools-1.5.
|
|
30
|
+
dayhoff_tools-1.5.9.dist-info/METADATA,sha256=mGdK_rLBA7HwjkUI7e2sQ5oGExLsBU8A-GIBD9-xyaM,2914
|
|
31
|
+
dayhoff_tools-1.5.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
32
|
+
dayhoff_tools-1.5.9.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
|
33
|
+
dayhoff_tools-1.5.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|