diff --git a/autarch.py b/autarch.py index 453f3eb..7a3f6b9 100644 --- a/autarch.py +++ b/autarch.py @@ -210,6 +210,11 @@ def create_parser(): metavar='PORT', help='Web dashboard port (default: 8181)' ) + parser.add_argument( + '--no-tray', + action='store_true', + help='Disable system tray icon (run web server in foreground only)' + ) # Web service management parser.add_argument( @@ -680,6 +685,20 @@ def main(): proto = 'http' print(f"{Colors.GREEN}[+] Starting AUTARCH Web Dashboard on {proto}://{host}:{port}{Colors.RESET}") + + # System tray mode (default on desktop environments) + if not args.no_tray: + try: + from core.tray import TrayManager, TRAY_AVAILABLE + if TRAY_AVAILABLE: + print(f"{Colors.DIM} System tray icon active — right-click to control{Colors.RESET}") + tray = TrayManager(app, host, port, ssl_context=ssl_ctx) + tray.run() # Blocks until Exit + sys.exit(0) + except Exception: + pass # Fall through to normal mode + + # Fallback: run Flask directly (headless / --no-tray) app.run(host=host, port=port, debug=False, ssl_context=ssl_ctx) sys.exit(0) diff --git a/autarch_public.spec b/autarch_public.spec index 90a24c5..9c916c3 100644 --- a/autarch_public.spec +++ b/autarch_public.spec @@ -1,7 +1,10 @@ # -*- mode: python ; coding: utf-8 -*- # PyInstaller spec for AUTARCH Public Release -# Build: pyinstaller autarch_public.spec -# Output: dist/autarch_public.exe (single-file executable) +# +# Build: pyinstaller autarch_public.spec +# Output: dist/autarch/ +# ├── autarch.exe (CLI — full framework, console window) +# └── autarch_web.exe (Web — double-click to launch dashboard + tray icon, no console) import sys from pathlib import Path @@ -11,25 +14,31 @@ SRC = Path(SPECPATH) block_cipher = None # ── Data files (non-Python assets to bundle) ───────────────────────────────── -added_files = [ +# Only include files that actually exist to prevent build failures +_candidate_files = [ # Web assets - (str(SRC / 'web' / 'templates'), 'web/templates'), - (str(SRC / 'web' / 'static'), 'web/static'), + (SRC / 'web' / 'templates', 'web/templates'), + (SRC / 'web' / 'static', 'web/static'), # Data (SQLite DBs, site lists, config defaults) - (str(SRC / 'data'), 'data'), + (SRC / 'data', 'data'), - # Modules directory (dynamically loaded) - (str(SRC / 'modules'), 'modules'), + # Modules directory (dynamically loaded at runtime) + (SRC / 'modules', 'modules'), + + # LLM model + (SRC / 'models' / 'Hal_v2.gguf', 'models'), # Root-level config and docs - (str(SRC / 'autarch_settings.conf'), '.'), - (str(SRC / 'user_manual.md'), '.'), - (str(SRC / 'windows_manual.md'), '.'), - (str(SRC / 'custom_sites.inf'), '.'), - (str(SRC / 'custom_adultsites.json'), '.'), + (SRC / 'autarch_settings.conf', '.'), + (SRC / 'user_manual.md', '.'), + (SRC / 'windows_manual.md', '.'), + (SRC / 'custom_sites.inf', '.'), + (SRC / 'custom_adultsites.json', '.'), ] +added_files = [(str(src), dst) for src, dst in _candidate_files if src.exists()] + # ── Hidden imports ──────────────────────────────────────────────────────────── hidden_imports = [ # Flask ecosystem @@ -39,10 +48,13 @@ hidden_imports = [ # Core libraries 'bcrypt', 'requests', 'msgpack', 'pyserial', 'qrcode', 'PIL', - 'PIL.Image', 'PIL.ImageDraw', 'cryptography', + 'PIL.Image', 'PIL.ImageDraw', 'PIL.ImageFont', 'cryptography', + + # System tray + 'pystray', 'pystray._win32', # AUTARCH core modules - 'core.config', 'core.paths', 'core.banner', 'core.menu', + 'core.config', 'core.paths', 'core.banner', 'core.menu', 'core.tray', 'core.llm', 'core.agent', 'core.tools', 'core.msf', 'core.msf_interface', 'core.hardware', 'core.android_protect', @@ -74,6 +86,7 @@ hidden_imports = [ 'web.routes.chat', 'web.routes.targets', 'web.routes.encmodules', + 'web.routes.llm_trainer', # Standard library (sometimes missed on Windows) 'email.mime.text', 'email.mime.multipart', @@ -82,9 +95,30 @@ hidden_imports = [ 'threading', 'queue', 'uuid', 'hashlib', 'zlib', 'configparser', 'platform', 'socket', 'shutil', 'importlib', 'importlib.util', 'importlib.metadata', + 'webbrowser', 'ssl', ] -a = Analysis( +excludes = [ + # Exclude heavy optional deps not needed at runtime + 'torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic', + 'tkinter', 'matplotlib', 'numpy', + # CUDA / quantization libraries + 'bitsandbytes', + # HuggingFace ecosystem + 'huggingface_hub', 'safetensors', 'tokenizers', + # MCP/uvicorn/starlette + 'mcp', 'uvicorn', 'starlette', 'anyio', 'httpx', 'httpx_sse', + 'httpcore', 'h11', 'h2', 'hpack', 'hyperframe', + # Pydantic + 'pydantic', 'pydantic_core', 'pydantic_settings', + # Other heavy packages + 'scipy', 'pandas', 'tensorflow', 'keras', + 'IPython', 'notebook', 'jupyterlab', + 'fsspec', 'rich', 'typer', +] + +# ── Analysis for CLI entry point ───────────────────────────────────────────── +a_cli = Analysis( ['autarch.py'], pathex=[str(SRC)], binaries=[], @@ -93,38 +127,40 @@ a = Analysis( hookspath=[], hooksconfig={}, runtime_hooks=[], - excludes=[ - # Exclude heavy optional deps not needed at runtime - 'torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic', - 'tkinter', 'matplotlib', 'numpy', - # CUDA / quantization libraries (bitsandbytes ships 169MB of CUDA DLLs) - 'bitsandbytes', - # HuggingFace ecosystem (pulled in by llama_cpp_python) - 'huggingface_hub', 'safetensors', 'tokenizers', - # MCP/uvicorn/starlette (heavy server stack not needed in frozen exe) - 'mcp', 'uvicorn', 'starlette', 'anyio', 'httpx', 'httpx_sse', - 'httpcore', 'h11', 'h2', 'hpack', 'hyperframe', - # Pydantic (pulled in by MCP/huggingface) - 'pydantic', 'pydantic_core', 'pydantic_settings', - # Other heavy / unnecessary packages - 'scipy', 'pandas', 'tensorflow', 'keras', - 'IPython', 'notebook', 'jupyterlab', - 'fsspec', 'rich', 'typer', - ], + excludes=excludes, noarchive=False, optimize=0, ) -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +# ── Analysis for Web entry point ───────────────────────────────────────────── +a_web = Analysis( + ['autarch_web.py'], + pathex=[str(SRC)], + binaries=[], + datas=added_files, + hiddenimports=hidden_imports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=excludes, + noarchive=False, + optimize=0, +) -# ── Single-file executable ─────────────────────────────────────────────────── -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, +# ── Merge analyses (shared libraries only stored once) ─────────────────────── +MERGE( + (a_cli, 'autarch', 'autarch'), + (a_web, 'autarch_web', 'autarch_web'), +) + +# ── CLI executable (console window) ───────────────────────────────────────── +pyz_cli = PYZ(a_cli.pure, a_cli.zipped_data, cipher=block_cipher) +exe_cli = EXE( + pyz_cli, + a_cli.scripts, [], - name='autarch_public', + exclude_binaries=True, + name='autarch', debug=False, bootloader_ignore_signals=False, strip=False, @@ -137,3 +173,38 @@ exe = EXE( entitlements_file=None, icon=None, ) + +# ── Web executable (NO console window — tray icon only) ───────────────────── +pyz_web = PYZ(a_web.pure, a_web.zipped_data, cipher=block_cipher) +exe_web = EXE( + pyz_web, + a_web.scripts, + [], + exclude_binaries=True, + name='autarch_web', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, # <-- No console window + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=None, +) + +# ── Collect everything into one directory ──────────────────────────────────── +coll = COLLECT( + exe_cli, + a_cli.binaries, + a_cli.datas, + exe_web, + a_web.binaries, + a_web.datas, + strip=False, + upx=True, + upx_exclude=[], + name='autarch', +) diff --git a/autarch_web.py b/autarch_web.py new file mode 100644 index 0000000..bcd9463 --- /dev/null +++ b/autarch_web.py @@ -0,0 +1,66 @@ +"""AUTARCH Web Launcher — double-click to start the web dashboard with system tray. + +This is the entry point for autarch_web.exe (no console window). +It starts the Flask web server and shows a system tray icon for control. +""" + +import sys +import os +from pathlib import Path + +# Ensure framework is importable +if getattr(sys, 'frozen', False): + FRAMEWORK_DIR = Path(sys._MEIPASS) +else: + FRAMEWORK_DIR = Path(__file__).parent +sys.path.insert(0, str(FRAMEWORK_DIR)) + + +def main(): + from web.app import create_app + from core.config import get_config + from core.paths import get_data_dir + + config = get_config() + app = create_app() + host = config.get('web', 'host', fallback='0.0.0.0') + port = config.get_int('web', 'port', fallback=8181) + + # Auto-generate self-signed TLS cert + ssl_ctx = None + use_https = config.get('web', 'https', fallback='true').lower() != 'false' + if use_https: + import subprocess + cert_dir = os.path.join(get_data_dir(), 'certs') + os.makedirs(cert_dir, exist_ok=True) + cert_path = os.path.join(cert_dir, 'autarch.crt') + key_path = os.path.join(cert_dir, 'autarch.key') + if not os.path.exists(cert_path) or not os.path.exists(key_path): + try: + subprocess.run([ + 'openssl', 'req', '-x509', '-newkey', 'rsa:2048', + '-keyout', key_path, '-out', cert_path, + '-days', '3650', '-nodes', + '-subj', '/CN=AUTARCH/O=darkHal', + ], check=True, capture_output=True) + except Exception: + use_https = False + if use_https: + ssl_ctx = (cert_path, key_path) + + # Try system tray mode (preferred — no console window needed) + try: + from core.tray import TrayManager, TRAY_AVAILABLE + if TRAY_AVAILABLE: + tray = TrayManager(app, host, port, ssl_context=ssl_ctx) + tray.run() + return + except Exception: + pass + + # Fallback: run Flask directly + app.run(host=host, port=port, debug=False, ssl_context=ssl_ctx) + + +if __name__ == "__main__": + main() diff --git a/core/menu.py b/core/menu.py index 9ed03f7..3b140aa 100644 --- a/core/menu.py +++ b/core/menu.py @@ -100,29 +100,36 @@ class MainMenu: print(f"{color}[{symbol}] {message}{Colors.RESET}") def load_modules(self): - """Load all available modules from the modules directory.""" - modules_path = self._app_dir / self.config.get('autarch', 'modules_path', 'modules') + """Load all available modules from the modules directory. - if not modules_path.exists(): - self.print_status(f"Modules directory not found: {modules_path}", "warning") - return + In a frozen (PyInstaller) build, scans both the bundled modules inside + _MEIPASS and the user modules directory next to the exe. User modules + override bundled modules with the same name. + """ + from core.paths import get_modules_dir, get_user_modules_dir, is_frozen - for module_file in modules_path.glob("*.py"): - if module_file.name.startswith("_"): - continue + # Collect module files — bundled first, then user (user overrides) + module_files: dict[str, Path] = {} - module_name = module_file.stem + bundled = get_modules_dir() + if bundled.exists(): + for f in bundled.glob("*.py"): + if not f.name.startswith("_") and f.stem != "setup": + module_files[f.stem] = f - # Skip the setup module from regular listing - if module_name == "setup": - continue + if is_frozen(): + user_dir = get_user_modules_dir() + if user_dir.exists(): + for f in user_dir.glob("*.py"): + if not f.name.startswith("_") and f.stem != "setup": + module_files[f.stem] = f # Override bundled + for module_name, module_file in module_files.items(): try: spec = importlib.util.spec_from_file_location(module_name, module_file) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - # Check if module has required 'run' function if hasattr(module, 'run'): self.modules[module_name] = ModuleInfo(module_name, module_file, module) else: diff --git a/core/paths.py b/core/paths.py index 9ea7183..c3f5b2a 100644 --- a/core/paths.py +++ b/core/paths.py @@ -14,22 +14,60 @@ from typing import Optional, List # ── Application Root ──────────────────────────────────────────────── +# +# Two directories matter: +# _BUNDLE_DIR — read-only bundled assets (templates, static, default modules) +# Points to sys._MEIPASS in a frozen PyInstaller build, +# otherwise same as _APP_DIR. +# _APP_DIR — writable application root (config, data, results, user modules) +# Points to the .exe's parent directory in a frozen build, +# otherwise the project root (parent of core/). -# Computed once: the autarch project root (parent of core/) -_APP_DIR = Path(__file__).resolve().parent.parent +import sys as _sys + +_FROZEN = getattr(_sys, 'frozen', False) + +if _FROZEN: + # PyInstaller frozen build + _BUNDLE_DIR = Path(_sys._MEIPASS) + _APP_DIR = Path(_sys.executable).resolve().parent +else: + # Normal Python execution + _APP_DIR = Path(__file__).resolve().parent.parent + _BUNDLE_DIR = _APP_DIR + + +def is_frozen() -> bool: + """Return True if running from a PyInstaller bundle.""" + return _FROZEN def get_app_dir() -> Path: - """Return the AUTARCH application root directory.""" + """Return the writable application root directory.""" return _APP_DIR +def get_bundle_dir() -> Path: + """Return the bundle directory (read-only assets: templates, static, default modules).""" + return _BUNDLE_DIR + + def get_core_dir() -> Path: - return _APP_DIR / 'core' + return _BUNDLE_DIR / 'core' def get_modules_dir() -> Path: - return _APP_DIR / 'modules' + """Return the bundled modules directory (read-only in frozen mode).""" + return _BUNDLE_DIR / 'modules' + + +def get_user_modules_dir() -> Path: + """Return the user modules directory (writable, next to exe). + New or modified modules go here; scanned in addition to bundled modules.""" + d = _APP_DIR / 'modules' + if _FROZEN: + d.mkdir(parents=True, exist_ok=True) + return d def get_data_dir() -> Path: @@ -39,7 +77,15 @@ def get_data_dir() -> Path: def get_config_path() -> Path: - return _APP_DIR / 'autarch_settings.conf' + """Return config path. Writable copy lives next to the exe; + falls back to the bundled default if the writable copy doesn't exist yet.""" + writable = _APP_DIR / 'autarch_settings.conf' + if not writable.exists() and _FROZEN: + bundled = _BUNDLE_DIR / 'autarch_settings.conf' + if bundled.exists(): + import shutil as _sh + _sh.copy2(str(bundled), str(writable)) + return writable def get_results_dir() -> Path: diff --git a/core/tray.py b/core/tray.py new file mode 100644 index 0000000..ee8ed4d --- /dev/null +++ b/core/tray.py @@ -0,0 +1,130 @@ +"""AUTARCH System Tray Icon + +Provides a taskbar/system tray icon with Start, Stop, Restart, Open Dashboard, +and Exit controls for the web dashboard. + +Requires: pystray, Pillow +""" + +import sys +import threading +import webbrowser + +try: + import pystray + from PIL import Image, ImageDraw, ImageFont + TRAY_AVAILABLE = True +except ImportError: + TRAY_AVAILABLE = False + + +def create_icon_image(size=64): + """Create AUTARCH tray icon programmatically — dark circle with cyan 'A'.""" + img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + # Dark background circle with cyan border + draw.ellipse([1, 1, size - 2, size - 2], fill=(15, 15, 25, 255), + outline=(0, 180, 255, 255), width=2) + + # Letter "A" centered + try: + font = ImageFont.truetype("arial.ttf", int(size * 0.5)) + except OSError: + font = ImageFont.load_default() + + bbox = draw.textbbox((0, 0), "A", font=font) + tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1] + x = (size - tw) // 2 + y = (size - th) // 2 - bbox[1] + draw.text((x, y), "A", fill=(0, 200, 255, 255), font=font) + + return img + + +class TrayManager: + """Manages the system tray icon and Flask server lifecycle.""" + + def __init__(self, app, host, port, ssl_context=None): + self.app = app + self.host = host + self.port = port + self.ssl_context = ssl_context + self._server = None + self._thread = None + self.running = False + self._icon = None + self._proto = 'https' if ssl_context else 'http' + + def start_server(self): + """Start the Flask web server in a background thread.""" + if self.running: + return + + from werkzeug.serving import make_server + self._server = make_server(self.host, self.port, self.app, threaded=True) + + if self.ssl_context: + import ssl + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(self.ssl_context[0], self.ssl_context[1]) + self._server.socket = ctx.wrap_socket(self._server.socket, server_side=True) + + self.running = True + self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) + self._thread.start() + + def stop_server(self): + """Stop the Flask web server.""" + if not self.running or not self._server: + return + self._server.shutdown() + self._server = None + self._thread = None + self.running = False + + def restart_server(self): + """Stop and restart the Flask web server.""" + self.stop_server() + self.start_server() + + def open_browser(self): + """Open the dashboard in the default web browser.""" + if self.running: + host = 'localhost' if self.host in ('0.0.0.0', '::') else self.host + webbrowser.open(f"{self._proto}://{host}:{self.port}") + + def quit(self): + """Stop server and exit the tray icon.""" + self.stop_server() + if self._icon: + self._icon.stop() + + def run(self): + """Start server and show tray icon. Blocks until Exit is clicked.""" + if not TRAY_AVAILABLE: + raise RuntimeError("pystray or Pillow not installed") + + self.start_server() + + image = create_icon_image() + menu = pystray.Menu( + pystray.MenuItem( + lambda item: f"AUTARCH — {'Running' if self.running else 'Stopped'}", + None, enabled=False), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Start", lambda: self.start_server(), + enabled=lambda item: not self.running), + pystray.MenuItem("Stop", lambda: self.stop_server(), + enabled=lambda item: self.running), + pystray.MenuItem("Restart", lambda: self.restart_server(), + enabled=lambda item: self.running), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Open Dashboard", lambda: self.open_browser(), + enabled=lambda item: self.running, default=True), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Exit", lambda: self.quit()), + ) + + self._icon = pystray.Icon("autarch", image, "AUTARCH", menu=menu) + self._icon.run() # Blocks until quit() diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..0b36666 --- /dev/null +++ b/installer.iss @@ -0,0 +1,85 @@ +; ============================================================================ +; AUTARCH Inno Setup Installer Script +; ============================================================================ +; +; Prerequisites: +; 1. Build PyInstaller first: pyinstaller autarch_public.spec +; 2. Install Inno Setup: https://jrsoftware.org/isdl.php +; 3. Compile this script: Open in Inno Setup Compiler -> Build -> Compile +; Or from CLI: iscc installer.iss +; +; Output: Output\AUTARCH_Setup.exe +; ============================================================================ + +[Setup] +AppName=AUTARCH +AppVersion=1.3 +AppVerName=AUTARCH 1.3 +AppPublisher=darkHal Security Group +AppPublisherURL=https://github.com/darkhal +AppSupportURL=https://github.com/darkhal +DefaultDirName={localappdata}\AUTARCH +DefaultGroupName=AUTARCH +OutputBaseFilename=AUTARCH_Setup +Compression=lzma2 +SolidCompression=no +LZMANumBlockThreads=4 +PrivilegesRequired=lowest +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +UninstallDisplayName=AUTARCH +DisableProgramGroupPage=yes +WizardStyle=modern +SetupLogging=yes + +; Uncomment and set path if you have a custom icon: +; SetupIconFile=assets\autarch.ico + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "Create a Desktop shortcut"; GroupDescription: "Additional shortcuts:" +Name: "startupicon"; Description: "Launch Web Dashboard on Windows startup"; GroupDescription: "Additional shortcuts:"; Flags: unchecked + +[Files] +; GGUF model — store uncompressed (3.9GB, barely compressible, avoids OOM) +Source: "dist\autarch\_internal\models\Hal_v2.gguf"; DestDir: "{app}\_internal\models"; Flags: ignoreversion nocompression +; Everything else from PyInstaller output (compressed with lzma2) +Source: "dist\autarch\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "_internal\models\Hal_v2.gguf" + +[Icons] +; Start Menu +Name: "{group}\AUTARCH Web Dashboard"; Filename: "{app}\autarch_web.exe"; Comment: "Launch AUTARCH Web Dashboard with system tray" +Name: "{group}\AUTARCH CLI"; Filename: "{app}\autarch.exe"; Comment: "AUTARCH command-line interface" +Name: "{group}\Uninstall AUTARCH"; Filename: "{uninstallexe}" + +; Desktop (optional) +Name: "{commondesktop}\AUTARCH Web"; Filename: "{app}\autarch_web.exe"; Tasks: desktopicon; Comment: "Launch AUTARCH Web Dashboard" + +; Windows Startup (optional) +Name: "{userstartup}\AUTARCH Web"; Filename: "{app}\autarch_web.exe"; Tasks: startupicon + +[Run] +; Option to launch after install +Filename: "{app}\autarch_web.exe"; Description: "Launch AUTARCH Web Dashboard"; Flags: nowait postinstall skipifsilent + +[UninstallRun] +; Kill running instances before uninstall +Filename: "taskkill"; Parameters: "/F /IM autarch.exe"; Flags: runhidden +Filename: "taskkill"; Parameters: "/F /IM autarch_web.exe"; Flags: runhidden + +[UninstallDelete] +; Clean up runtime-generated files +Type: filesandordirs; Name: "{app}\data" +Type: filesandordirs; Name: "{app}\results" +Type: filesandordirs; Name: "{app}\dossiers" +Type: filesandordirs; Name: "{app}\backups" +Type: files; Name: "{app}\autarch_settings.conf" + +[Code] +// Show installed size in wizard +function InitializeSetup(): Boolean; +begin + Result := True; +end; diff --git a/installer.nsi b/installer.nsi new file mode 100644 index 0000000..106298e --- /dev/null +++ b/installer.nsi @@ -0,0 +1,144 @@ +; ============================================================================ +; AUTARCH NSIS Installer Script +; ============================================================================ +; +; Prerequisites: +; 1. Build PyInstaller first: pyinstaller autarch_public.spec +; 2. Install NSIS: https://nsis.sourceforge.io/Download +; 3. Compile this script: makensis installer.nsi +; Or right-click installer.nsi -> "Compile NSIS Script" +; +; Output: AUTARCH_Setup.exe +; ============================================================================ + +!include "MUI2.nsh" +!include "FileFunc.nsh" +!include "LogicLib.nsh" + +; ── App metadata ───────────────────────────────────────────────────────────── +!define APPNAME "AUTARCH" +!define APPVERSION "1.3" +!define PUBLISHER "darkHal Security Group" +!define DESCRIPTION "Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking" + +; Source directory — PyInstaller onedir output +!define SRCDIR "dist\autarch" + +; ── Installer settings ─────────────────────────────────────────────────────── +Name "${APPNAME} ${APPVERSION}" +OutFile "AUTARCH_Setup.exe" +InstallDir "$LOCALAPPDATA\${APPNAME}" +InstallDirRegKey HKCU "Software\${APPNAME}" "InstallDir" +RequestExecutionLevel user +SetCompressor /SOLID lzma +SetCompressorDictSize 64 +Unicode True + +; ── Variables ──────────────────────────────────────────────────────────────── +Var StartMenuGroup + +; ── MUI configuration ──────────────────────────────────────────────────────── +!define MUI_ABORTWARNING +!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" +!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" + +; Welcome page +!define MUI_WELCOMEPAGE_TITLE "Welcome to ${APPNAME} Setup" +!define MUI_WELCOMEPAGE_TEXT "This wizard will install ${APPNAME} ${APPVERSION} on your computer.$\r$\n$\r$\n${DESCRIPTION}$\r$\n$\r$\nClick Next to continue." + +; Finish page — option to launch +!define MUI_FINISHPAGE_RUN "$INSTDIR\autarch_web.exe" +!define MUI_FINISHPAGE_RUN_TEXT "Launch ${APPNAME} Web Dashboard" +!define MUI_FINISHPAGE_SHOWREADME "" +!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED +!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut + +; ── Pages ──────────────────────────────────────────────────────────────────── +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +; Uninstaller pages +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +; Language +!insertmacro MUI_LANGUAGE "English" + +; ── Install section ────────────────────────────────────────────────────────── +Section "Install" SecInstall + SetOutPath "$INSTDIR" + + ; Copy everything from the PyInstaller dist/autarch/ directory + File /r "${SRCDIR}\*.*" + + ; Write uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + + ; Registry — install location + Add/Remove Programs entry + WriteRegStr HKCU "Software\${APPNAME}" "InstallDir" "$INSTDIR" + + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "DisplayName" "${APPNAME}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "DisplayVersion" "${APPVERSION}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "Publisher" "${PUBLISHER}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "UninstallString" '"$INSTDIR\Uninstall.exe"' + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "InstallLocation" "$INSTDIR" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "DisplayIcon" "$INSTDIR\autarch.exe" + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "NoModify" 1 + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "NoRepair" 1 + + ; Estimate installed size for Add/Remove Programs + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "EstimatedSize" "$0" + + ; ── Start Menu shortcuts ───────────────────────────────────────────────── + CreateDirectory "$SMPROGRAMS\${APPNAME}" + + CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME} Web Dashboard.lnk" \ + "$INSTDIR\autarch_web.exe" "" "$INSTDIR\autarch_web.exe" 0 + CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME} CLI.lnk" \ + "$INSTDIR\autarch.exe" "" "$INSTDIR\autarch.exe" 0 + CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall ${APPNAME}.lnk" \ + "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0 + +SectionEnd + +; ── Desktop shortcut function (called from finish page checkbox) ───────────── +Function CreateDesktopShortcut + CreateShortcut "$DESKTOP\${APPNAME} Web.lnk" \ + "$INSTDIR\autarch_web.exe" "" "$INSTDIR\autarch_web.exe" 0 +FunctionEnd + +; ── Uninstall section ──────────────────────────────────────────────────────── +Section "Uninstall" + + ; Kill running instances + nsExec::ExecToLog 'taskkill /F /IM autarch.exe' + nsExec::ExecToLog 'taskkill /F /IM autarch_web.exe' + + ; Remove Start Menu + RMDir /r "$SMPROGRAMS\${APPNAME}" + + ; Remove Desktop shortcut + Delete "$DESKTOP\${APPNAME} Web.lnk" + + ; Remove install directory (everything) + RMDir /r "$INSTDIR" + + ; Remove registry entries + DeleteRegKey HKCU "Software\${APPNAME}" + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" + +SectionEnd diff --git a/setup_msi.py b/setup_msi.py index 28cf1ce..c32b0a4 100644 --- a/setup_msi.py +++ b/setup_msi.py @@ -16,6 +16,7 @@ include_files = [ (str(SRC / 'web' / 'static'), 'web/static'), (str(SRC / 'data'), 'data'), (str(SRC / 'modules'), 'modules'), + (str(SRC / 'models' / 'Hal_v2.gguf'), 'models/Hal_v2.gguf'), (str(SRC / 'autarch_settings.conf'), 'autarch_settings.conf'), (str(SRC / 'user_manual.md'), 'user_manual.md'), (str(SRC / 'windows_manual.md'), 'windows_manual.md'), @@ -27,10 +28,11 @@ include_files = [(s, d) for s, d in include_files if Path(s).exists()] build_exe_options = { 'packages': [ 'flask', 'jinja2', 'werkzeug', 'markupsafe', - 'bcrypt', 'requests', 'core', 'web', 'web.routes', 'modules', + 'bcrypt', 'requests', 'pystray', 'PIL', + 'core', 'web', 'web.routes', 'modules', ], 'includes': [ - 'core.config', 'core.paths', 'core.banner', 'core.menu', + 'core.config', 'core.paths', 'core.banner', 'core.menu', 'core.tray', 'core.llm', 'core.agent', 'core.tools', 'core.msf', 'core.msf_interface', 'core.hardware', 'core.android_protect', @@ -49,6 +51,7 @@ build_exe_options = { 'web.routes.revshell', 'web.routes.archon', 'web.routes.msf', 'web.routes.chat', 'web.routes.targets', 'web.routes.encmodules', + 'web.routes.llm_trainer', ], 'excludes': ['torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic', 'tkinter', 'matplotlib', 'numpy', @@ -66,7 +69,7 @@ build_exe_options = { bdist_msi_options = { 'upgrade_code': '{A07B3D2E-5F1C-4D8A-9E6B-0C2F7A8D4E1B}', 'add_to_path': False, - 'initial_target_dir': r'[ProgramFilesFolder]\AUTARCH', + 'initial_target_dir': r'[LocalAppDataFolder]\AUTARCH', } setup( @@ -81,8 +84,13 @@ setup( executables=[ Executable( 'autarch.py', - target_name='autarch_public', - base=None, # console application + target_name='autarch', + base=None, # console application (CLI) + ), + Executable( + 'autarch_web.py', + target_name='autarch_web', + base='Win32GUI', # no console window (tray icon only) ), ], ) diff --git a/web/app.py b/web/app.py index 8fbcb34..37f5813 100644 --- a/web/app.py +++ b/web/app.py @@ -9,15 +9,20 @@ from pathlib import Path from flask import Flask # Ensure framework is importable -FRAMEWORK_DIR = Path(__file__).parent.parent +if getattr(sys, 'frozen', False): + FRAMEWORK_DIR = Path(sys._MEIPASS) +else: + FRAMEWORK_DIR = Path(__file__).parent.parent sys.path.insert(0, str(FRAMEWORK_DIR)) def create_app(): + # In frozen builds, templates/static are inside _MEIPASS, not next to __file__ + bundle_web = FRAMEWORK_DIR / 'web' app = Flask( __name__, - template_folder=str(Path(__file__).parent / 'templates'), - static_folder=str(Path(__file__).parent / 'static') + template_folder=str(bundle_web / 'templates'), + static_folder=str(bundle_web / 'static') ) # Config