Add system tray, dual-exe build, and installer scripts

- core/tray.py: System tray icon with Start/Stop/Restart/Open Dashboard/Exit
- autarch_web.py: Windowless web launcher entry point (Win32GUI, no console)
- core/paths.py: Frozen build support — dual-directory pattern for PyInstaller
- core/menu.py: Module loading scans both bundled and user module directories
- web/app.py: Template/static paths resolve correctly in frozen builds
- autarch.py: --no-tray flag, tray integration for --web mode
- autarch_public.spec: Dual-exe PyInstaller spec with MERGE/COLLECT
- setup_msi.py: Dual executables, LocalAppData install, model inclusion
- installer.iss: Inno Setup script (model stored uncompressed to avoid OOM)
- installer.nsi: NSIS installer script with MUI2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DigiJ 2026-03-02 22:02:22 -08:00
parent 150f58f57a
commit 4a721a73b4
10 changed files with 650 additions and 69 deletions

View File

@ -210,6 +210,11 @@ def create_parser():
metavar='PORT', metavar='PORT',
help='Web dashboard port (default: 8181)' 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 # Web service management
parser.add_argument( parser.add_argument(
@ -680,6 +685,20 @@ def main():
proto = 'http' proto = 'http'
print(f"{Colors.GREEN}[+] Starting AUTARCH Web Dashboard on {proto}://{host}:{port}{Colors.RESET}") 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) app.run(host=host, port=port, debug=False, ssl_context=ssl_ctx)
sys.exit(0) sys.exit(0)

View File

@ -1,7 +1,10 @@
# -*- mode: python ; coding: utf-8 -*- # -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec for AUTARCH Public Release # PyInstaller spec for AUTARCH Public Release
#
# Build: pyinstaller autarch_public.spec # Build: pyinstaller autarch_public.spec
# Output: dist/autarch_public.exe (single-file executable) # 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 import sys
from pathlib import Path from pathlib import Path
@ -11,25 +14,31 @@ SRC = Path(SPECPATH)
block_cipher = None block_cipher = None
# ── Data files (non-Python assets to bundle) ───────────────────────────────── # ── Data files (non-Python assets to bundle) ─────────────────────────────────
added_files = [ # Only include files that actually exist to prevent build failures
_candidate_files = [
# Web assets # Web assets
(str(SRC / 'web' / 'templates'), 'web/templates'), (SRC / 'web' / 'templates', 'web/templates'),
(str(SRC / 'web' / 'static'), 'web/static'), (SRC / 'web' / 'static', 'web/static'),
# Data (SQLite DBs, site lists, config defaults) # Data (SQLite DBs, site lists, config defaults)
(str(SRC / 'data'), 'data'), (SRC / 'data', 'data'),
# Modules directory (dynamically loaded) # Modules directory (dynamically loaded at runtime)
(str(SRC / 'modules'), 'modules'), (SRC / 'modules', 'modules'),
# LLM model
(SRC / 'models' / 'Hal_v2.gguf', 'models'),
# Root-level config and docs # Root-level config and docs
(str(SRC / 'autarch_settings.conf'), '.'), (SRC / 'autarch_settings.conf', '.'),
(str(SRC / 'user_manual.md'), '.'), (SRC / 'user_manual.md', '.'),
(str(SRC / 'windows_manual.md'), '.'), (SRC / 'windows_manual.md', '.'),
(str(SRC / 'custom_sites.inf'), '.'), (SRC / 'custom_sites.inf', '.'),
(str(SRC / 'custom_adultsites.json'), '.'), (SRC / 'custom_adultsites.json', '.'),
] ]
added_files = [(str(src), dst) for src, dst in _candidate_files if src.exists()]
# ── Hidden imports ──────────────────────────────────────────────────────────── # ── Hidden imports ────────────────────────────────────────────────────────────
hidden_imports = [ hidden_imports = [
# Flask ecosystem # Flask ecosystem
@ -39,10 +48,13 @@ hidden_imports = [
# Core libraries # Core libraries
'bcrypt', 'requests', 'msgpack', 'pyserial', 'qrcode', 'PIL', '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 # 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.llm', 'core.agent', 'core.tools',
'core.msf', 'core.msf_interface', 'core.msf', 'core.msf_interface',
'core.hardware', 'core.android_protect', 'core.hardware', 'core.android_protect',
@ -74,6 +86,7 @@ hidden_imports = [
'web.routes.chat', 'web.routes.chat',
'web.routes.targets', 'web.routes.targets',
'web.routes.encmodules', 'web.routes.encmodules',
'web.routes.llm_trainer',
# Standard library (sometimes missed on Windows) # Standard library (sometimes missed on Windows)
'email.mime.text', 'email.mime.multipart', 'email.mime.text', 'email.mime.multipart',
@ -82,9 +95,30 @@ hidden_imports = [
'threading', 'queue', 'uuid', 'hashlib', 'zlib', 'threading', 'queue', 'uuid', 'hashlib', 'zlib',
'configparser', 'platform', 'socket', 'shutil', 'configparser', 'platform', 'socket', 'shutil',
'importlib', 'importlib.util', 'importlib.metadata', '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'], ['autarch.py'],
pathex=[str(SRC)], pathex=[str(SRC)],
binaries=[], binaries=[],
@ -93,38 +127,40 @@ a = Analysis(
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
excludes=[ excludes=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',
],
noarchive=False, noarchive=False,
optimize=0, 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 ─────────────────────────────────────────────────── # ── Merge analyses (shared libraries only stored once) ───────────────────────
exe = EXE( MERGE(
pyz, (a_cli, 'autarch', 'autarch'),
a.scripts, (a_web, 'autarch_web', 'autarch_web'),
a.binaries, )
a.datas,
# ── 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, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
@ -137,3 +173,38 @@ exe = EXE(
entitlements_file=None, entitlements_file=None,
icon=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',
)

66
autarch_web.py Normal file
View File

@ -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()

View File

@ -100,29 +100,36 @@ class MainMenu:
print(f"{color}[{symbol}] {message}{Colors.RESET}") print(f"{color}[{symbol}] {message}{Colors.RESET}")
def load_modules(self): def load_modules(self):
"""Load all available modules from the modules directory.""" """Load all available modules from the modules directory.
modules_path = self._app_dir / self.config.get('autarch', 'modules_path', 'modules')
if not modules_path.exists(): In a frozen (PyInstaller) build, scans both the bundled modules inside
self.print_status(f"Modules directory not found: {modules_path}", "warning") _MEIPASS and the user modules directory next to the exe. User modules
return 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"): # Collect module files — bundled first, then user (user overrides)
if module_file.name.startswith("_"): module_files: dict[str, Path] = {}
continue
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 is_frozen():
if module_name == "setup": user_dir = get_user_modules_dir()
continue 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: try:
spec = importlib.util.spec_from_file_location(module_name, module_file) spec = importlib.util.spec_from_file_location(module_name, module_file)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module)
# Check if module has required 'run' function
if hasattr(module, 'run'): if hasattr(module, 'run'):
self.modules[module_name] = ModuleInfo(module_name, module_file, module) self.modules[module_name] = ModuleInfo(module_name, module_file, module)
else: else:

View File

@ -14,22 +14,60 @@ from typing import Optional, List
# ── Application Root ──────────────────────────────────────────────── # ── 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/) import sys as _sys
_APP_DIR = Path(__file__).resolve().parent.parent
_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: def get_app_dir() -> Path:
"""Return the AUTARCH application root directory.""" """Return the writable application root directory."""
return _APP_DIR 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: def get_core_dir() -> Path:
return _APP_DIR / 'core' return _BUNDLE_DIR / 'core'
def get_modules_dir() -> Path: 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: def get_data_dir() -> Path:
@ -39,7 +77,15 @@ def get_data_dir() -> Path:
def get_config_path() -> 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: def get_results_dir() -> Path:

130
core/tray.py Normal file
View File

@ -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()

85
installer.iss Normal file
View File

@ -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;

144
installer.nsi Normal file
View File

@ -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

View File

@ -16,6 +16,7 @@ include_files = [
(str(SRC / 'web' / 'static'), 'web/static'), (str(SRC / 'web' / 'static'), 'web/static'),
(str(SRC / 'data'), 'data'), (str(SRC / 'data'), 'data'),
(str(SRC / 'modules'), 'modules'), (str(SRC / 'modules'), 'modules'),
(str(SRC / 'models' / 'Hal_v2.gguf'), 'models/Hal_v2.gguf'),
(str(SRC / 'autarch_settings.conf'), 'autarch_settings.conf'), (str(SRC / 'autarch_settings.conf'), 'autarch_settings.conf'),
(str(SRC / 'user_manual.md'), 'user_manual.md'), (str(SRC / 'user_manual.md'), 'user_manual.md'),
(str(SRC / 'windows_manual.md'), 'windows_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 = { build_exe_options = {
'packages': [ 'packages': [
'flask', 'jinja2', 'werkzeug', 'markupsafe', 'flask', 'jinja2', 'werkzeug', 'markupsafe',
'bcrypt', 'requests', 'core', 'web', 'web.routes', 'modules', 'bcrypt', 'requests', 'pystray', 'PIL',
'core', 'web', 'web.routes', 'modules',
], ],
'includes': [ '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.llm', 'core.agent', 'core.tools',
'core.msf', 'core.msf_interface', 'core.msf', 'core.msf_interface',
'core.hardware', 'core.android_protect', 'core.hardware', 'core.android_protect',
@ -49,6 +51,7 @@ build_exe_options = {
'web.routes.revshell', 'web.routes.archon', 'web.routes.revshell', 'web.routes.archon',
'web.routes.msf', 'web.routes.chat', 'web.routes.msf', 'web.routes.chat',
'web.routes.targets', 'web.routes.encmodules', 'web.routes.targets', 'web.routes.encmodules',
'web.routes.llm_trainer',
], ],
'excludes': ['torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic', 'excludes': ['torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic',
'tkinter', 'matplotlib', 'numpy', 'tkinter', 'matplotlib', 'numpy',
@ -66,7 +69,7 @@ build_exe_options = {
bdist_msi_options = { bdist_msi_options = {
'upgrade_code': '{A07B3D2E-5F1C-4D8A-9E6B-0C2F7A8D4E1B}', 'upgrade_code': '{A07B3D2E-5F1C-4D8A-9E6B-0C2F7A8D4E1B}',
'add_to_path': False, 'add_to_path': False,
'initial_target_dir': r'[ProgramFilesFolder]\AUTARCH', 'initial_target_dir': r'[LocalAppDataFolder]\AUTARCH',
} }
setup( setup(
@ -81,8 +84,13 @@ setup(
executables=[ executables=[
Executable( Executable(
'autarch.py', 'autarch.py',
target_name='autarch_public', target_name='autarch',
base=None, # console application base=None, # console application (CLI)
),
Executable(
'autarch_web.py',
target_name='autarch_web',
base='Win32GUI', # no console window (tray icon only)
), ),
], ],
) )

View File

@ -9,15 +9,20 @@ from pathlib import Path
from flask import Flask from flask import Flask
# Ensure framework is importable # 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)) sys.path.insert(0, str(FRAMEWORK_DIR))
def create_app(): def create_app():
# In frozen builds, templates/static are inside _MEIPASS, not next to __file__
bundle_web = FRAMEWORK_DIR / 'web'
app = Flask( app = Flask(
__name__, __name__,
template_folder=str(Path(__file__).parent / 'templates'), template_folder=str(bundle_web / 'templates'),
static_folder=str(Path(__file__).parent / 'static') static_folder=str(bundle_web / 'static')
) )
# Config # Config