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:
parent
150f58f57a
commit
4a721a73b4
19
autarch.py
19
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)
|
||||
|
||||
|
||||
@ -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)
|
||||
# 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',
|
||||
)
|
||||
|
||||
66
autarch_web.py
Normal file
66
autarch_web.py
Normal 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()
|
||||
33
core/menu.py
33
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:
|
||||
|
||||
@ -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:
|
||||
|
||||
130
core/tray.py
Normal file
130
core/tray.py
Normal 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
85
installer.iss
Normal 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
144
installer.nsi
Normal 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
|
||||
18
setup_msi.py
18
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)
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
11
web/app.py
11
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user