import os import sys import shutil import zipapp import hashlib import platform import subprocess import argparse ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) SRC_DIR = os.path.join(ROOT_DIR, "src") DIST_DIR = os.path.abspath("dist") BUILD_TEMP = os.path.abspath("build_temp") def clean_and_prep(): if os.path.exists(DIST_DIR): shutil.rmtree(DIST_DIR) os.makedirs(DIST_DIR, exist_ok=True) if os.path.exists(BUILD_TEMP): shutil.rmtree(BUILD_TEMP) os.makedirs(BUILD_TEMP, exist_ok=True) def build_pyinstaller_binary(script_path, binary_name): print(f"[*] Compiling {binary_name} with PyInstaller...") cmd = [ sys.executable, "-m", "PyInstaller", "--onefile", "--clean", "--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 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_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) shutil.copy(os.path.join(SRC_DIR, "Linux_Client.py"), os.path.join(app_dir, "Linux_Client.py")) 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) shutil.copy(os.path.join(SRC_DIR, "Server.py"), os.path.join(srv_dir, "Server.py")) 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 build_windows(): print("[*] Compiling Windows standalone executables...") win_client_script = os.path.join(SRC_DIR, "Win_Client.py") build_pyinstaller_binary(win_client_script, "Win_Client") server_script = os.path.join(SRC_DIR, "Server.py") build_pyinstaller_binary(server_script, "Server") client_bin = os.path.join(DIST_DIR, "Win_Client.exe") server_bin = os.path.join(DIST_DIR, "Server.exe") 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: print(f"[!] Warning: Expected {client_bin} was not found.") 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: print(f"[!] Warning: Expected {server_bin} was not found.") 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...") is_linux_host = platform.system() == "Linux" if is_linux_host: 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") # 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() client_bin = os.path.join(DIST_DIR, "Linux_Client.bin") server_bin = os.path.join(DIST_DIR, "Server.bin") 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: print(f"[!] Warning: Expected {client_bin} was not found.") 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: print(f"[!] Warning: Expected {server_bin} was not found.") 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 main(target=None): if target is None: parser = argparse.ArgumentParser(description="LOGAR Standalone Binary Compiler & Packager") parser.add_argument( "--target", "-t", choices=["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 = "windows" if platform.system() == "Windows" else "linux" elif target == "win": target = "windows" print("=" * 60) print(" LOGAR Binary Packaging") print(f" Host Platform: {platform.system()} ({platform.machine()})") print(f" Target Platform: {target.upper()}") print("=" * 60) clean_and_prep() if 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()