Autarch Will Control The Internet

This commit is contained in:
DigiJ
2026-03-13 15:17:15 -07:00
commit 4d3570781e
401 changed files with 484494 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
[Unit]
Description=AUTARCH Web Dashboard
Documentation=file:///home/snake/autarch/GUIDE.md
After=network.target
Wants=network.target
[Service]
Type=simple
User=snake
Group=snake
WorkingDirectory=/home/snake/autarch
ExecStart=/usr/bin/python3 /home/snake/autarch/autarch.py --web --no-banner
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=autarch-web
# Security hardening
NoNewPrivileges=false
ProtectHome=false
PrivateTmp=true
# Environment
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target

287
scripts/build-all.ps1 Normal file
View File

@@ -0,0 +1,287 @@
# ═══════════════════════════════════════════════════════════════════════════
# AUTARCH — Full Windows Installer Builder
#
# Auto-installs required tools, then builds:
# dist\bin\AUTARCH\AUTARCH.exe — standalone executable bundle
# dist\bin\AUTARCH-1.3-win64.msi — Windows installer
#
# Usage (run as Administrator or allow UAC prompt):
# powershell -ExecutionPolicy Bypass -File scripts\build-all.ps1
#
# What this script installs (if not already present):
# - pyinstaller (via pip)
# - WiX Toolset 4 (via dotnet tool install --global wix)
# - .NET SDK (via winget, if dotnet is missing)
# ═══════════════════════════════════════════════════════════════════════════
param(
[string]$Python = "python",
[string]$Version = "1.3",
[switch]$SkipExe = $false, # Skip .exe build (use existing dist\bin\AUTARCH\)
[switch]$SkipMsi = $false # Skip .msi build
)
$ErrorActionPreference = "Stop"
$AppDir = Split-Path -Parent $PSScriptRoot
$BinDir = Join-Path $AppDir "dist\bin"
$DistDir = Join-Path $AppDir "dist"
Write-Host ""
Write-Host "████████████████████████████████████████████████████" -ForegroundColor Cyan
Write-Host " AUTARCH $Version — Windows Build System" -ForegroundColor Cyan
Write-Host "████████████████████████████████████████████████████" -ForegroundColor Cyan
Write-Host ""
# ── Helper functions ──────────────────────────────────────────────────────────
function Write-Step([string]$msg) {
Write-Host ""
Write-Host "$msg" -ForegroundColor Yellow
}
function Write-OK([string]$msg) {
Write-Host "$msg" -ForegroundColor Green
}
function Write-Warn([string]$msg) {
Write-Host "$msg" -ForegroundColor Magenta
}
function Test-Command([string]$cmd) {
return $null -ne (Get-Command $cmd -ErrorAction SilentlyContinue)
}
# ── 1. Verify Python ──────────────────────────────────────────────────────────
Write-Step "Checking Python..."
try {
$pyVer = & $Python --version 2>&1
Write-OK "$pyVer"
} catch {
Write-Host "ERROR: Python not found. Install Python 3.10+ from python.org" -ForegroundColor Red
exit 1
}
# ── 2. Install / verify PyInstaller ──────────────────────────────────────────
Write-Step "Checking PyInstaller..."
$piVer = & $Python -c "import PyInstaller; print(PyInstaller.__version__)" 2>&1
if ($piVer -match "^\d") {
Write-OK "PyInstaller $piVer"
} else {
Write-Warn "PyInstaller not found — installing..."
& $Python -m pip install pyinstaller --quiet
$piVer = & $Python -c "import PyInstaller; print(PyInstaller.__version__)" 2>&1
Write-OK "PyInstaller $piVer installed"
}
# ── 3. Install / verify .NET SDK (required for WiX 4) ────────────────────────
if (-not $SkipMsi) {
Write-Step "Checking .NET SDK (required for WiX)..."
if (Test-Command "dotnet") {
$dotnetVer = (dotnet --version 2>&1)
Write-OK ".NET SDK $dotnetVer"
} else {
Write-Warn ".NET SDK not found — installing via winget..."
if (Test-Command "winget") {
winget install Microsoft.DotNet.SDK.8 --silent --accept-package-agreements --accept-source-agreements
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
if (Test-Command "dotnet") {
Write-OK ".NET SDK installed"
} else {
Write-Host "ERROR: Failed to install .NET SDK. Install manually from https://dot.net" -ForegroundColor Red
Write-Host " Then re-run this script." -ForegroundColor Yellow
exit 1
}
} else {
Write-Host "ERROR: winget not found. Install .NET SDK 8+ manually from https://dot.net" -ForegroundColor Red
Write-Host " Then re-run this script." -ForegroundColor Yellow
exit 1
}
}
}
# ── 4. Install / verify WiX Toolset 4 ────────────────────────────────────────
if (-not $SkipMsi) {
Write-Step "Checking WiX Toolset 4..."
$wixOk = $false
if (Test-Command "wix") {
$wixVer = (wix --version 2>&1)
Write-OK "wix $wixVer"
$wixOk = $true
} else {
# Try via dotnet tool
$dtWix = (dotnet tool list --global 2>&1) | Select-String "wix"
if ($dtWix) {
Write-OK "WiX found (dotnet tool)"
$wixOk = $true
} else {
Write-Warn "WiX not found — installing via dotnet tool..."
dotnet tool install --global wix --prerelease 2>&1 | Out-Null
# Refresh PATH
$env:Path += ";$env:USERPROFILE\.dotnet\tools"
if (Test-Command "wix") {
$wixVer = (wix --version 2>&1)
Write-OK "WiX $wixVer installed"
$wixOk = $true
} else {
Write-Warn "WiX could not be installed automatically."
Write-Warn "Install manually: dotnet tool install --global wix"
Write-Warn "Skipping MSI build."
$SkipMsi = $true
}
}
}
}
# ── 5. Create output directory ────────────────────────────────────────────────
Write-Step "Preparing output directory..."
if (-not (Test-Path $BinDir)) {
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
}
Write-OK "dist\bin\"
# ── 6. Build .exe with PyInstaller ───────────────────────────────────────────
if (-not $SkipExe) {
Write-Step "Building AUTARCH.exe (PyInstaller one-directory bundle)..."
Write-Host " This may take 310 minutes..." -ForegroundColor DarkGray
$SpecFile = Join-Path $AppDir "autarch.spec"
$WorkDir = Join-Path $DistDir ".pyinstaller-work"
Set-Location $AppDir
& $Python -m PyInstaller $SpecFile `
--distpath $BinDir `
--workpath $WorkDir `
--noconfirm `
--clean
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: PyInstaller build failed." -ForegroundColor Red
exit $LASTEXITCODE
}
$exePath = Join-Path $BinDir "AUTARCH\AUTARCH.exe"
if (Test-Path $exePath) {
$sizeMB = [math]::Round((Get-ChildItem (Join-Path $BinDir "AUTARCH") -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB, 1)
Write-OK "dist\bin\AUTARCH\AUTARCH.exe ($sizeMB MB bundle)"
} else {
Write-Host "ERROR: AUTARCH.exe not found after build." -ForegroundColor Red
exit 1
}
} else {
Write-Warn "Skipping .exe build (-SkipExe)"
$exePath = Join-Path $BinDir "AUTARCH\AUTARCH.exe"
if (-not (Test-Path $exePath)) {
Write-Host "ERROR: dist\bin\AUTARCH\AUTARCH.exe not found. Remove -SkipExe to build it." -ForegroundColor Red
exit 1
}
}
# ── 7. Generate WiX source (.wxs) from PyInstaller output ────────────────────
if (-not $SkipMsi) {
Write-Step "Generating WiX source from AUTARCH bundle..."
$WxsFile = Join-Path $DistDir ".wix\AUTARCH.wxs"
$WxsDir = Split-Path $WxsFile
$BundleDir = Join-Path $BinDir "AUTARCH"
$MsiOut = Join-Path $BinDir "AUTARCH-${Version}-win64.msi"
if (-not (Test-Path $WxsDir)) {
New-Item -ItemType Directory -Path $WxsDir -Force | Out-Null
}
# Use WiX 4 harvest tool to generate component list from the bundle directory
$HeatOut = Join-Path $WxsDir "components.wxs"
# Build WiX 4 MSI directly using wix build command
Write-Host " Running wix build (WiX 4)..." -ForegroundColor DarkGray
# Create a minimal WiX 4 package definition
$WixSrcDir = Join-Path $DistDir ".wix"
$PackageWxs = Join-Path $WixSrcDir "Package.wxs"
# Generate file list for WiX
$files = Get-ChildItem $BundleDir -Recurse -File
$compLines = @()
$fileLines = @()
$i = 0
foreach ($f in $files) {
$rel = $f.FullName.Substring($BundleDir.Length + 1)
$relDir = [System.IO.Path]::GetDirectoryName($rel)
$id = "f$i"
$compId = "c$i"
$fileLines += " <File Id='$id' Source='$($f.FullName.Replace('\','\\'))' />"
$compLines += " <Component Id='$compId' Guid='*'><File Id='${id}f' Source='$($f.FullName.Replace('\','\\'))' /></Component>"
$i++
}
# Write Package.wxs (WiX 4 syntax)
@"
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="AUTARCH" Version="$Version.0.0" Manufacturer="darkHal Security Group"
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
Language="1033" Codepage="1252">
<MajorUpgrade DowngradeErrorMessage="A newer version of AUTARCH is already installed." />
<MediaTemplate EmbedCab="yes" />
<Feature Id="Main" Title="AUTARCH" Level="1">
<ComponentGroupRef Id="AppFiles" />
</Feature>
<StandardDirectory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="AUTARCH">
<ComponentGroup Id="AppFiles">
"@ | Out-File -FilePath $PackageWxs -Encoding utf8
# Add all files as components
$i = 0
foreach ($f in $files) {
$rel = $f.FullName.Substring($BundleDir.Length + 1)
$relDir = [System.IO.Path]::GetDirectoryName($rel)
$fid = "File_$i"
$cid = "Comp_$i"
$did = if ($relDir) { "Dir_$($relDir.Replace('\','_').Replace(' ','_'))" } else { "INSTALLFOLDER" }
$srcPath = $f.FullName
" <Component Id='$cid' Guid='*'><File Id='$fid' Source='$srcPath' /></Component>" |
Out-File -FilePath $PackageWxs -Encoding utf8 -Append
$i++
}
@"
</ComponentGroup>
</Directory>
</StandardDirectory>
</Package>
</Wix>
"@ | Out-File -FilePath $PackageWxs -Encoding utf8 -Append
Write-Host " Compiling MSI with WiX 4..." -ForegroundColor DarkGray
$env:Path += ";$env:USERPROFILE\.dotnet\tools"
wix build $PackageWxs -out $MsiOut
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: WiX MSI build failed." -ForegroundColor Red
Write-Warn "The .exe bundle is still available at dist\bin\AUTARCH\AUTARCH.exe"
exit $LASTEXITCODE
}
if (Test-Path $MsiOut) {
$sizeMB = [math]::Round((Get-Item $MsiOut).Length / 1MB, 1)
Write-OK "dist\bin\AUTARCH-${Version}-win64.msi ($sizeMB MB)"
}
}
# ── 8. Summary ────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "████████████████████████████████████████████████████" -ForegroundColor Green
Write-Host " BUILD COMPLETE" -ForegroundColor Green
Write-Host "████████████████████████████████████████████████████" -ForegroundColor Green
Write-Host ""
Write-Host " Standalone bundle: dist\bin\AUTARCH\AUTARCH.exe" -ForegroundColor White
if (-not $SkipMsi) {
Write-Host " MSI installer: dist\bin\AUTARCH-${Version}-win64.msi" -ForegroundColor White
}
Write-Host ""
Write-Host " Run standalone: .\dist\bin\AUTARCH\AUTARCH.exe --web" -ForegroundColor Cyan
Write-Host " Install MSI: msiexec /i dist\bin\AUTARCH-${Version}-win64.msi" -ForegroundColor Cyan
Write-Host ""

