golem-vm-provider 0.1.17__py3-none-any.whl → 0.1.19__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.
- {golem_vm_provider-0.1.17.dist-info → golem_vm_provider-0.1.19.dist-info}/METADATA +1 -1
- {golem_vm_provider-0.1.17.dist-info → golem_vm_provider-0.1.19.dist-info}/RECORD +8 -7
- provider/config.py +21 -3
- provider/main.py +6 -2
- provider/utils/setup.py +100 -0
- provider/vm/proxy_manager.py +19 -6
- {golem_vm_provider-0.1.17.dist-info → golem_vm_provider-0.1.19.dist-info}/WHEEL +0 -0
- {golem_vm_provider-0.1.17.dist-info → golem_vm_provider-0.1.19.dist-info}/entry_points.txt +0 -0
@@ -2,25 +2,26 @@ provider/__init__.py,sha256=HO1fkPpZqPO3z8O8-eVIyx8xXSMIVuTR_b1YF0RtXOg,45
|
|
2
2
|
provider/api/__init__.py,sha256=ssX1ugDqEPt8Fn04IymgmG-Ev8PiXLsCSaiZVvHQnec,344
|
3
3
|
provider/api/models.py,sha256=JOzoNf1oE5N97UqTN5xuIrTkqn2tCHqPDaIzGA3jUyo,3513
|
4
4
|
provider/api/routes.py,sha256=P27RQvNqFWn6PacRwr1PaVz-yv5KAWsp9KeORejkXSI,6452
|
5
|
-
provider/config.py,sha256
|
5
|
+
provider/config.py,sha256=-Cu05ebOjUbhnh5iv3raQ7Z79HMhZ9EcRIRrZVW3Ino,14513
|
6
6
|
provider/discovery/__init__.py,sha256=VR3NRoQtZRH5Vs8FG7jnGLR7p7wn7XeZdLaBb3t8e1g,123
|
7
7
|
provider/discovery/advertiser.py,sha256=yv7RbRf1K43qOLAEa2Olj9hhN8etl2qsBuoHok0xoVs,6784
|
8
8
|
provider/discovery/resource_tracker.py,sha256=8dYhJxoe_jLRwisHoA0jr575YhUKmLIqSXfW88KshcQ,6000
|
9
|
-
provider/main.py,sha256=
|
9
|
+
provider/main.py,sha256=jl80WRIvsvfbHFvoPZqtIhFDUJm0andcyXqUu6Dfi2E,9389
|
10
10
|
provider/network/port_verifier.py,sha256=AUtBGuZdfq9Jt4BRDuYesh5YEmwneEzYUgIw-uajZhA,12977
|
11
11
|
provider/security/ethereum.py,sha256=SDRDbcjynbVy44kNnxlDcYLL0BZ3Qnc0DvmneQ-WKLE,1383
|
12
12
|
provider/utils/ascii_art.py,sha256=ykBFsztk57GIiz1NJ-EII5UvN74iECqQL4h9VmiW6Z8,3161
|
13
13
|
provider/utils/logging.py,sha256=C_elr0sJROHKQgErYpHJQvfujgh0k4Zf2gg8ZKfrmVk,2590
|
14
14
|
provider/utils/port_display.py,sha256=5d_604Eo-82dqx_yV2ZScq7bKQ8IsXacc-yXC_KAz3A,11031
|
15
15
|
provider/utils/retry.py,sha256=ekP2ucaSJNN-lBcrIvyHa4QYPKNITMl1a5V1X6BBvsw,1560
|
16
|
+
provider/utils/setup.py,sha256=Z5dLuBQkb5vdoQsu1HJZwXmu9NWsiBYJ7Vq9-C-_tY8,2932
|
16
17
|
provider/vm/__init__.py,sha256=JGs50tUmzOR1rQ_w4fMY_3XWylmiA1G7KKWZkVw51mY,501
|
17
18
|
provider/vm/cloud_init.py,sha256=E5dDH7dqStRcJNDfbarBBe83-c9N63W8B5ycIrHI8eU,4627
|
18
19
|
provider/vm/models.py,sha256=zkfvP5Z50SPDNajwZTt9NTDIMRQIsZLvSOsuirHEcJM,6256
|
19
20
|
provider/vm/multipass.py,sha256=FOcsfcJ-NrgBg_fvq_CKOKsQ0xOmk7Z34KXi3ag_Vl8,16603
|
20
21
|
provider/vm/name_mapper.py,sha256=MrshNeJ4Dw-WBsyiIVcn9N5xyOxaBKX4Yqhyh_m5IFg,4103
|
21
22
|
provider/vm/port_manager.py,sha256=d03uwU76vx6LgADMN8ffBT9t400XQ3vtYlXr6cLIFN0,9831
|
22
|
-
provider/vm/proxy_manager.py,sha256=
|
23
|
-
golem_vm_provider-0.1.
|
24
|
-
golem_vm_provider-0.1.
|
25
|
-
golem_vm_provider-0.1.
|
26
|
-
golem_vm_provider-0.1.
|
23
|
+
provider/vm/proxy_manager.py,sha256=k12bjq1WkizkpUJIyFEoHgT21vhy0l2pSggp3m-8bFc,10895
|
24
|
+
golem_vm_provider-0.1.19.dist-info/METADATA,sha256=zt40xePOqxAmby_p-Keplksm4vnBXJ8k3H5G4H6FEqs,10594
|
25
|
+
golem_vm_provider-0.1.19.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
26
|
+
golem_vm_provider-0.1.19.dist-info/entry_points.txt,sha256=E4rCWo_Do_2zCG_GewNuftfVlHF_8b_OvioZre0dfeA,54
|
27
|
+
golem_vm_provider-0.1.19.dist-info/RECORD,,
|
provider/config.py
CHANGED
@@ -64,6 +64,7 @@ class Settings(BaseSettings):
|
|
64
64
|
"""Resolve and create cloud-init directory path."""
|
65
65
|
import platform
|
66
66
|
import tempfile
|
67
|
+
from .utils.setup import setup_cloud_init_dir, check_setup_needed, mark_setup_complete
|
67
68
|
|
68
69
|
def verify_dir_permissions(path: Path) -> bool:
|
69
70
|
"""Verify directory has correct permissions and is accessible."""
|
@@ -86,15 +87,32 @@ class Settings(BaseSettings):
|
|
86
87
|
if system == "linux" and Path("/snap/bin/multipass").exists():
|
87
88
|
# Linux with snap
|
88
89
|
path = Path("/var/snap/multipass/common/cloud-init")
|
90
|
+
|
91
|
+
# Check if we need to set up permissions
|
92
|
+
if check_setup_needed():
|
93
|
+
logger.info("First run detected, setting up cloud-init directory...")
|
94
|
+
success, error = setup_cloud_init_dir(path)
|
95
|
+
if success:
|
96
|
+
logger.info("✓ Cloud-init directory setup complete")
|
97
|
+
mark_setup_complete()
|
98
|
+
else:
|
99
|
+
logger.error(f"Failed to set up cloud-init directory: {error}")
|
100
|
+
logger.error("\nTo fix this manually, run these commands:")
|
101
|
+
logger.error(" sudo mkdir -p /var/snap/multipass/common/cloud-init")
|
102
|
+
logger.error(" sudo chown -R $USER:$USER /var/snap/multipass/common/cloud-init")
|
103
|
+
logger.error(" sudo chmod -R 755 /var/snap/multipass/common/cloud-init\n")
|
104
|
+
# Fall back to user's home directory
|
105
|
+
path = Path.home() / ".local" / "share" / "golem" / "provider" / "cloud-init"
|
106
|
+
|
89
107
|
elif system == "linux":
|
90
108
|
# Linux without snap
|
91
|
-
path = Path("/
|
109
|
+
path = Path.home() / ".local" / "share" / "golem" / "provider" / "cloud-init"
|
92
110
|
elif system == "darwin":
|
93
111
|
# macOS
|
94
|
-
path = Path("
|
112
|
+
path = Path.home() / "Library" / "Application Support" / "golem" / "provider" / "cloud-init"
|
95
113
|
elif system == "windows":
|
96
114
|
# Windows
|
97
|
-
path = Path(os.path.expandvars("%
|
115
|
+
path = Path(os.path.expandvars("%LOCALAPPDATA%")) / "golem" / "provider" / "cloud-init"
|
98
116
|
else:
|
99
117
|
path = Path.home() / ".golem" / "provider" / "cloud-init"
|
100
118
|
|
provider/main.py
CHANGED
@@ -31,11 +31,15 @@ async def setup_provider() -> None:
|
|
31
31
|
provider = MultipassProvider(resource_tracker, port_manager=port_manager)
|
32
32
|
try:
|
33
33
|
await asyncio.wait_for(provider.initialize(), timeout=30)
|
34
|
-
app.state.provider = provider
|
35
34
|
|
36
|
-
# Store proxy manager
|
35
|
+
# Store provider and proxy manager references
|
36
|
+
app.state.provider = provider
|
37
37
|
app.state.proxy_manager = provider.proxy_manager
|
38
38
|
|
39
|
+
# Restore proxy configurations
|
40
|
+
logger.process("🔄 Restoring proxy configurations...")
|
41
|
+
await app.state.proxy_manager._load_state()
|
42
|
+
|
39
43
|
except asyncio.TimeoutError:
|
40
44
|
logger.error("Provider initialization timed out")
|
41
45
|
raise
|
provider/utils/setup.py
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
import os
|
2
|
+
import subprocess
|
3
|
+
import platform
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Tuple
|
6
|
+
|
7
|
+
from .logging import setup_logger
|
8
|
+
|
9
|
+
logger = setup_logger(__name__)
|
10
|
+
|
11
|
+
def run_sudo_command(cmd: str) -> Tuple[bool, str]:
|
12
|
+
"""Run a command with sudo.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
cmd: Command to run
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
Tuple of (success, error_message)
|
19
|
+
"""
|
20
|
+
try:
|
21
|
+
# Try non-interactive sudo first
|
22
|
+
result = subprocess.run(
|
23
|
+
f"sudo -n {cmd}",
|
24
|
+
shell=True,
|
25
|
+
capture_output=True,
|
26
|
+
text=True
|
27
|
+
)
|
28
|
+
if result.returncode == 0:
|
29
|
+
return True, ""
|
30
|
+
|
31
|
+
# If that fails, try interactive sudo
|
32
|
+
logger.warning("Non-interactive sudo failed, will prompt for password")
|
33
|
+
result = subprocess.run(
|
34
|
+
f"sudo {cmd}",
|
35
|
+
shell=True,
|
36
|
+
capture_output=True,
|
37
|
+
text=True
|
38
|
+
)
|
39
|
+
if result.returncode == 0:
|
40
|
+
return True, ""
|
41
|
+
|
42
|
+
return False, result.stderr
|
43
|
+
|
44
|
+
except Exception as e:
|
45
|
+
return False, str(e)
|
46
|
+
|
47
|
+
def setup_cloud_init_dir(path: Path) -> Tuple[bool, str]:
|
48
|
+
"""Set up cloud-init directory with correct permissions.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
path: Path to cloud-init directory
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
Tuple of (success, error_message)
|
55
|
+
"""
|
56
|
+
if platform.system().lower() != "linux" or not Path("/snap/bin/multipass").exists():
|
57
|
+
# Only needed for Linux with snap
|
58
|
+
return True, ""
|
59
|
+
|
60
|
+
try:
|
61
|
+
# Create directory
|
62
|
+
success, error = run_sudo_command(f"mkdir -p {path}")
|
63
|
+
if not success:
|
64
|
+
return False, f"Failed to create directory: {error}"
|
65
|
+
|
66
|
+
# Set ownership
|
67
|
+
user = os.environ.get("USER", os.environ.get("USERNAME"))
|
68
|
+
success, error = run_sudo_command(f"chown -R {user}:{user} {path}")
|
69
|
+
if not success:
|
70
|
+
return False, f"Failed to set ownership: {error}"
|
71
|
+
|
72
|
+
# Set permissions
|
73
|
+
success, error = run_sudo_command(f"chmod -R 755 {path}")
|
74
|
+
if not success:
|
75
|
+
return False, f"Failed to set permissions: {error}"
|
76
|
+
|
77
|
+
return True, ""
|
78
|
+
|
79
|
+
except Exception as e:
|
80
|
+
return False, str(e)
|
81
|
+
|
82
|
+
def check_setup_needed() -> bool:
|
83
|
+
"""Check if setup is needed.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
True if setup is needed, False otherwise
|
87
|
+
"""
|
88
|
+
# Only needed for Linux with snap
|
89
|
+
if platform.system().lower() != "linux" or not Path("/snap/bin/multipass").exists():
|
90
|
+
return False
|
91
|
+
|
92
|
+
# Check if setup has already been completed
|
93
|
+
setup_flag = Path.home() / ".golem" / "provider" / ".setup-complete"
|
94
|
+
return not setup_flag.exists()
|
95
|
+
|
96
|
+
def mark_setup_complete() -> None:
|
97
|
+
"""Mark setup as complete."""
|
98
|
+
setup_flag = Path.home() / ".golem" / "provider" / ".setup-complete"
|
99
|
+
setup_flag.parent.mkdir(parents=True, exist_ok=True)
|
100
|
+
setup_flag.touch()
|
provider/vm/proxy_manager.py
CHANGED
@@ -158,18 +158,31 @@ class PythonProxyManager:
|
|
158
158
|
self.port_manager = port_manager
|
159
159
|
self.state_file = state_file or os.path.expanduser("~/.golem/provider/proxy_state.json")
|
160
160
|
self._proxies: Dict[str, ProxyServer] = {} # vm_id -> ProxyServer
|
161
|
-
|
161
|
+
# Note: _load_state is now async and will be called explicitly during provider setup
|
162
162
|
|
163
|
-
def _load_state(self) -> None:
|
164
|
-
"""Load proxy state from file."""
|
163
|
+
async def _load_state(self) -> None:
|
164
|
+
"""Load and restore proxy state from file."""
|
165
165
|
try:
|
166
166
|
state_path = Path(self.state_file)
|
167
167
|
if state_path.exists():
|
168
168
|
with open(state_path, 'r') as f:
|
169
169
|
state = json.load(f)
|
170
|
-
#
|
171
|
-
|
172
|
-
|
170
|
+
# Restore proxy servers from saved state
|
171
|
+
restore_tasks = []
|
172
|
+
for vm_id, proxy_info in state.items():
|
173
|
+
# Create task to restore proxy
|
174
|
+
task = self.add_vm(
|
175
|
+
vm_id=vm_id,
|
176
|
+
vm_ip=proxy_info['target'],
|
177
|
+
port=proxy_info['port']
|
178
|
+
)
|
179
|
+
restore_tasks.append(task)
|
180
|
+
|
181
|
+
# Wait for all proxies to be restored
|
182
|
+
if restore_tasks:
|
183
|
+
results = await asyncio.gather(*restore_tasks, return_exceptions=True)
|
184
|
+
successful = sum(1 for r in results if r is True)
|
185
|
+
logger.info(f"Restored {successful}/{len(state)} proxy configurations")
|
173
186
|
except Exception as e:
|
174
187
|
logger.error(f"Failed to load proxy state: {e}")
|
175
188
|
|
File without changes
|
File without changes
|