golem-vm-provider 0.1.12__py3-none-any.whl → 0.1.14__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.12.dist-info → golem_vm_provider-0.1.14.dist-info}/METADATA +1 -1
- {golem_vm_provider-0.1.12.dist-info → golem_vm_provider-0.1.14.dist-info}/RECORD +6 -6
- provider/config.py +161 -99
- provider/main.py +4 -32
- {golem_vm_provider-0.1.12.dist-info → golem_vm_provider-0.1.14.dist-info}/WHEEL +0 -0
- {golem_vm_provider-0.1.12.dist-info → golem_vm_provider-0.1.14.dist-info}/entry_points.txt +0 -0
@@ -2,11 +2,11 @@ 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=tq23K-FHjqQM0cu9GIjKi6_a96YHCEyqpprMwzK36oI,10344
|
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=nVuMxq6npioif4-bFXGjQWJCKdy6O5ZbtX3zxhCM3zI,9206
|
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
|
@@ -20,7 +20,7 @@ provider/vm/multipass.py,sha256=RLUqCeoYz4PG8RL7dBu_TzjNEAmgIz9NonBtSuYc4kw,1643
|
|
20
20
|
provider/vm/name_mapper.py,sha256=MrshNeJ4Dw-WBsyiIVcn9N5xyOxaBKX4Yqhyh_m5IFg,4103
|
21
21
|
provider/vm/port_manager.py,sha256=d03uwU76vx6LgADMN8ffBT9t400XQ3vtYlXr6cLIFN0,9831
|
22
22
|
provider/vm/proxy_manager.py,sha256=cu0FPPbeCc3CR6NRE_CnLjiRg7xVdSFUylVUOL1g1sI,10154
|
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
|
+
golem_vm_provider-0.1.14.dist-info/METADATA,sha256=a5GRzycDL9296fLDQnI1XHKA-gDOq10WVgNifEve5Ts,10594
|
24
|
+
golem_vm_provider-0.1.14.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
25
|
+
golem_vm_provider-0.1.14.dist-info/entry_points.txt,sha256=E4rCWo_Do_2zCG_GewNuftfVlHF_8b_OvioZre0dfeA,54
|
26
|
+
golem_vm_provider-0.1.14.dist-info/RECORD,,
|
provider/config.py
CHANGED
@@ -3,7 +3,10 @@ from pathlib import Path
|
|
3
3
|
from typing import Optional
|
4
4
|
import uuid
|
5
5
|
|
6
|
-
from pydantic import BaseSettings, validator
|
6
|
+
from pydantic import BaseSettings, validator, Field
|
7
|
+
from .utils.logging import setup_logger
|
8
|
+
|
9
|
+
logger = setup_logger(__name__)
|
7
10
|
|
8
11
|
|
9
12
|
class Settings(BaseSettings):
|
@@ -56,22 +59,41 @@ class Settings(BaseSettings):
|
|
56
59
|
|
57
60
|
@validator("VM_DATA_DIR", pre=True)
|
58
61
|
def resolve_vm_data_dir(cls, v: str) -> str:
|
59
|
-
"""Resolve VM data directory path."""
|
62
|
+
"""Resolve and create VM data directory path."""
|
60
63
|
if not v:
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
path = Path.home() / ".golem" / "provider" / "vms"
|
65
|
+
else:
|
66
|
+
path = Path(v)
|
67
|
+
if not path.is_absolute():
|
68
|
+
path = Path.home() / path
|
69
|
+
|
70
|
+
try:
|
71
|
+
path.mkdir(parents=True, exist_ok=True)
|
72
|
+
logger.debug(f"Created VM data directory at {path}")
|
73
|
+
except Exception as e:
|
74
|
+
logger.error(f"Failed to create VM data directory at {path}: {e}")
|
75
|
+
raise ValueError(f"Failed to create VM data directory: {e}")
|
76
|
+
|
65
77
|
return str(path)
|
66
78
|
|
67
79
|
@validator("SSH_KEY_DIR", pre=True)
|
68
80
|
def resolve_ssh_key_dir(cls, v: str) -> str:
|
69
|
-
"""Resolve SSH key directory path."""
|
81
|
+
"""Resolve and create SSH key directory path with secure permissions."""
|
70
82
|
if not v:
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
83
|
+
path = Path.home() / ".golem" / "provider" / "ssh"
|
84
|
+
else:
|
85
|
+
path = Path(v)
|
86
|
+
if not path.is_absolute():
|
87
|
+
path = Path.home() / path
|
88
|
+
|
89
|
+
try:
|
90
|
+
path.mkdir(parents=True, exist_ok=True)
|
91
|
+
path.chmod(0o700) # Secure permissions for SSH keys
|
92
|
+
logger.debug(f"Created SSH key directory at {path} with secure permissions")
|
93
|
+
except Exception as e:
|
94
|
+
logger.error(f"Failed to create SSH key directory at {path}: {e}")
|
95
|
+
raise ValueError(f"Failed to create SSH key directory: {e}")
|
96
|
+
|
75
97
|
return str(path)
|
76
98
|
|
77
99
|
# Resource Settings
|
@@ -88,98 +110,129 @@ class Settings(BaseSettings):
|
|
88
110
|
RATE_LIMIT_PER_MINUTE: int = 100
|
89
111
|
|
90
112
|
# Multipass Settings
|
91
|
-
MULTIPASS_BINARY_PATH: str =
|
113
|
+
MULTIPASS_BINARY_PATH: str = Field(
|
114
|
+
default="",
|
115
|
+
description="Path to multipass binary"
|
116
|
+
)
|
92
117
|
|
93
|
-
@validator("MULTIPASS_BINARY_PATH"
|
118
|
+
@validator("MULTIPASS_BINARY_PATH")
|
94
119
|
def detect_multipass_path(cls, v: str) -> str:
|
95
120
|
"""Detect and validate Multipass binary path."""
|
121
|
+
import platform
|
122
|
+
import subprocess
|
123
|
+
|
124
|
+
def validate_path(path: str) -> bool:
|
125
|
+
"""Validate that a path exists and is executable."""
|
126
|
+
return os.path.isfile(path) and os.access(path, os.X_OK)
|
127
|
+
|
128
|
+
# If path provided via environment variable, ONLY validate that path
|
96
129
|
if v:
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
130
|
+
logger.info(f"Checking multipass binary at: {v}")
|
131
|
+
if not validate_path(v):
|
132
|
+
msg = f"Invalid multipass binary path: {v} (not found or not executable)"
|
133
|
+
logger.error(msg)
|
134
|
+
raise ValueError(msg)
|
135
|
+
logger.info(f"✓ Found valid multipass binary at: {v}")
|
136
|
+
return v
|
137
|
+
|
138
|
+
logger.info("No multipass path provided, attempting auto-detection...")
|
139
|
+
system = platform.system().lower()
|
140
|
+
logger.info(f"Detected OS: {system}")
|
141
|
+
binary_name = "multipass.exe" if system == "windows" else "multipass"
|
142
|
+
|
143
|
+
# Try to find multipass based on OS
|
144
|
+
if system == "linux":
|
145
|
+
logger.info("Checking for snap installation...")
|
146
|
+
# First try to find snap and check if multipass is installed
|
147
|
+
try:
|
148
|
+
# Check if snap exists
|
149
|
+
snap_result = subprocess.run(
|
150
|
+
["which", "snap"],
|
151
|
+
capture_output=True,
|
152
|
+
text=True,
|
153
|
+
check=True
|
154
|
+
)
|
155
|
+
if snap_result.returncode == 0:
|
156
|
+
logger.info("✓ Found snap, checking for multipass installation...")
|
157
|
+
# Check if multipass is installed via snap
|
158
|
+
try:
|
159
|
+
snap_list = subprocess.run(
|
160
|
+
["snap", "list", "multipass"],
|
161
|
+
capture_output=True,
|
162
|
+
text=True,
|
163
|
+
check=True
|
164
|
+
)
|
165
|
+
if snap_list.returncode == 0:
|
166
|
+
snap_path = "/snap/bin/multipass"
|
167
|
+
if validate_path(snap_path):
|
168
|
+
logger.info(f"✓ Found multipass via snap at {snap_path}")
|
169
|
+
return snap_path
|
170
|
+
except subprocess.CalledProcessError:
|
171
|
+
logger.info("✗ Multipass not installed via snap")
|
172
|
+
pass
|
173
|
+
except subprocess.CalledProcessError:
|
174
|
+
logger.info("✗ Snap not found")
|
175
|
+
pass
|
121
176
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
177
|
+
# Common Linux paths if snap installation not found
|
178
|
+
search_paths = [
|
179
|
+
"/usr/local/bin",
|
180
|
+
"/usr/bin",
|
181
|
+
"/snap/bin"
|
182
|
+
]
|
183
|
+
logger.info(f"Checking common Linux paths: {', '.join(search_paths)}")
|
128
184
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
185
|
+
elif system == "darwin": # macOS
|
186
|
+
search_paths = [
|
187
|
+
"/opt/homebrew/bin", # M1 Mac
|
188
|
+
"/usr/local/bin", # Intel Mac
|
189
|
+
"/opt/local/bin" # MacPorts
|
190
|
+
]
|
191
|
+
logger.info(f"Checking macOS paths: {', '.join(search_paths)}")
|
135
192
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
193
|
+
elif system == "windows":
|
194
|
+
search_paths = [
|
195
|
+
os.path.expandvars(r"%ProgramFiles%\Multipass"),
|
196
|
+
os.path.expandvars(r"%ProgramFiles(x86)%\Multipass"),
|
197
|
+
os.path.expandvars(r"%LocalAppData%\Multipass")
|
198
|
+
]
|
199
|
+
logger.info(f"Checking Windows paths: {', '.join(search_paths)}")
|
142
200
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# Search for multipass binary in OS-specific paths
|
147
|
-
for directory in search_paths:
|
148
|
-
path = os.path.join(directory, binary_name)
|
149
|
-
if os.path.isfile(path) and os.access(path, os.X_OK):
|
150
|
-
return path
|
151
|
-
|
152
|
-
# OS-specific installation instructions
|
153
|
-
if system == "linux":
|
154
|
-
raise ValueError(
|
155
|
-
"Multipass binary not found. Please install using:\n"
|
156
|
-
"sudo snap install multipass\n"
|
157
|
-
"Or set GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
158
|
-
)
|
159
|
-
elif system == "darwin":
|
160
|
-
raise ValueError(
|
161
|
-
"Multipass binary not found. Please install using:\n"
|
162
|
-
"brew install multipass\n"
|
163
|
-
"Or set GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
164
|
-
)
|
165
|
-
elif system == "windows":
|
166
|
-
raise ValueError(
|
167
|
-
"Multipass binary not found. Please install from:\n"
|
168
|
-
"Microsoft Store or https://multipass.run/download/windows\n"
|
169
|
-
"Or set GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
170
|
-
)
|
171
|
-
else:
|
172
|
-
raise ValueError(
|
173
|
-
"Multipass binary not found. Please install Multipass or set "
|
174
|
-
"GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
175
|
-
)
|
201
|
+
else:
|
202
|
+
search_paths = ["/usr/local/bin", "/usr/bin"]
|
203
|
+
logger.info(f"Checking default paths: {', '.join(search_paths)}")
|
176
204
|
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
205
|
+
# Search for multipass binary in OS-specific paths
|
206
|
+
for directory in search_paths:
|
207
|
+
path = os.path.join(directory, binary_name)
|
208
|
+
if validate_path(path):
|
209
|
+
logger.info(f"✓ Found valid multipass binary at: {path}")
|
210
|
+
return path
|
211
|
+
|
212
|
+
# OS-specific installation instructions
|
213
|
+
if system == "linux":
|
214
|
+
raise ValueError(
|
215
|
+
"Multipass binary not found. Please install using:\n"
|
216
|
+
"sudo snap install multipass\n"
|
217
|
+
"Or set GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
218
|
+
)
|
219
|
+
elif system == "darwin":
|
220
|
+
raise ValueError(
|
221
|
+
"Multipass binary not found. Please install using:\n"
|
222
|
+
"brew install multipass\n"
|
223
|
+
"Or set GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
224
|
+
)
|
225
|
+
elif system == "windows":
|
226
|
+
raise ValueError(
|
227
|
+
"Multipass binary not found. Please install from:\n"
|
228
|
+
"Microsoft Store or https://multipass.run/download/windows\n"
|
229
|
+
"Or set GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
230
|
+
)
|
231
|
+
else:
|
232
|
+
raise ValueError(
|
233
|
+
"Multipass binary not found. Please install Multipass or set "
|
234
|
+
"GOLEM_PROVIDER_MULTIPASS_BINARY_PATH to your Multipass binary path."
|
235
|
+
)
|
183
236
|
|
184
237
|
# Proxy Settings
|
185
238
|
PORT_RANGE_START: int = 50800
|
@@ -189,12 +242,21 @@ class Settings(BaseSettings):
|
|
189
242
|
|
190
243
|
@validator("PROXY_STATE_DIR", pre=True)
|
191
244
|
def resolve_proxy_state_dir(cls, v: str) -> str:
|
192
|
-
"""Resolve proxy state directory path."""
|
245
|
+
"""Resolve and create proxy state directory path."""
|
193
246
|
if not v:
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
247
|
+
path = Path.home() / ".golem" / "provider" / "proxy"
|
248
|
+
else:
|
249
|
+
path = Path(v)
|
250
|
+
if not path.is_absolute():
|
251
|
+
path = Path.home() / path
|
252
|
+
|
253
|
+
try:
|
254
|
+
path.mkdir(parents=True, exist_ok=True)
|
255
|
+
logger.debug(f"Created proxy state directory at {path}")
|
256
|
+
except Exception as e:
|
257
|
+
logger.error(f"Failed to create proxy state directory at {path}: {e}")
|
258
|
+
raise ValueError(f"Failed to create proxy state directory: {e}")
|
259
|
+
|
198
260
|
return str(path)
|
199
261
|
|
200
262
|
@validator("PUBLIC_IP", pre=True)
|
provider/main.py
CHANGED
@@ -163,41 +163,13 @@ __all__ = ["app", "start"]
|
|
163
163
|
|
164
164
|
def check_requirements():
|
165
165
|
"""Check if all requirements are met."""
|
166
|
-
import os
|
167
|
-
from pathlib import Path
|
168
|
-
|
169
|
-
# Check if multipass is installed
|
170
|
-
multipass_path = os.environ.get('GOLEM_PROVIDER_MULTIPASS_BINARY_PATH', '/usr/local/bin/multipass')
|
171
|
-
if not Path(multipass_path).exists():
|
172
|
-
logger.error(f"Multipass binary not found at {multipass_path}")
|
173
|
-
return False
|
174
|
-
|
175
|
-
# Check required directories
|
176
|
-
vm_data_dir = os.environ.get(
|
177
|
-
'GOLEM_PROVIDER_VM_DATA_DIR',
|
178
|
-
str(Path.home() / '.golem' / 'provider' / 'vms')
|
179
|
-
)
|
180
|
-
ssh_key_dir = os.environ.get(
|
181
|
-
'GOLEM_PROVIDER_SSH_KEY_DIR',
|
182
|
-
str(Path.home() / '.golem' / 'provider' / 'ssh')
|
183
|
-
)
|
184
|
-
proxy_state_dir = os.environ.get(
|
185
|
-
'GOLEM_PROVIDER_PROXY_STATE_DIR',
|
186
|
-
str(Path.home() / '.golem' / 'provider' / 'proxy')
|
187
|
-
)
|
188
|
-
|
189
166
|
try:
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
path.mkdir(parents=True, exist_ok=True)
|
194
|
-
if directory == ssh_key_dir:
|
195
|
-
path.chmod(0o700) # Secure permissions for SSH keys
|
167
|
+
# Import settings to trigger validation
|
168
|
+
from .config import settings
|
169
|
+
return True
|
196
170
|
except Exception as e:
|
197
|
-
logger.error(f"
|
171
|
+
logger.error(f"Requirements check failed: {e}")
|
198
172
|
return False
|
199
|
-
|
200
|
-
return True
|
201
173
|
|
202
174
|
async def verify_provider_port(port: int) -> bool:
|
203
175
|
"""Verify that the provider port is available for binding.
|
File without changes
|
File without changes
|