309
scripts/build-deb.sh Normal file
View File

@@ -0,0 +1,309 @@
#!/usr/bin/env bash
# ───────────────────────────────────────────────────────────────
# AUTARCH .deb package builder
#
# Usage: bash scripts/build-deb.sh [version] [--arch arm64|amd64|all]
# Output: dist/autarch_{version}_{arch}.deb
#
# No git. No debhelper. No pybuild. Just dpkg-deb.
# ───────────────────────────────────────────────────────────────
set -euo pipefail
APP_NAME="autarch"
SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
# ── Parse arguments ──────────────────────────────────────────
FORCE_ARCH=""
VERSION=""
for arg in "$@"; do
case "$arg" in
--arch=*) FORCE_ARCH="${arg#--arch=}" ;;
--arch) ;; # handled by next iteration
arm64|amd64|armhf|all)
if [[ -z "$FORCE_ARCH" ]]; then
FORCE_ARCH="$arg"
elif [[ -z "$VERSION" ]]; then
VERSION="$arg"
fi
;;
*)
if [[ -z "$VERSION" && ! "$arg" =~ ^-- ]]; then
VERSION="$arg"
fi
;;
esac
done
# Handle "all" — build for multiple architectures
if [[ "$FORCE_ARCH" == "all" ]]; then
echo "Building for all architectures..."
for arch in arm64 amd64; do
bash "$0" ${VERSION:+$VERSION} "$arch"
done
exit 0
fi
# ── Detect or override architecture ──────────────────────────
if [[ -n "$FORCE_ARCH" ]]; then
DEB_ARCH="$FORCE_ARCH"
else
DEB_ARCH="$(dpkg --print-architecture)"
fi
case "$DEB_ARCH" in
arm64) PLATFORM_TAG="linux-arm64" ;;
amd64) PLATFORM_TAG="linux-x86_64" ;;
armhf) PLATFORM_TAG="linux-armhf" ;;
*) PLATFORM_TAG="linux-${DEB_ARCH}" ;;
esac
# ── Determine version ────────────────────────────────────────
if [[ -z "$VERSION" ]]; then
VERSION="$(grep -m1 '^VERSION' "$SRC_DIR/autarch.py" | sed 's/.*"\(.*\)".*/\1/')"
if [[ -z "$VERSION" ]]; then
echo "ERROR: Could not extract VERSION from autarch.py" >&2
exit 1
fi
fi
echo "Building ${APP_NAME} ${VERSION} for ${DEB_ARCH} (${PLATFORM_TAG})"
# ── Paths ─────────────────────────────────────────────────────
DIST_DIR="$SRC_DIR/dist"
BUILD_DIR="$DIST_DIR/.build"
PKG_NAME="${APP_NAME}_${VERSION}_${DEB_ARCH}"
STAGE="$BUILD_DIR/$PKG_NAME"
OPT="$STAGE/opt/autarch"
# Clean previous build
rm -rf "$STAGE"
mkdir -p "$OPT" "$STAGE/DEBIAN" "$STAGE/usr/bin"
# ── Copy application files ────────────────────────────────────
echo "Copying application files..."
# Core Python files at root
cp "$SRC_DIR/autarch.py" "$OPT/"
cp "$SRC_DIR/requirements.txt" "$OPT/"
# Settings: ship live config (dpkg conffile-protected) AND .default template
cp "$SRC_DIR/autarch_settings.conf" "$OPT/autarch_settings.conf"
cp "$SRC_DIR/autarch_settings.conf" "$OPT/autarch_settings.conf.default"
# User-editable data files
cp "$SRC_DIR/custom_sites.inf" "$OPT/"
cp "$SRC_DIR/custom_adultsites.json" "$OPT/"
# Documentation
[[ -f "$SRC_DIR/GUIDE.md" ]] && cp "$SRC_DIR/GUIDE.md" "$OPT/"
[[ -f "$SRC_DIR/user_manual.md" ]] && cp "$SRC_DIR/user_manual.md" "$OPT/"
# Directory trees
for dir in core modules web; do
cp -a "$SRC_DIR/$dir" "$OPT/"
done
# Data (sites db etc.)
cp -a "$SRC_DIR/data" "$OPT/"
# Hardware config templates
if [[ -d "$SRC_DIR/.config" ]]; then
cp -a "$SRC_DIR/.config" "$OPT/"
fi
# Bundled tools for THIS architecture
if [[ -d "$SRC_DIR/tools/$PLATFORM_TAG" ]]; then
mkdir -p "$OPT/tools/$PLATFORM_TAG"
cp -a "$SRC_DIR/tools/$PLATFORM_TAG/." "$OPT/tools/$PLATFORM_TAG/"
fi
# Android tools (arch-independent — same adb/fastboot for the host)
if [[ -d "$SRC_DIR/android" ]]; then
cp -a "$SRC_DIR/android" "$OPT/"
fi
# Companion APK (if built)
APK="$SRC_DIR/autarch_companion/app/build/outputs/apk/debug/app-debug.apk"
if [[ -f "$APK" ]]; then
mkdir -p "$OPT/companion"
cp "$APK" "$OPT/companion/archon.apk"
fi
# Systemd service
if [[ -f "$SRC_DIR/scripts/autarch-web.service" ]]; then
mkdir -p "$STAGE/etc/systemd/system"
cp "$SRC_DIR/scripts/autarch-web.service" "$STAGE/etc/systemd/system/autarch-web.service"
fi
# ── Strip excluded files ──────────────────────────────────────
echo "Stripping excluded files..."
# __pycache__ and .pyc
find "$OPT" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find "$OPT" -name "*.pyc" -delete 2>/dev/null || true
# Backup files
find "$OPT" -name "*.bk" -delete 2>/dev/null || true
# .claude directory
rm -rf "$OPT/.claude"
# node_modules, src (build artifacts)
rm -rf "$OPT/node_modules" "$OPT/src"
# User-generated data that shouldn't ship
rm -f "$OPT"/*_profiles.json
# System/dev files
rm -f "$OPT/system.inf" "$OPT/backupexec_dump.mtf"
# Dev docs
rm -f "$OPT/DEVLOG.md" "$OPT/devjournal.md" "$OPT/autarch_dev.md"
rm -f "$OPT/android_plan.md" "$OPT/master_plan.md"
# Node/package files
rm -f "$OPT/package.json" "$OPT/package-lock.json" "$OPT/.gitignore"
# User data dirs — create empty structure but don't ship contents
for datadir in results dossiers data/captures data/exports data/hardware data/pentest_sessions data/uploads; do
rm -rf "$OPT/$datadir"
done
# ── Generate DEBIAN/control ───────────────────────────────────
INSTALLED_KB=$(du -sk "$STAGE" | cut -f1)
cat > "$STAGE/DEBIAN/control" <<EOF
Package: ${APP_NAME}
Version: ${VERSION}
Architecture: ${DEB_ARCH}
Maintainer: darkHal Security Group <noreply@darkhal.local>
Description: AUTARCH - Autonomous Tactical Agent for Reconnaissance,
Counterintelligence, and Hacking. Self-contained security framework
with web UI, OSINT tools, network scanning, hardware flashing,
and optional LLM integration.
Section: utils
Priority: optional
Installed-Size: ${INSTALLED_KB}
Depends: python3 (>= 3.10), python3-pip, python3-venv
Recommends: nmap, tcpdump, tshark, miniupnpc, wireguard-tools
Suggests: metasploit-framework, clamav
EOF
# ── Generate DEBIAN/conffiles ─────────────────────────────────
cat > "$STAGE/DEBIAN/conffiles" <<EOF
/opt/autarch/autarch_settings.conf
/opt/autarch/custom_sites.inf
/opt/autarch/custom_adultsites.json
EOF
# ── Generate DEBIAN/postinst ──────────────────────────────────
cat > "$STAGE/DEBIAN/postinst" <<'POSTINST'
#!/bin/bash
set -e
APP="/opt/autarch"
# First install: ensure live config exists (dpkg ships it, but just in case)
if [ ! -f "$APP/autarch_settings.conf" ]; then
cp "$APP/autarch_settings.conf.default" "$APP/autarch_settings.conf"
fi
# Create writable data directories
for d in results dossiers data/captures data/exports data/hardware data/pentest_sessions data/uploads; do
mkdir -p "$APP/$d"
done
# Set permissions on entry point
chmod +x "$APP/autarch.py"
# Set permissions on bundled tools
for f in "$APP"/tools/linux-*/nmap "$APP"/tools/linux-*/tcpdump \
"$APP"/tools/linux-*/upnpc "$APP"/tools/linux-*/wg; do
[ -f "$f" ] && chmod +x "$f"
done
# Android binaries
for f in "$APP"/android/adb "$APP"/android/fastboot; do
[ -f "$f" ] && chmod +x "$f"
done
# Create Python venv and install dependencies
if [ ! -d "$APP/venv" ]; then
echo "Creating Python virtual environment..."
python3 -m venv "$APP/venv"
fi
echo "Installing Python dependencies..."
"$APP/venv/bin/pip" install --quiet --upgrade pip
"$APP/venv/bin/pip" install --quiet -r "$APP/requirements.txt"
echo "AUTARCH installed successfully."
echo "Run 'autarch --help' to get started."
POSTINST
chmod 0755 "$STAGE/DEBIAN/postinst"
# ── Generate DEBIAN/prerm ─────────────────────────────────────
cat > "$STAGE/DEBIAN/prerm" <<'PRERM'
#!/bin/bash
set -e
# Nothing to do before removal — just a placeholder for future needs.
PRERM
chmod 0755 "$STAGE/DEBIAN/prerm"
# ── Generate DEBIAN/postrm ────────────────────────────────────
cat > "$STAGE/DEBIAN/postrm" <<'POSTRM'
#!/bin/bash
set -e
APP="/opt/autarch"
if [ "$1" = "purge" ]; then
# Remove venv (large, regenerable)
rm -rf "$APP/venv"
# Remove empty data directories only
for d in results dossiers data/captures data/exports data/hardware data/pentest_sessions data/uploads data; do
[ -d "$APP/$d" ] && rmdir --ignore-fail-on-non-empty "$APP/$d" 2>/dev/null || true
done
# Remove app dir if completely empty
rmdir --ignore-fail-on-non-empty "$APP" 2>/dev/null || true
fi
POSTRM
chmod 0755 "$STAGE/DEBIAN/postrm"
# ── Generate /usr/bin/autarch wrapper ─────────────────────────
cat > "$STAGE/usr/bin/autarch" <<'WRAPPER'
#!/bin/bash
exec /opt/autarch/venv/bin/python3 /opt/autarch/autarch.py "$@"
WRAPPER
chmod 0755 "$STAGE/usr/bin/autarch"
# ── Build the .deb ────────────────────────────────────────────
echo "Building .deb package..."
mkdir -p "$DIST_DIR"
DEB_OUT="$DIST_DIR/${PKG_NAME}.deb"
if command -v fakeroot >/dev/null 2>&1; then
fakeroot dpkg-deb --build "$STAGE" "$DEB_OUT"
else
dpkg-deb --build "$STAGE" "$DEB_OUT"
fi
# ── Clean up staging ──────────────────────────────────────────
rm -rf "$BUILD_DIR"
# ── Summary ───────────────────────────────────────────────────
DEB_SIZE=$(du -h "$DEB_OUT" | cut -f1)
echo ""
echo "════════════════════════════════════════════════════"
echo " Package: $DEB_OUT"
echo " Size: $DEB_SIZE"
echo " Arch: $DEB_ARCH ($PLATFORM_TAG)"
echo "════════════════════════════════════════════════════"
echo ""
echo "Inspect: dpkg-deb --info $DEB_OUT"
echo "Contents: dpkg-deb --contents $DEB_OUT | head -50"
echo "Install: sudo dpkg -i $DEB_OUT && sudo apt-get install -f"

