165 lines
4.0 KiB
Python
165 lines
4.0 KiB
Python
"""Build short-lived Windows Agent enrollment packages in memory."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import json
|
|
import os
|
|
import zipfile
|
|
from pathlib import Path
|
|
from urllib.parse import urlsplit
|
|
|
|
|
|
WINDOWS_PACKAGE_ROOT = "MinerOpsAgent-Windows"
|
|
|
|
WINDOWS_PACKAGE_FILES = (
|
|
"Enroll-MinerOpsAgent.ps1",
|
|
"install.ps1",
|
|
"uninstall.ps1",
|
|
"MinerOpsAgentService.ps1",
|
|
"MinerOpsAgentTray.ps1",
|
|
"MinerOpsAgentGUI.ps1",
|
|
"MinerOpsApi3EnrichmentPoller.ps1",
|
|
"MinerOpsPoolPoller.ps1",
|
|
"MinerOpsPoolPollerTask.ps1",
|
|
"EnrichOneMiner.ps1",
|
|
"MinerOpsControl.ps1",
|
|
"VERSION",
|
|
"README.md",
|
|
)
|
|
|
|
DEFAULT_WINDOWS_AGENT_SOURCE = Path(
|
|
os.environ.get(
|
|
"MINEROPS_WINDOWS_AGENT_SOURCE",
|
|
"/app/agents/windows",
|
|
)
|
|
)
|
|
|
|
|
|
class AgentWindowsPackageError(RuntimeError):
|
|
"""Base Windows package construction error."""
|
|
|
|
|
|
class AgentWindowsPackageSourceError(AgentWindowsPackageError):
|
|
"""Canonical Windows Agent source is unavailable or incomplete."""
|
|
|
|
|
|
def normalize_hub_url(value: str) -> str:
|
|
candidate = value.strip()
|
|
parsed = urlsplit(candidate)
|
|
|
|
if parsed.scheme not in {"http", "https"}:
|
|
raise AgentWindowsPackageError(
|
|
"MinerOps Hub URL must use http or https"
|
|
)
|
|
|
|
if not parsed.netloc:
|
|
raise AgentWindowsPackageError(
|
|
"MinerOps Hub URL must include a host"
|
|
)
|
|
|
|
if parsed.username or parsed.password:
|
|
raise AgentWindowsPackageError(
|
|
"MinerOps Hub URL must not contain credentials"
|
|
)
|
|
|
|
if parsed.query or parsed.fragment:
|
|
raise AgentWindowsPackageError(
|
|
"MinerOps Hub URL must not contain query or fragment data"
|
|
)
|
|
|
|
if parsed.path not in {"", "/"}:
|
|
raise AgentWindowsPackageError(
|
|
"MinerOps Hub URL must be an origin without a path"
|
|
)
|
|
|
|
return f"{parsed.scheme}://{parsed.netloc}"
|
|
|
|
|
|
def build_windows_agent_package(
|
|
*,
|
|
enrollment_token: str,
|
|
hub_url: str,
|
|
source_root: Path | None = None,
|
|
) -> bytes:
|
|
token = enrollment_token.strip()
|
|
|
|
if not 32 <= len(token) <= 256:
|
|
raise AgentWindowsPackageError(
|
|
"Enrollment token has an invalid length"
|
|
)
|
|
|
|
normalized_hub_url = normalize_hub_url(hub_url)
|
|
|
|
root = (
|
|
Path(source_root)
|
|
if source_root is not None
|
|
else DEFAULT_WINDOWS_AGENT_SOURCE
|
|
)
|
|
|
|
if not root.is_dir():
|
|
raise AgentWindowsPackageSourceError(
|
|
"Canonical Windows Agent source is unavailable"
|
|
)
|
|
|
|
missing = [
|
|
name
|
|
for name in WINDOWS_PACKAGE_FILES
|
|
if not (root / name).is_file()
|
|
]
|
|
|
|
if missing:
|
|
raise AgentWindowsPackageSourceError(
|
|
"Canonical Windows Agent source is incomplete: "
|
|
+ ", ".join(missing)
|
|
)
|
|
|
|
enrollment = {
|
|
"hub_url": normalized_hub_url,
|
|
"token": token,
|
|
}
|
|
|
|
instructions = """MinerOps Windows Agent
|
|
|
|
1. Extract this ZIP on the target Windows machine.
|
|
2. Open Windows PowerShell as Administrator.
|
|
3. Change to the extracted MinerOpsAgent-Windows folder.
|
|
4. Run:
|
|
|
|
.\\Enroll-MinerOpsAgent.ps1
|
|
|
|
The enrollment credential is short-lived and single-use.
|
|
Do not post enrollment.json in chat, tickets, screenshots, or logs.
|
|
Delete the ZIP and extracted setup folder after successful installation.
|
|
"""
|
|
|
|
buffer = io.BytesIO()
|
|
|
|
with zipfile.ZipFile(
|
|
buffer,
|
|
mode="w",
|
|
compression=zipfile.ZIP_DEFLATED,
|
|
) as archive:
|
|
for name in WINDOWS_PACKAGE_FILES:
|
|
archive.write(
|
|
root / name,
|
|
arcname=f"{WINDOWS_PACKAGE_ROOT}/{name}",
|
|
)
|
|
|
|
archive.writestr(
|
|
f"{WINDOWS_PACKAGE_ROOT}/enrollment.json",
|
|
json.dumps(
|
|
enrollment,
|
|
indent=2,
|
|
sort_keys=True,
|
|
)
|
|
+ "\n",
|
|
)
|
|
|
|
archive.writestr(
|
|
f"{WINDOWS_PACKAGE_ROOT}/INSTALL.txt",
|
|
instructions,
|
|
)
|
|
|
|
return buffer.getvalue()
|