|
|
|
@@ -86,6 +86,87 @@ def build_linux_zipapp_fallback():
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def prepare_windows_runtime():
|
|
|
|
|
"""
|
|
|
|
|
Downloads the official Python 3.11 embeddable 64-bit runtime, configures ._pth,
|
|
|
|
|
and downloads/unpacks all required dependencies (wheels) for standalone execution.
|
|
|
|
|
"""
|
|
|
|
|
runtime_dir = os.path.join(DIST_DIR, "runtime")
|
|
|
|
|
if os.path.exists(runtime_dir):
|
|
|
|
|
return runtime_dir
|
|
|
|
|
|
|
|
|
|
os.makedirs(runtime_dir, exist_ok=True)
|
|
|
|
|
site_packages = os.path.join(runtime_dir, "site-packages")
|
|
|
|
|
os.makedirs(site_packages, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# 1. Download & Extract Python 3.11 embeddable zip
|
|
|
|
|
embed_zip_name = "python-3.11.9-embed-amd64.zip"
|
|
|
|
|
embed_zip_path = os.path.join(BUILD_TEMP, embed_zip_name)
|
|
|
|
|
embed_url = f"https://www.python.org/ftp/python/3.11.9/{embed_zip_name}"
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(embed_zip_path):
|
|
|
|
|
print(f"[*] Downloading Python 3.11 embeddable runtime from {embed_url}...")
|
|
|
|
|
import urllib.request
|
|
|
|
|
urllib.request.urlretrieve(embed_url, embed_zip_path)
|
|
|
|
|
|
|
|
|
|
import zipfile
|
|
|
|
|
with zipfile.ZipFile(embed_zip_path, "r") as zf:
|
|
|
|
|
zf.extractall(runtime_dir)
|
|
|
|
|
|
|
|
|
|
# 2. Configure python311._pth
|
|
|
|
|
pth_file = os.path.join(runtime_dir, "python311._pth")
|
|
|
|
|
with open(pth_file, "w", encoding="utf-8") as f:
|
|
|
|
|
f.write("python311.zip\n.\nsite-packages\nsite-packages/win32\nsite-packages/win32/lib\nsite-packages/Pythonwin\nimport site\n")
|
|
|
|
|
|
|
|
|
|
# 3. Download and unpack Windows AMD64 dependencies
|
|
|
|
|
wheels_dir = os.path.join(BUILD_TEMP, "win_wheels")
|
|
|
|
|
os.makedirs(wheels_dir, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
print("[*] Fetching required Windows x64 dependency wheels...")
|
|
|
|
|
# Build PGPy 0.6.0 wheel from git (supports modern cryptography)
|
|
|
|
|
try:
|
|
|
|
|
subprocess.run([
|
|
|
|
|
sys.executable, "-m", "pip", "wheel", "--no-deps",
|
|
|
|
|
"git+https://github.com/SecurityInnovation/PGPy.git",
|
|
|
|
|
"--wheel-dir", wheels_dir
|
|
|
|
|
], check=False, capture_output=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Download binary wheels for win_amd64 cp311
|
|
|
|
|
deps = [
|
|
|
|
|
"cryptography", "fastapi", "uvicorn", "pydantic",
|
|
|
|
|
"pywin32", "websockets", "pyasn1", "six", "cffi", "pycparser"
|
|
|
|
|
]
|
|
|
|
|
pip_cmd = [
|
|
|
|
|
sys.executable, "-m", "pip", "download",
|
|
|
|
|
"--only-binary=:all:",
|
|
|
|
|
"--platform", "win_amd64",
|
|
|
|
|
"--python-version", "311",
|
|
|
|
|
"--implementation", "cp",
|
|
|
|
|
"--abi", "cp311",
|
|
|
|
|
"--dest", wheels_dir
|
|
|
|
|
] + deps
|
|
|
|
|
|
|
|
|
|
subprocess.run(pip_cmd, check=False, capture_output=True)
|
|
|
|
|
|
|
|
|
|
for whl in os.listdir(wheels_dir):
|
|
|
|
|
if whl.endswith(".whl"):
|
|
|
|
|
whl_path = os.path.join(wheels_dir, whl)
|
|
|
|
|
with zipfile.ZipFile(whl_path, "r") as zf:
|
|
|
|
|
zf.extractall(site_packages)
|
|
|
|
|
|
|
|
|
|
# 4. Copy pywin32 system DLLs to runtime root for immediate DLL loader resolution
|
|
|
|
|
sys32_dir = os.path.join(site_packages, "pywin32_system32")
|
|
|
|
|
if os.path.exists(sys32_dir):
|
|
|
|
|
for dll in os.listdir(sys32_dir):
|
|
|
|
|
if dll.lower().endswith(".dll"):
|
|
|
|
|
shutil.copy(os.path.join(sys32_dir, dll), runtime_dir)
|
|
|
|
|
|
|
|
|
|
print("[+] Successfully assembled standalone Windows Python 3.11 runtime environment.")
|
|
|
|
|
return runtime_dir
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_windows_exe_zipapp(script_name, exe_name):
|
|
|
|
|
"""
|
|
|
|
|
Cross-compilation helper: Creates a standalone Windows .exe by prepending
|
|
|
|
@@ -103,7 +184,7 @@ def create_windows_exe_zipapp(script_name, exe_name):
|
|
|
|
|
zipapp.create_archive(
|
|
|
|
|
source=app_temp_dir,
|
|
|
|
|
target=zipapp_temp,
|
|
|
|
|
interpreter="python.exe",
|
|
|
|
|
interpreter=r"runtime\python.exe",
|
|
|
|
|
main=f"{module_name}:main"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -253,28 +334,16 @@ def compile_windows_installers():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_windows():
|
|
|
|
|
print("[*] Compiling Windows standalone executables...")
|
|
|
|
|
is_windows_host = platform.system() == "Windows"
|
|
|
|
|
|
|
|
|
|
if is_windows_host:
|
|
|
|
|
win_client_script = os.path.join(SRC_DIR, "Win_Client.py")
|
|
|
|
|
server_script = os.path.join(SRC_DIR, "Server.py")
|
|
|
|
|
try:
|
|
|
|
|
build_pyinstaller_binary(win_client_script, "Win_Client")
|
|
|
|
|
build_pyinstaller_binary(server_script, "Server")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[!] PyInstaller on Windows failed ({e}). Falling back to PE launcher stub packaging...")
|
|
|
|
|
create_windows_exe_zipapp("Win_Client.py", "Win_Client")
|
|
|
|
|
create_windows_exe_zipapp("Server.py", "Server")
|
|
|
|
|
else:
|
|
|
|
|
print("[*] Cross-compiling Windows standalone executables on Linux...")
|
|
|
|
|
create_windows_exe_zipapp("Win_Client.py", "Win_Client")
|
|
|
|
|
create_windows_exe_zipapp("Server.py", "Server")
|
|
|
|
|
print("[*] Packaging self-contained Windows standalone executables...")
|
|
|
|
|
prepare_windows_runtime()
|
|
|
|
|
create_windows_exe_zipapp("Win_Client.py", "Win_Client")
|
|
|
|
|
create_windows_exe_zipapp("Server.py", "Server")
|
|
|
|
|
|
|
|
|
|
compile_windows_installers()
|
|
|
|
|
|
|
|
|
|
client_bin = os.path.join(DIST_DIR, "Win_Client.exe")
|
|
|
|
|
server_bin = os.path.join(DIST_DIR, "Server.exe")
|
|
|
|
|
runtime_dir = os.path.join(DIST_DIR, "runtime")
|
|
|
|
|
|
|
|
|
|
# Windows Zip Bundles
|
|
|
|
|
import zipfile
|
|
|
|
@@ -285,6 +354,12 @@ def build_windows():
|
|
|
|
|
sample_cfg = os.path.join(ROOT_DIR, "out", "win_client", "client_config.sample.json")
|
|
|
|
|
if os.path.exists(sample_cfg):
|
|
|
|
|
zf.write(sample_cfg, arcname="client_config.sample.json")
|
|
|
|
|
if os.path.exists(runtime_dir):
|
|
|
|
|
for root, _, files in os.walk(runtime_dir):
|
|
|
|
|
for file in files:
|
|
|
|
|
full_p = os.path.join(root, file)
|
|
|
|
|
rel_p = os.path.relpath(full_p, DIST_DIR)
|
|
|
|
|
zf.write(full_p, arcname=rel_p)
|
|
|
|
|
print(f"[+] Packaged Windows Client Zip: {os.path.basename(client_zip)}")
|
|
|
|
|
|
|
|
|
|
if os.path.exists(server_bin):
|
|
|
|
@@ -294,6 +369,12 @@ def build_windows():
|
|
|
|
|
sample_cfg = os.path.join(ROOT_DIR, "server_config.sample.json")
|
|
|
|
|
if os.path.exists(sample_cfg):
|
|
|
|
|
zf.write(sample_cfg, arcname="server_config.sample.json")
|
|
|
|
|
if os.path.exists(runtime_dir):
|
|
|
|
|
for root, _, files in os.walk(runtime_dir):
|
|
|
|
|
for file in files:
|
|
|
|
|
full_p = os.path.join(root, file)
|
|
|
|
|
rel_p = os.path.relpath(full_p, DIST_DIR)
|
|
|
|
|
zf.write(full_p, arcname=rel_p)
|
|
|
|
|
print(f"[+] Packaged Windows Server Zip: {os.path.basename(server_zip)}")
|
|
|
|
|
|
|
|
|
|
sums = []
|
|
|
|
|