128
scripts/build-exe.ps1 Normal file
View File

@@ -0,0 +1,128 @@
# ═══════════════════════════════════════════════════════════════════════════
# AUTARCH — PyInstaller .exe Builder
#
# Creates a standalone Windows executable bundle using PyInstaller.
# Output: dist\bin\AUTARCH\AUTARCH.exe (one-directory bundle)
#
# Usage:
# powershell -ExecutionPolicy Bypass -File scripts\build-exe.ps1
# powershell -ExecutionPolicy Bypass -File scripts\build-exe.ps1 -OneFile
#
# Prerequisites:
# pip install pyinstaller
# ═══════════════════════════════════════════════════════════════════════════
param(
[switch]$OneFile = $false, # --onefile build (larger, slower to start)
[string]$Python = "python" # Python interpreter to use
)
$ErrorActionPreference = "Stop"
$AppDir = Split-Path -Parent $PSScriptRoot
Write-Host ""
Write-Host "════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host " AUTARCH .exe Builder (PyInstaller)" -ForegroundColor Cyan
Write-Host "════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host ""
# ── Verify PyInstaller ────────────────────────────────────────────────────────
try {
$ver = & $Python -c "import PyInstaller; print(PyInstaller.__version__)" 2>&1
Write-Host "PyInstaller $ver" -ForegroundColor Green
} catch {
Write-Host "ERROR: PyInstaller not found." -ForegroundColor Red
Write-Host "Install it: pip install pyinstaller" -ForegroundColor Yellow
exit 1
}
# ── Prepare output directory ──────────────────────────────────────────────────
$BinDir = Join-Path $AppDir "dist\bin"
if (-not (Test-Path $BinDir)) {
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
}
# ── Run PyInstaller ───────────────────────────────────────────────────────────
$SpecFile = Join-Path $AppDir "autarch.spec"
$DistDir = $BinDir
$WorkDir = Join-Path $AppDir "dist\.pyinstaller-work"
Write-Host "Building AUTARCH.exe..." -ForegroundColor Yellow
Write-Host " Spec: $SpecFile" -ForegroundColor DarkGray
Write-Host " Output: $DistDir" -ForegroundColor DarkGray
Write-Host ""
$Args = @(
$SpecFile,
"--distpath", $DistDir,
"--workpath", $WorkDir,
"--noconfirm",
"--clean"
)
if ($OneFile) {
# Override spec and do a one-file build directly
Write-Host "Mode: --onefile (single .exe, slower startup)" -ForegroundColor Magenta
$Args = @(
(Join-Path $AppDir "autarch.py"),
"--onefile",
"--name", "AUTARCH",
"--distpath", $DistDir,
"--workpath", $WorkDir,
"--noconfirm",
"--clean",
"--add-data", "web/templates;web/templates",
"--add-data", "web/static;web/static",
"--add-data", "data;data",
"--add-data", "modules;modules",
"--add-data", "autarch_settings.conf;.",
"--add-data", "user_manual.md;.",
"--add-data", "windows_manual.md;.",
"--add-data", "custom_sites.inf;.",
"--add-data", "custom_adultsites.json;.",
"--add-data", "android;android",
"--add-data", "tools;tools",
"--hidden-import", "flask",
"--hidden-import", "werkzeug",
"--hidden-import", "jinja2",
"--hidden-import", "bcrypt",
"--hidden-import", "requests",
"--hidden-import", "msgpack",
"--console"
)
}
Set-Location $AppDir
& $Python -m PyInstaller @Args
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host "ERROR: PyInstaller build failed (exit code $LASTEXITCODE)" -ForegroundColor Red
exit $LASTEXITCODE
}
# ── Report ────────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "════════════════════════════════════════════════" -ForegroundColor Green
Write-Host " Build complete!" -ForegroundColor Green
Write-Host "════════════════════════════════════════════════" -ForegroundColor Green
Write-Host ""
if ($OneFile) {
$exePath = Join-Path $DistDir "AUTARCH.exe"
if (Test-Path $exePath) {
$sizeMB = [math]::Round((Get-Item $exePath).Length / 1MB, 1)
Write-Host " Output: $exePath ($sizeMB MB)" -ForegroundColor White
}
} else {
$exePath = Join-Path $DistDir "AUTARCH\AUTARCH.exe"
if (Test-Path $exePath) {
$sizeKB = [math]::Round((Get-Item $exePath).Length / 1KB, 0)
Write-Host " Exe: $exePath ($sizeKB KB)" -ForegroundColor White
$dirSize = [math]::Round((Get-ChildItem (Join-Path $DistDir "AUTARCH") -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB, 1)
Write-Host " Bundle: dist\bin\AUTARCH\ ($dirSize MB total)" -ForegroundColor White
}
}
Write-Host ""
Write-Host " Run it: .\dist\bin\AUTARCH\AUTARCH.exe --web" -ForegroundColor Cyan
Write-Host ""

54
scripts/build-hw-libs.sh Normal file
View File

@@ -0,0 +1,54 @@
#!/bin/bash
# Build browser-ready bundles for AUTARCH hardware direct-mode libraries
# Run from project root: bash scripts/build-hw-libs.sh
#
# Requires: npm install (run once to install dependencies)
# Output: web/static/js/lib/*.js (committed to project, no node needed at runtime)
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
OUT_DIR="$PROJECT_DIR/web/static/js/lib"
mkdir -p "$OUT_DIR"
echo "Building hardware library bundles..."
echo "Output: $OUT_DIR"
# ADB bundle (ya-webadb / Tango)
echo " [1/3] Building adb-bundle.js..."
npx esbuild "$PROJECT_DIR/src/adb-entry.js" \
--bundle \
--format=iife \
--global-name=YumeAdb \
--platform=browser \
--target=chrome89 \
--outfile="$OUT_DIR/adb-bundle.js" \
--minify
# Fastboot bundle
echo " [2/3] Building fastboot-bundle.js..."
npx esbuild "$PROJECT_DIR/src/fastboot-entry.js" \
--bundle \
--format=iife \
--global-name=Fastboot \
--platform=browser \
--target=chrome89 \
--outfile="$OUT_DIR/fastboot-bundle.js" \
--minify
# ESP32 bundle (esptool-js)
echo " [3/3] Building esptool-bundle.js..."
npx esbuild "$PROJECT_DIR/src/esptool-entry.js" \
--bundle \
--format=iife \
--global-name=EspTool \
--platform=browser \
--target=chrome89 \
--outfile="$OUT_DIR/esptool-bundle.js" \
--minify
echo ""
echo "Build complete:"
ls -lh "$OUT_DIR"/*.js

62
scripts/build-msi.ps1 Normal file
View File

@@ -0,0 +1,62 @@
# ═══════════════════════════════════════════════════════════════════════════
# AUTARCH - Windows MSI Installer Builder
#
# Creates a Windows .msi installer using Python's built-in msilib.
# Packages the PyInstaller bundle (dist\bin\AUTARCH\) into an MSI.
# Output: dist\bin\AUTARCH-1.3-win64.msi
#
# Usage:
# powershell -ExecutionPolicy Bypass -File scripts\build-msi.ps1
#
# Prerequisites:
# - Python 3.10+ (msilib is a Windows standard library module)
# - dist\bin\AUTARCH\ must exist (run build-exe.ps1 first)
# ===============================================================================
param(
[string]$Python = "python"
)
$ErrorActionPreference = "Stop"
$AppDir = Split-Path -Parent $PSScriptRoot
Write-Host ""
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host " AUTARCH .msi Builder (msilib)" -ForegroundColor Cyan
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host ""
$BundleDir = Join-Path $AppDir "dist\bin\AUTARCH"
if (-not (Test-Path (Join-Path $BundleDir "AUTARCH.exe"))) {
Write-Host "ERROR: dist\bin\AUTARCH\AUTARCH.exe not found." -ForegroundColor Red
Write-Host " Run build-exe.ps1 first to create the bundle." -ForegroundColor Yellow
exit 1
}
Set-Location $AppDir
Write-Host "Building MSI from dist\bin\AUTARCH\ bundle..." -ForegroundColor Yellow
Write-Host ""
& $Python (Join-Path $AppDir "scripts\make_msi.py")
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host "ERROR: MSI build failed (exit code $LASTEXITCODE)" -ForegroundColor Red
exit $LASTEXITCODE
}
$msiFiles = Get-ChildItem (Join-Path $AppDir "dist\bin") -Filter "*.msi" -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "===============================================" -ForegroundColor Green
Write-Host " MSI build complete!" -ForegroundColor Green
Write-Host "===============================================" -ForegroundColor Green
Write-Host ""
foreach ($msi in $msiFiles) {
$sizeMB = [math]::Round($msi.Length / 1MB, 1)
Write-Host " Output: $($msi.FullName) ($sizeMB MB)" -ForegroundColor White
}
Write-Host ""
Write-Host " Install: Double-click the .msi file" -ForegroundColor Cyan
Write-Host " Or: msiexec /i AUTARCH-1.3-win64.msi" -ForegroundColor Cyan
Write-Host ""

266
scripts/build-windows.sh Normal file
View File

@@ -0,0 +1,266 @@
#!/usr/bin/env bash
# ───────────────────────────────────────────────────────────────
# AUTARCH Windows Package Builder
#
# Creates a standalone Windows-ready ZIP with:
# - All Python source + web assets
# - Batch launcher (autarch.bat)
# - PowerShell installer (install.ps1)
# - requirements.txt for pip install
# - Placeholder for Windows tools
#
# Usage: bash scripts/build-windows.sh [version]
# Output: dist/autarch_{version}_windows.zip
#
# NOTE: This builds a SOURCE distribution, not a frozen .exe.
# The install.ps1 script handles Python venv + dependency install.
# For a frozen .exe, run PyInstaller ON a Windows machine.
# ───────────────────────────────────────────────────────────────
set -euo pipefail
APP_NAME="autarch"
SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
# ── Determine version ────────────────────────────────────────
if [[ ${1:-} ]]; then
VERSION="$1"
else
VERSION="$(grep -m1 '^VERSION' "$SRC_DIR/autarch.py" | sed 's/.*"\(.*\)".*/\1/')"
if [[ -z "$VERSION" ]]; then
echo "ERROR: Could not extract VERSION from autarch.py" >&2
exit 1
fi
fi
echo "Building ${APP_NAME} ${VERSION} for Windows"
# ── Paths ─────────────────────────────────────────────────────
DIST_DIR="$SRC_DIR/dist"
BUILD_DIR="$DIST_DIR/.win-build"
STAGE="$BUILD_DIR/${APP_NAME}_${VERSION}_windows"
# Clean
rm -rf "$STAGE"
mkdir -p "$STAGE"
# ── Copy application files ────────────────────────────────────
echo "Copying application files..."
# Core
cp "$SRC_DIR/autarch.py" "$STAGE/"
cp "$SRC_DIR/requirements.txt" "$STAGE/"
# Config
cp "$SRC_DIR/autarch_settings.conf" "$STAGE/autarch_settings.conf"
cp "$SRC_DIR/autarch_settings.conf" "$STAGE/autarch_settings.conf.default"
# User files
cp "$SRC_DIR/custom_sites.inf" "$STAGE/"
cp "$SRC_DIR/custom_adultsites.json" "$STAGE/"
# Documentation
[[ -f "$SRC_DIR/GUIDE.md" ]] && cp "$SRC_DIR/GUIDE.md" "$STAGE/"
[[ -f "$SRC_DIR/user_manual.md" ]] && cp "$SRC_DIR/user_manual.md" "$STAGE/"
# Directory trees
for dir in core modules web; do
cp -a "$SRC_DIR/$dir" "$STAGE/"
done
# Data (sites db etc.)
cp -a "$SRC_DIR/data" "$STAGE/"
# Config templates
if [[ -d "$SRC_DIR/.config" ]]; then
cp -a "$SRC_DIR/.config" "$STAGE/"
fi
# Windows tools directory (placeholder — user downloads nmap/tshark etc.)
mkdir -p "$STAGE/tools/windows-x86_64"
# Companion APK
APK="$SRC_DIR/autarch_companion/app/build/outputs/apk/debug/app-debug.apk"
if [[ -f "$APK" ]]; then
mkdir -p "$STAGE/companion"
cp "$APK" "$STAGE/companion/archon.apk"
fi
# ── Strip excluded files ──────────────────────────────────────
echo "Stripping excluded files..."
find "$STAGE" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find "$STAGE" -name "*.pyc" -delete 2>/dev/null || true
find "$STAGE" -name "*.bk" -delete 2>/dev/null || true
rm -rf "$STAGE/.claude" "$STAGE/node_modules" "$STAGE/src"
rm -f "$STAGE"/*_profiles.json
rm -f "$STAGE/system.inf" "$STAGE/backupexec_dump.mtf"
rm -f "$STAGE/DEVLOG.md" "$STAGE/devjournal.md" "$STAGE/autarch_dev.md"
rm -f "$STAGE/android_plan.md" "$STAGE/master_plan.md"
rm -f "$STAGE/package.json" "$STAGE/package-lock.json" "$STAGE/.gitignore"
for datadir in results dossiers data/captures data/exports data/hardware data/pentest_sessions data/uploads; do
rm -rf "$STAGE/$datadir"
done
# ── Create Windows launcher (autarch.bat) ─────────────────────
cat > "$STAGE/autarch.bat" <<'BAT'
@echo off
REM AUTARCH Launcher for Windows
REM Uses Python virtual environment if available, falls back to system Python
setlocal
set "APP_DIR=%~dp0"
if exist "%APP_DIR%venv\Scripts\python.exe" (
"%APP_DIR%venv\Scripts\python.exe" "%APP_DIR%autarch.py" %*
) else (
python "%APP_DIR%autarch.py" %*
)
endlocal
BAT
# ── Create web dashboard launcher ─────────────────────────────
cat > "$STAGE/start-web.bat" <<'BAT'
@echo off
REM Start AUTARCH Web Dashboard
setlocal
set "APP_DIR=%~dp0"
echo Starting AUTARCH Web Dashboard...
echo Open your browser to: http://localhost:8181
echo Press Ctrl+C to stop.
echo.
if exist "%APP_DIR%venv\Scripts\python.exe" (
"%APP_DIR%venv\Scripts\python.exe" "%APP_DIR%autarch.py" --web %*
) else (
python "%APP_DIR%autarch.py" --web %*
)
endlocal
BAT
# ── Create installer script (PowerShell) ──────────────────────
cat > "$STAGE/install.ps1" <<'PS1'
# AUTARCH Windows Installer
# Run: powershell -ExecutionPolicy Bypass -File install.ps1
Write-Host ""
Write-Host "================================" -ForegroundColor Green
Write-Host " AUTARCH Installer for Windows" -ForegroundColor Green
Write-Host "================================" -ForegroundColor Green
Write-Host ""
$AppDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# Check Python
$python = Get-Command python -ErrorAction SilentlyContinue
if (-not $python) {
Write-Host "ERROR: Python not found. Install Python 3.10+ from python.org" -ForegroundColor Red
Write-Host "Make sure to check 'Add Python to PATH' during installation." -ForegroundColor Yellow
exit 1
}
$pyVer = python --version 2>&1
Write-Host "Found: $pyVer" -ForegroundColor Cyan
# Create virtual environment
$venvDir = Join-Path $AppDir "venv"
if (-not (Test-Path $venvDir)) {
Write-Host "Creating Python virtual environment..." -ForegroundColor Yellow
python -m venv $venvDir
}
# Install dependencies
$pip = Join-Path $venvDir "Scripts\pip.exe"
Write-Host "Installing dependencies..." -ForegroundColor Yellow
& $pip install --quiet --upgrade pip
& $pip install --quiet -r (Join-Path $AppDir "requirements.txt")
# Create data directories
$dataDirs = @("results", "dossiers", "data\captures", "data\exports",
"data\hardware", "data\pentest_sessions", "data\uploads")
foreach ($d in $dataDirs) {
$path = Join-Path $AppDir $d
if (-not (Test-Path $path)) {
New-Item -ItemType Directory -Path $path -Force | Out-Null
}
}
# Create desktop shortcut
$desktop = [Environment]::GetFolderPath("Desktop")
$shortcutPath = Join-Path $desktop "AUTARCH.lnk"
$shell = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = Join-Path $AppDir "autarch.bat"
$shortcut.WorkingDirectory = $AppDir
$shortcut.Description = "AUTARCH Security Platform"
$shortcut.Save()
Write-Host ""
Write-Host "================================" -ForegroundColor Green
Write-Host " Installation complete!" -ForegroundColor Green
Write-Host "================================" -ForegroundColor Green
Write-Host ""
Write-Host "Run AUTARCH:" -ForegroundColor Cyan
Write-Host " CLI: autarch.bat" -ForegroundColor White
Write-Host " Web: start-web.bat" -ForegroundColor White
Write-Host " Manual: python autarch.py --manual" -ForegroundColor White
Write-Host ""
Write-Host "Desktop shortcut created." -ForegroundColor Yellow
Write-Host ""
PS1
# ── Create README for Windows ─────────────────────────────────
cat > "$STAGE/README-WINDOWS.txt" <<'README'
AUTARCH for Windows
===================
Quick Start:
1. Run install.ps1 (right-click > Run with PowerShell)
This creates a Python environment and installs dependencies.
2. Double-click autarch.bat to start the CLI menu.
Or double-click start-web.bat for the browser dashboard.
Requirements:
- Python 3.10 or newer (python.org - check "Add to PATH")
- Windows 10 or newer
Optional Tools (place in tools\windows-x86_64\):
- nmap.exe (nmap.org)
- tshark.exe (wireshark.org)
- wg.exe (wireguard.com)
Manual:
python autarch.py --manual (in terminal)
Open user_manual.md (any text editor/Markdown viewer)
http://localhost:8181/manual (when web dashboard is running)
Companion App:
The Archon Android companion app APK is in the companion\ folder.
Install it on your phone via ADB or file transfer.
README
# ── Create the ZIP ────────────────────────────────────────────
echo "Creating ZIP archive..."
mkdir -p "$DIST_DIR"
ZIP_NAME="${APP_NAME}_${VERSION}_windows.zip"
ZIP_OUT="$DIST_DIR/$ZIP_NAME"
(cd "$BUILD_DIR" && zip -r -q "$ZIP_OUT" "$(basename "$STAGE")")
# ── Clean up ──────────────────────────────────────────────────
rm -rf "$BUILD_DIR"
# ── Summary ───────────────────────────────────────────────────
ZIP_SIZE=$(du -h "$ZIP_OUT" | cut -f1)
echo ""
echo "════════════════════════════════════════════════════"
echo " Package: $ZIP_OUT"
echo " Size: $ZIP_SIZE"
echo "════════════════════════════════════════════════════"
echo ""
echo "Transfer this ZIP to a Windows machine and run install.ps1"

86
scripts/encrypt_module.py Normal file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
encrypt_module.py — Encrypt a Python module into AUTARCH .aes format.
Usage:
python scripts/encrypt_module.py <source.py> [output.aes] [--password P] [--name N]
Examples:
python scripts/encrypt_module.py modules/encmod_sources/floppy_dick.py
python scripts/encrypt_module.py mymod.py modules/encrypted/mymod.aes --password s3cr3t
python scripts/encrypt_module.py mymod.py --password s3cr3t --name "My Module" --version 1.1
"""
import argparse
import getpass
import json
import sys
from pathlib import Path
SRC_DIR = Path(__file__).parent.parent
sys.path.insert(0, str(SRC_DIR))
def main():
parser = argparse.ArgumentParser(description='Encrypt a Python module to AUTARCH .aes format')
parser.add_argument('source', help='Path to the source .py file')
parser.add_argument('output', nargs='?', help='Output .aes path (default: modules/encrypted/<stem>.aes)')
parser.add_argument('--password', '-p', default='', help='Encryption password (prompted if omitted)')
parser.add_argument('--name', default='', help='Display name for the module')
parser.add_argument('--version', default='1.0', help='Module version (default: 1.0)')
parser.add_argument('--author', default='', help='Module author')
parser.add_argument('--description', default='', help='Module description')
parser.add_argument('--tags', default='', help='Comma-separated tags')
args = parser.parse_args()
src = Path(args.source)
if not src.exists():
print(f"ERROR: Source file not found: {src}", file=sys.stderr)
sys.exit(1)
# Determine output path
if args.output:
out = Path(args.output)
else:
enc_dir = SRC_DIR / 'modules' / 'encrypted'
enc_dir.mkdir(parents=True, exist_ok=True)
out = enc_dir / (src.stem + '.aes')
# Get password
password = args.password
if not password:
password = getpass.getpass(f"Encryption password for {src.name}: ")
confirm = getpass.getpass("Confirm password: ")
if password != confirm:
print("ERROR: Passwords do not match.", file=sys.stderr)
sys.exit(1)
if not password:
print("ERROR: Password cannot be empty.", file=sys.stderr)
sys.exit(1)
# Build metadata
tags = [t.strip() for t in args.tags.split(',') if t.strip()]
metadata = {
'name': args.name or src.stem.replace('_', ' ').title(),
'version': args.version,
'author': args.author,
'description': args.description,
'tags': tags,
'source': src.name,
}
# Encrypt
from core.module_crypto import encrypt_file
encrypt_file(src, out, password, metadata)
size_kb = round(out.stat().st_size / 1024, 1)
print(f" Encrypted: {src.name} -> {out} ({size_kb} KB)")
print(f" Name: {metadata['name']}")
print(f" Version: {metadata['version']}")
print(f" Tags: {', '.join(tags) or '(none)'}")
print()
print(" Copy the .aes file to modules/encrypted/ and it will appear in the web UI.")
if __name__ == '__main__':
main()

176
scripts/make_msi.py Normal file
View File

@@ -0,0 +1,176 @@
"""
make_msi.py — Create an MSI installer for AUTARCH using Python's built-in msilib.
Packages the contents of dist/bin/AUTARCH/ (PyInstaller one-dir build) into
a Windows Installer .msi file at dist/bin/AUTARCH-{VERSION}-win64.msi.
Usage:
python scripts/make_msi.py
Requires:
- dist/bin/AUTARCH/ to exist (run PyInstaller first)
- Windows (msilib is Windows-only)
"""
import msilib
import msilib.schema
import msilib.sequence
import msilib.text
import os
import sys
import uuid
from pathlib import Path
# ── Configuration ─────────────────────────────────────────────────────────────
SRC_DIR = Path(__file__).parent.parent
BUNDLE_DIR = SRC_DIR / 'dist' / 'bin' / 'AUTARCH'
BIN_DIR = SRC_DIR / 'dist' / 'bin'
VERSION = '1.3'
APP_NAME = 'AUTARCH'
MANUFACTURER = 'darkHal Security Group'
PRODUCT_CODE = '{6E4A2B35-C8F1-4D28-A91E-8D4F7C3B2A91}'
UPGRADE_CODE = '{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}'
MSI_OUT = BIN_DIR / f'AUTARCH-{VERSION}-win64.msi'
# ─────────────────────────────────────────────────────────────────────────────
def make_id(s: str, prefix: str = '') -> str:
"""Create a valid MSI identifier from a string (max 72 chars, no spaces/slashes)."""
safe = s.replace('\\', '_').replace('/', '_').replace(' ', '_').replace('.', '_').replace('-', '_')
result = (prefix + safe)[:72]
if result and result[0].isdigit():
result = '_' + result[1:]
return result
def collect_files(bundle_dir: Path):
"""Walk the bundle directory and return (relative_path, abs_path) tuples."""
items = []
for path in sorted(bundle_dir.rglob('*')):
if path.is_file():
rel = path.relative_to(bundle_dir)
items.append((rel, path))
return items
def build_msi():
if not BUNDLE_DIR.exists():
print(f"ERROR: Bundle directory not found: {BUNDLE_DIR}")
print("Run PyInstaller first: pyinstaller autarch.spec --distpath dist/bin")
sys.exit(1)
BIN_DIR.mkdir(parents=True, exist_ok=True)
print(f"Packaging {BUNDLE_DIR} -> {MSI_OUT}")
files = collect_files(BUNDLE_DIR)
print(f" Files to package: {len(files)}")
# ── Create the MSI database ───────────────────────────────────────────────
db = msilib.init_database(
str(MSI_OUT),
msilib.schema,
APP_NAME,
PRODUCT_CODE,
VERSION,
MANUFACTURER,
)
msilib.add_tables(db, msilib.sequence)
# ── Property table (extend — init_database already set some base properties) ──
# Use the low-level view to INSERT only new properties
try:
msilib.add_data(db, 'Property', [
('ALLUSERS', '1'),
('ARPNOMODIFY', '1'),
])
except Exception:
pass # Properties may already exist from init_database; skip
# ── Directory structure ───────────────────────────────────────────────────
# Collect all unique subdirectories
dirs = {}
dirs['TARGETDIR'] = ('TARGETDIR', 'SourceDir')
dirs['ProgramFilesFolder'] = ('TARGETDIR', 'PFiles')
dirs['INSTALLFOLDER'] = ('ProgramFilesFolder', f'{APP_NAME}|{APP_NAME}')
subdir_set = set()
for rel, _ in files:
parts = rel.parts[:-1] # directory parts (no filename)
for depth in range(len(parts)):
sub = '\\'.join(parts[:depth + 1])
subdir_set.add(sub)
# Map subdir path → directory ID
dir_id_map = {'': 'INSTALLFOLDER'}
dir_rows = [
('TARGETDIR', None, 'SourceDir'),
('ProgramFilesFolder', 'TARGETDIR', '.'),
('INSTALLFOLDER', 'ProgramFilesFolder', APP_NAME),
]
for sub in sorted(subdir_set):
parts = sub.split('\\')
parent_path = '\\'.join(parts[:-1])
parent_id = dir_id_map.get(parent_path, 'INSTALLFOLDER')
dir_id = make_id(sub, 'dir_')
dir_id_map[sub] = dir_id
short_name = parts[-1][:8] # 8.3 name (simplified)
long_name = parts[-1]
dir_name = f'{short_name}|{long_name}' if short_name != long_name else long_name
dir_rows.append((dir_id, parent_id, dir_name))
msilib.add_data(db, 'Directory', dir_rows)
# ── Feature ───────────────────────────────────────────────────────────────
msilib.add_data(db, 'Feature', [
('Main', None, 'AUTARCH Application', 'Complete AUTARCH installation', 1, 1, None, 32),
])
# ── Components and files ──────────────────────────────────────────────────
comp_rows = []
file_rows = []
feat_comp = []
for idx, (rel, abs_path) in enumerate(files):
parts = rel.parts
subdir_key = '\\'.join(parts[:-1])
dir_id = dir_id_map.get(subdir_key, 'INSTALLFOLDER')
comp_id = f'c{idx}'
file_id = f'f{idx}'
comp_guid = str(uuid.uuid5(uuid.UUID(UPGRADE_CODE), str(rel))).upper()
comp_guid = '{' + comp_guid + '}'
# Component row: (Component, ComponentId, Directory_, Attributes, Condition, KeyPath)
comp_rows.append((comp_id, comp_guid, dir_id, 0, None, file_id))
# File row: (File, Component_, FileName, FileSize, Version, Language, Attributes, Sequence)
fname = parts[-1]
short = fname[:8]
long = fname
file_name = f'{short}|{long}' if short != long else long
file_size = abs_path.stat().st_size
file_rows.append((file_id, comp_id, file_name, file_size, None, None, 512, idx + 1))
# FeatureComponents: (Feature_, Component_)
feat_comp.append(('Main', comp_id))
msilib.add_data(db, 'Component', comp_rows)
msilib.add_data(db, 'File', file_rows)
msilib.add_data(db, 'FeatureComponents', feat_comp)
# ── Media / cabinet ──────────────────────────────────────────────────────
# CAB.commit() embeds the cabinet, adds the Media row, and calls db.Commit()
cab = msilib.CAB('autarch.cab')
for idx, (rel, abs_path) in enumerate(files):
# append(full_path, file_id, logical_name_in_cab)
cab.append(str(abs_path), f'f{idx}', rel.name)
cab.commit(db) # handles Media table insert + db.Commit() internally
size_mb = round(MSI_OUT.stat().st_size / (1024 * 1024), 1)
print(f"\n OK: MSI created: {MSI_OUT} ({size_mb} MB)")
if __name__ == '__main__':
build_msi()