Files
LOGAR/compilation/package_dist.py
T
me0nline cd6b9baa8e
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m4s
Release Binaries & Packages / Build & Release Standalone Binaries (Linux & Windows Cross-Compilation) (push) Successful in 4m52s
Build and Push LOGAR Runner / Build & Push Runner Image (push) Successful in 10m21s
fix(build): protect NSIS and InnoSetup version header definitions with ifndef guards
2026-10-03 23:46:59 +02:00

728 lines
28 KiB
Python

import os
import sys
import shutil
import zipapp
import hashlib
import platform
import subprocess
import argparse
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
SRC_DIR = os.path.join(ROOT_DIR, "src")
DIST_DIR = os.path.join(ROOT_DIR, "dist")
BUILD_TEMP = os.path.join(ROOT_DIR, "build_temp")
def clean_and_prep():
if os.path.exists(DIST_DIR):
for item in os.listdir(DIST_DIR):
if item == "runtime":
continue
item_path = os.path.join(DIST_DIR, item)
try:
if os.path.isdir(item_path):
shutil.rmtree(item_path)
else:
os.remove(item_path)
except Exception:
pass
else:
os.makedirs(DIST_DIR, exist_ok=True)
if os.path.exists(BUILD_TEMP):
try:
shutil.rmtree(BUILD_TEMP)
except Exception:
pass
os.makedirs(BUILD_TEMP, exist_ok=True)
# Generate or verify branding images and icons
branding_script = os.path.join(ROOT_DIR, "compilation", "generate_branding_assets.py")
if os.path.exists(branding_script):
try:
subprocess.run([sys.executable, branding_script], cwd=os.path.join(ROOT_DIR, "compilation"), check=False)
except Exception:
pass
def calculate_sha256(filepath):
h = hashlib.sha256()
with open(filepath, "rb") as f:
while chunk := f.read(65536):
h.update(chunk)
return h.hexdigest()
def write_checksum_file(filename, digest, binary_filename):
out_path = os.path.join(DIST_DIR, filename)
with open(out_path, "w", encoding="utf-8") as f:
f.write(f"{digest} {binary_filename}\n")
print(f"[+] Generated checksum file: {filename} ({digest[:16]}...)")
def build_pyinstaller_binary(script_path, binary_name):
print(f"[*] Compiling {binary_name} with PyInstaller (Bytecode optimized & stripped)...")
cmd = [
sys.executable, "-m", "PyInstaller",
"--onefile",
"--clean",
"--strip",
"--optimize", "2",
"--distpath", DIST_DIR,
"--workpath", os.path.join(BUILD_TEMP, f"work_{binary_name}"),
"--specpath", os.path.join(BUILD_TEMP, f"spec_{binary_name}"),
"--name", binary_name,
script_path
]
res = subprocess.run(cmd, capture_output=True, text=True)
if res.returncode != 0:
print(f"[!] Compilation error for {binary_name}:\n{res.stderr}")
raise RuntimeError(f"Failed to build {binary_name}")
print(f"[+] Successfully compiled {binary_name}")
def build_linux_zipapp_fallback():
print("[*] Packaging Linux standalone zipapp fallback binaries...")
# Linux Client zipapp
app_dir = os.path.join(BUILD_TEMP, "linux_app")
os.makedirs(app_dir, exist_ok=True)
for f in os.listdir(SRC_DIR):
if f.endswith(".py"):
shutil.copy(os.path.join(SRC_DIR, f), os.path.join(app_dir, f))
client_out = os.path.join(DIST_DIR, "Linux_Client.bin")
zipapp.create_archive(
source=app_dir,
target=client_out,
interpreter="/usr/bin/env python3",
main="Linux_Client:main"
)
# Server zipapp
srv_dir = os.path.join(BUILD_TEMP, "linux_srv")
os.makedirs(srv_dir, exist_ok=True)
for f in os.listdir(SRC_DIR):
if f.endswith(".py"):
shutil.copy(os.path.join(SRC_DIR, f), os.path.join(srv_dir, f))
server_out = os.path.join(DIST_DIR, "Server.bin")
zipapp.create_archive(
source=srv_dir,
target=server_out,
interpreter="/usr/bin/env python3",
main="Server:main"
)
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
the standard Python Windows 64-bit PE launcher stub (t64.exe) to a compiled zipapp archive.
"""
app_temp_dir = os.path.join(BUILD_TEMP, f"win_app_{exe_name}")
os.makedirs(app_temp_dir, exist_ok=True)
for f in os.listdir(SRC_DIR):
if f.endswith(".py"):
shutil.copy(os.path.join(SRC_DIR, f), os.path.join(app_temp_dir, f))
module_name = os.path.splitext(script_name)[0]
zipapp_temp = os.path.join(BUILD_TEMP, f"{exe_name}.zip")
zipapp.create_archive(
source=app_temp_dir,
target=zipapp_temp,
interpreter=r"runtime\python.exe",
main=f"{module_name}:main"
)
stub_bytes = b""
try:
import pip._vendor.distlib.resources as distlib_res
finder = distlib_res.finder("pip._vendor.distlib")
res = finder.find("t64.exe")
if res:
stub_bytes = res.bytes
except Exception:
pass
if not stub_bytes:
try:
import distlib.resources as distlib_res
finder = distlib_res.finder("distlib")
res = finder.find("t64.exe")
if res:
stub_bytes = res.bytes
except Exception:
pass
out_exe_path = os.path.join(DIST_DIR, f"{exe_name}.exe")
with open(out_exe_path, "wb") as out_f:
if stub_bytes:
out_f.write(stub_bytes)
with open(zipapp_temp, "rb") as in_f:
out_f.write(in_f.read())
print(f"[+] Successfully cross-compiled Windows PE executable: {exe_name}.exe (PE Stub attached: {bool(stub_bytes)})")
def ensure_makensis():
"""Finds or attempts to install makensis on the current system."""
for cmd in ["makensis", "/usr/bin/makensis", "/usr/local/bin/makensis",
r"C:\Program Files (x86)\NSIS\makensis.exe",
r"C:\Program Files\NSIS\makensis.exe"]:
p = shutil.which(cmd) if not os.path.isabs(cmd) else cmd
if p and (os.path.exists(p) or shutil.which(p)):
return p
if platform.system() == "Linux":
print("[*] Attempting to install 'nsis' package dynamically...")
sudo = ["sudo"] if hasattr(os, "geteuid") and os.geteuid() != 0 and shutil.which("sudo") else []
if shutil.which("apt-get"):
try:
subprocess.run(sudo + ["apt-get", "update", "--allow-releaseinfo-change", "-o", "Acquire::Check-Valid-Until=false", "-y"], check=False)
subprocess.run(sudo + ["apt-get", "-f", "install", "-y"], check=False)
subprocess.run(sudo + ["apt-get", "install", "-y", "--fix-broken", "--fix-missing", "--no-install-recommends", "nsis", "nsis-common"], check=False)
except Exception as e:
print(f"[!] Warning: Dynamic apt install failed: {e}")
elif shutil.which("apk"):
try:
subprocess.run(sudo + ["apk", "add", "--no-cache", "nsis"], check=False)
except Exception:
pass
elif shutil.which("dnf"):
try:
subprocess.run(sudo + ["dnf", "install", "-y", "mingw32-nsis", "nsis"], check=False)
except Exception:
pass
for cmd in ["makensis", "/usr/bin/makensis", "/usr/local/bin/makensis"]:
p = shutil.which(cmd) if not os.path.isabs(cmd) else cmd
if p and (os.path.exists(p) or shutil.which(p)):
return p
return None
def ensure_libpython():
"""Attempts to install libpython shared library on Debian/Ubuntu for PyInstaller."""
if platform.system() == "Linux" and shutil.which("apt-get"):
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
sudo = ["sudo"] if hasattr(os, "geteuid") and os.geteuid() != 0 and shutil.which("sudo") else []
try:
subprocess.run(sudo + ["apt-get", "update", "--allow-releaseinfo-change", "-o", "Acquire::Check-Valid-Until=false", "-y"], check=False)
subprocess.run(sudo + ["apt-get", "-f", "install", "-y"], check=False)
subprocess.run(sudo + ["apt-get", "install", "-y", "--fix-broken", "--fix-missing", "--no-install-recommends", f"libpython{py_ver}", "python3-dev", f"python{py_ver}-dev", "libpython3-dev"], check=False)
except Exception:
pass
import re
def get_context_version():
"""
Extracts the current release version from the build context:
1. Environment variables (APP_VERSION, LOGAR_VERSION, GITEA_REF_NAME, GITHUB_REF_NAME, GITHUB_REF)
2. Git tag via `git describe --tags --exact-match` or `git describe --tags`
3. RELEASE_NOTES.md top header
4. README.md version badge
Returns tuple: (display_version, four_part_version)
e.g. ('0.3.5.1', '0.3.5.1') or ('0.3.5', '0.3.5.0')
"""
raw_version = ""
# 1. Environment variables
for env_var in ["APP_VERSION", "LOGAR_VERSION", "GITEA_REF_NAME", "GITHUB_REF_NAME", "GITHUB_REF"]:
val = os.environ.get(env_var, "").strip()
if val:
if val.startswith("refs/tags/"):
val = val[len("refs/tags/"):]
if val.startswith("v") and len(val) > 1 and val[1].isdigit():
val = val[1:]
if any(c.isdigit() for c in val):
raw_version = val
break
# 2. Git command
if not raw_version:
try:
res = subprocess.run(["git", "describe", "--tags", "--exact-match"], cwd=ROOT_DIR, capture_output=True, text=True, check=False)
if res.returncode == 0 and res.stdout.strip():
tag = res.stdout.strip()
if tag.startswith("v") and len(tag) > 1 and tag[1].isdigit():
tag = tag[1:]
raw_version = tag
except Exception:
pass
if not raw_version:
try:
res = subprocess.run(["git", "describe", "--tags"], cwd=ROOT_DIR, capture_output=True, text=True, check=False)
if res.returncode == 0 and res.stdout.strip():
tag = res.stdout.strip().split("-")[0]
if tag.startswith("v") and len(tag) > 1 and tag[1].isdigit():
tag = tag[1:]
raw_version = tag
except Exception:
pass
# 3. RELEASE_NOTES.md
if not raw_version:
rel_notes = os.path.join(ROOT_DIR, "RELEASE_NOTES.md")
if os.path.exists(rel_notes):
try:
with open(rel_notes, "r", encoding="utf-8") as f:
content = f.read()
m = re.search(r"##\s*\[v?([0-9.]+)\]", content)
if m:
raw_version = m.group(1).strip()
except Exception:
pass
# 4. README.md
if not raw_version:
readme = os.path.join(ROOT_DIR, "README.md")
if os.path.exists(readme):
try:
with open(readme, "r", encoding="utf-8") as f:
content = f.read()
m = re.search(r"version-v?([0-9.]+)-blue", content)
if m:
raw_version = m.group(1).strip()
except Exception:
pass
# Fallback
if not raw_version:
raw_version = "0.3.5.1"
# Clean display version (strip non-standard characters, keep digits and dots)
m_clean = re.search(r"([0-9]+(?:\.[0-9]+)*)", raw_version)
display_version = m_clean.group(1) if m_clean else raw_version
# Format 4-part numeric version for NSIS VIProductVersion (X.X.X.X)
parts = display_version.split(".")
while len(parts) < 4:
parts.append("0")
four_part_version = ".".join(parts[:4])
return display_version, four_part_version
def write_version_headers(display_version, four_part_version):
"""Writes dynamic version header files for NSIS and Inno Setup compilers."""
compilation_dir = os.path.join(ROOT_DIR, "compilation")
os.makedirs(compilation_dir, exist_ok=True)
nsh_file = os.path.join(compilation_dir, "version.nsh")
with open(nsh_file, "w", encoding="utf-8") as f:
f.write(
f"; Auto-generated version header from build context\n"
f"!ifndef APP_VERSION\n"
f' !define APP_VERSION "{four_part_version}"\n'
f"!endif\n"
f"!ifndef DISPLAY_VERSION\n"
f' !define DISPLAY_VERSION "{display_version}"\n'
f"!endif\n"
)
iss_file = os.path.join(compilation_dir, "version.iss")
with open(iss_file, "w", encoding="utf-8") as f:
f.write(
f"; Auto-generated version header from build context\n"
f"#ifndef AppVersion\n"
f' #define AppVersion "{display_version}"\n'
f"#endif\n"
)
print(f"[+] Dynamic context version resolved: {display_version} (NSIS VIProductVersion: {four_part_version})")
def compile_nsis_installers():
"""Compiles NSIS installers if makensis is available (native Linux/Windows)."""
makensis_bin = ensure_makensis()
if not makensis_bin:
print("[!] Note: NSIS compiler (makensis) not found in PATH or standard directories.")
return False
display_ver, vi_ver = get_context_version()
write_version_headers(display_ver, vi_ver)
compilation_dir = os.path.join(ROOT_DIR, "compilation")
makensis_cmd = [
makensis_bin,
f"-DAPP_VERSION={vi_ver}",
f"-DDISPLAY_VERSION={display_ver}"
]
failed_scripts = []
for nsi in ["installer_client.nsi", "installer_server.nsi"]:
nsi_path = os.path.join(compilation_dir, nsi)
if os.path.exists(nsi_path):
print(f"[*] Compiling NSIS script: {nsi} (v{display_ver}) via {makensis_cmd[0]}...")
try:
subprocess.run(makensis_cmd + [nsi_path], cwd=compilation_dir, check=True)
print(f"[+] Successfully compiled NSIS installer for {nsi}")
except Exception as e:
print(f"[!] Error: Failed to compile {nsi} with {makensis_cmd}: {e}")
failed_scripts.append(nsi)
if failed_scripts:
raise RuntimeError(f"NSIS compilation failed for: {', '.join(failed_scripts)}")
return True
def compile_inno_setup_installers():
"""Compiles Inno Setup installers if iscc or innosetup wrapper is available."""
iscc_cmd = None
user_iscc = os.path.expanduser(r"~\AppData\Local\Programs\Inno Setup 6\ISCC.exe")
if shutil.which("iscc"):
iscc_cmd = ["iscc"]
elif shutil.which("innosetup"):
iscc_cmd = ["innosetup"]
elif os.path.exists(user_iscc):
iscc_cmd = [user_iscc]
elif os.path.exists("C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"):
iscc_cmd = ["C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"]
elif os.path.exists("C:\\Program Files\\Inno Setup 6\\ISCC.exe"):
iscc_cmd = ["C:\\Program Files\\Inno Setup 6\\ISCC.exe"]
elif shutil.which("wine") and os.path.exists("/opt/innosetup/app/ISCC.exe"):
iscc_cmd = ["wine", "/opt/innosetup/app/ISCC.exe"]
elif shutil.which("wine"):
wine_iscc = os.path.expanduser("~/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe")
if os.path.exists(wine_iscc):
iscc_cmd = ["wine", wine_iscc]
if not iscc_cmd:
print("[!] Note: Inno Setup compiler (iscc / innosetup) not available on host. Skipping installer .exe creation.")
return False
display_ver, vi_ver = get_context_version()
write_version_headers(display_ver, vi_ver)
failed_scripts = []
for iss in ["installer_client.iss", "installer_server.iss"]:
iss_path = os.path.join(ROOT_DIR, "compilation", iss)
if os.path.exists(iss_path):
print(f"[*] Compiling Inno Setup script: {iss} (v{display_ver})...")
try:
subprocess.run(iscc_cmd + [f"/DAppVersion={display_ver}", iss_path], check=True)
print(f"[+] Successfully compiled {iss}")
except Exception as e:
print(f"[!] Error: Failed to compile {iss} with {iscc_cmd}: {e}")
failed_scripts.append(iss)
if failed_scripts:
raise RuntimeError(f"Inno Setup compilation failed for: {', '.join(failed_scripts)}")
return True
def compile_windows_installers():
if ensure_makensis():
compile_nsis_installers()
else:
compile_inno_setup_installers()
def build_windows():
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
if os.path.exists(client_bin):
client_zip = os.path.join(DIST_DIR, "LOGAR-Windows-Client.zip")
with zipfile.ZipFile(client_zip, "w", zipfile.ZIP_DEFLATED) as zf:
zf.write(client_bin, arcname="Win_Client.exe")
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):
server_zip = os.path.join(DIST_DIR, "LOGAR-Windows-Server.zip")
with zipfile.ZipFile(server_zip, "w", zipfile.ZIP_DEFLATED) as zf:
zf.write(server_bin, arcname="Server.exe")
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 = []
if os.path.exists(client_bin):
client_hash = calculate_sha256(client_bin)
write_checksum_file("win_client_sha256sum", client_hash, "Win_Client.exe")
write_checksum_file("win_agent_sha256sum", client_hash, "Win_Client.exe")
sums.append(f"{client_hash} Win_Client.exe")
else:
raise RuntimeError(f"Expected Windows client binary was not produced: {client_bin}")
if os.path.exists(server_bin):
server_hash = calculate_sha256(server_bin)
write_checksum_file("win_server_sha256sum", server_hash, "Server.exe")
sums.append(f"{server_hash} Server.exe")
else:
raise RuntimeError(f"Expected Windows server binary was not produced: {server_bin}")
# Inno Setup output checksums if present
for inst in ["LOGAR-Client-Setup.exe", "LOGAR-Server-Setup.exe", "LOGAR-Windows-Client.zip", "LOGAR-Windows-Server.zip"]:
inst_path = os.path.join(DIST_DIR, inst)
if os.path.exists(inst_path):
inst_hash = calculate_sha256(inst_path)
sums.append(f"{inst_hash} {inst}")
sums_path = os.path.join(DIST_DIR, "SHA256SUMS_windows.txt")
with open(sums_path, "w", encoding="utf-8") as f:
f.write("\n".join(sums) + "\n")
def build_linux():
print("[*] Compiling Linux standalone binaries and packaging installers...")
is_linux_host = platform.system() == "Linux"
if is_linux_host:
ensure_libpython()
try:
linux_client_script = os.path.join(SRC_DIR, "Linux_Client.py")
build_pyinstaller_binary(linux_client_script, "Linux_Client.bin")
server_script = os.path.join(SRC_DIR, "Server.py")
build_pyinstaller_binary(server_script, "Server.bin")
except Exception as e:
print(f"[!] PyInstaller compilation encountered an issue ({e}). Falling back to standalone zipapp packaging...")
build_linux_zipapp_fallback()
# Normalize extensions in case PyInstaller dropped .bin
for name in ["Linux_Client", "Server"]:
plain_path = os.path.join(DIST_DIR, name)
bin_path = os.path.join(DIST_DIR, f"{name}.bin")
if os.path.exists(plain_path) and not os.path.exists(bin_path):
os.rename(plain_path, bin_path)
# Ensure executable permissions on Linux
for b in ["Linux_Client.bin", "Server.bin"]:
p = os.path.join(DIST_DIR, b)
if os.path.exists(p):
try:
os.chmod(p, 0o755)
except Exception:
pass
else:
print("[!] Note: Host platform is not Linux. Generating executable zipapps for Linux target.")
build_linux_zipapp_fallback()
# Copy Linux installation scripts directly to dist
for sh in ["install_linux_client.sh", "install_linux_server.sh"]:
sh_src = os.path.join(ROOT_DIR, "compilation", sh)
if os.path.exists(sh_src):
sh_dst = os.path.join(DIST_DIR, sh)
shutil.copy(sh_src, sh_dst)
print(f"[+] Copied Linux installer script: {sh}")
# Package Linux tarballs
import tarfile
client_bin = os.path.join(DIST_DIR, "Linux_Client.bin")
server_bin = os.path.join(DIST_DIR, "Server.bin")
if os.path.exists(client_bin):
client_tar = os.path.join(DIST_DIR, "LOGAR-Linux-Client.tar.gz")
with tarfile.open(client_tar, "w:gz") as tf:
tf.add(client_bin, arcname="Linux_Client.bin")
client_sh = os.path.join(DIST_DIR, "install_linux_client.sh")
if os.path.exists(client_sh):
tf.add(client_sh, arcname="install_linux_client.sh")
print(f"[+] Packaged Linux Client Tarball: {os.path.basename(client_tar)}")
if os.path.exists(server_bin):
server_tar = os.path.join(DIST_DIR, "LOGAR-Linux-Server.tar.gz")
with tarfile.open(server_tar, "w:gz") as tf:
tf.add(server_bin, arcname="Server.bin")
server_sh = os.path.join(DIST_DIR, "install_linux_server.sh")
if os.path.exists(server_sh):
tf.add(server_sh, arcname="install_linux_server.sh")
print(f"[+] Packaged Linux Server Tarball: {os.path.basename(server_tar)}")
sums = []
if os.path.exists(client_bin):
client_hash = calculate_sha256(client_bin)
write_checksum_file("linux_client_sha256sum", client_hash, "Linux_Client.bin")
write_checksum_file("linux_agent_sha256sum", client_hash, "Linux_Client.bin")
sums.append(f"{client_hash} Linux_Client.bin")
else:
raise RuntimeError(f"Expected Linux client binary was not produced: {client_bin}")
if os.path.exists(server_bin):
server_hash = calculate_sha256(server_bin)
write_checksum_file("linux_server_sha256sum", server_hash, "Server.bin")
sums.append(f"{server_hash} Server.bin")
else:
raise RuntimeError(f"Expected Linux server binary was not produced: {server_bin}")
for item in ["install_linux_client.sh", "install_linux_server.sh", "LOGAR-Linux-Client.tar.gz", "LOGAR-Linux-Server.tar.gz"]:
item_path = os.path.join(DIST_DIR, item)
if os.path.exists(item_path):
item_hash = calculate_sha256(item_path)
sums.append(f"{item_hash} {item}")
sums_path = os.path.join(DIST_DIR, "SHA256SUMS_linux.txt")
with open(sums_path, "w", encoding="utf-8") as f:
f.write("\n".join(sums) + "\n")
def build_all():
print("[*] Building standalone distribution artifacts for both Linux and Windows...")
build_linux()
build_windows()
# Consolidated master checksums file
all_sums = []
for f in sorted(os.listdir(DIST_DIR)):
fpath = os.path.join(DIST_DIR, f)
if os.path.isfile(fpath) and not f.endswith(".txt") and not "sha256sum" in f:
h = calculate_sha256(fpath)
all_sums.append(f"{h} {f}")
with open(os.path.join(DIST_DIR, "SHA256SUMS.txt"), "w", encoding="utf-8") as f:
f.write("\n".join(all_sums) + "\n")
def main(target=None):
if target is None:
parser = argparse.ArgumentParser(description="LOGAR Standalone Binary Compiler & Packager")
parser.add_argument(
"--target", "-t",
choices=["all", "windows", "win", "linux", "auto"],
default="auto",
help="Target platform to compile binaries for (default: auto-detect)"
)
args, _ = parser.parse_known_args()
target = args.target
if target == "auto":
target = "all"
elif target == "win":
target = "windows"
print("=" * 60)
print(" LOGAR Binary Packaging & Cross-Compilation")
print(f" Host Platform: {platform.system()} ({platform.machine()})")
print(f" Target Platform: {target.upper()}")
print("=" * 60)
clean_and_prep()
if target == "all":
build_all()
elif target == "windows":
build_windows()
elif target == "linux":
build_linux()
else:
raise ValueError(f"Unsupported target: {target}")
# Clean temporary build directory
if os.path.exists(BUILD_TEMP):
shutil.rmtree(BUILD_TEMP, ignore_errors=True)
print("\n[+] Binary shipping artifacts assembled in 'dist/':")
for f in sorted(os.listdir(DIST_DIR)):
sz = os.path.getsize(os.path.join(DIST_DIR, f))
print(f" - {f} ({sz / (1024*1024):.2f} MB)" if sz > 1024*1024 else f" - {f} ({sz} bytes)")
print("=" * 60)
if __name__ == "__main__":
main()