# Embedded Resources in Knoe Installer ## Overview The Knoe Installer package includes several embedded resources that must be accessible both when running from source and when packaged as a standalone binary. ## Embedded Resources ### 1. Images (img/) - **knoeIcon.png** (1.5 MB) - Application icon - **knoeLogo.png** (2.2 MB) - Main logo - **knoeLogoSepia.png** (2.7 MB) - Sepia background for GUI - **knoeLogoBlueprint.png** (3.1 MB) - Blueprint variant - **knoeIconblueprint.png** (1.6 MB) - Blueprint icon variant ### 2. Binary Executables - **scan/network-agent** (6.8 MB) - Network scanner - Universal binary (x86_64 + arm64) - Used by network scan screen - Detects Kerberos, Active Directory, etc. ### 3. Application Bundles - **knoe-app/dist/Knoe Tools.app** (~12 MB) - Pre-built Knoe Tools - Complete macOS .app bundle - Used by installer creation screen - Can be copied to DMG or USB installer ## How Embedding Works ### Resource Path Resolution All resources use the `get_resource_path()` helper function: ```python def get_resource_path(relative_path): """Get absolute path to resource, works for dev and for PyInstaller.""" try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = Path(sys._MEIPASS) except AttributeError: # Running from source base_path = PROJECT_ROOT return base_path / relative_path ``` ### Usage Examples **Image Loading:** ```python bg_path = get_resource_path('img/knoeLogoSepia.png') if bg_path.exists(): image = Image.open(str(bg_path)) ``` **Binary Execution:** ```python scan_binary = get_resource_path("scan/network-agent") if scan_binary.exists(): process = subprocess.Popen([str(scan_binary)], ...) ``` **App Bundle Access:** ```python app_src = get_resource_path('knoe-app/dist/Knoe Tools.app') if app_src.exists(): # Copy to destination shutil.copytree(app_src, dest) ``` ## PyInstaller Configuration ### Spec File (scripts/generate_spec.py) **Data Files:** ```python datas = [ ('installer', 'installer'), ('conf', 'conf'), ('etc', 'etc'), ('img', 'img'), ('docs', 'docs'), ('knoe-app/dist/Knoe Tools.app', 'knoe-app/dist/Knoe Tools.app'), ] ``` **Binaries:** ```python binaries = [ ('scan/network-agent', 'scan'), ] ``` The `binaries` list ensures executable permissions are preserved. ## File Sizes Total embedded resources: ~30-35 MB Breakdown: - Images: ~11 MB - network-agent: 6.8 MB - Knoe Tools.app: ~12 MB - Other resources: ~5-10 MB Final installer bundle: ~50-100 MB (includes Python runtime) ## Usage in Installer ### Network Scan Screen The network scan screen uses `network-agent` to detect services: ```python scan_binary = get_resource_path("scan/network-agent") process = subprocess.Popen([str(scan_binary)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) ``` Output is captured in real-time and displayed in the scan results text widget. ### Installer Creation Screen The installer creation screen copies Knoe Tools.app to DMG: ```python app_src = get_resource_path('knoe-app/dist/Knoe Tools.app') if app_src.exists(): shutil.copytree(app_src, staging / 'Knoe Tools.app') ``` ## Testing ### Verify Resources from Source ```bash python3 test_embedded_resources.sh ``` This tests: - ✓ All image files exist - ✓ network-agent binary exists and is executable - ✓ Knoe Tools.app bundle exists - ✓ Spec file includes all resources - ✓ Resource sizes ### Verify Resources in Built Package After building: ```bash # Check extracted resources ./dist/knoe-installer --help # In another terminal, while installer is running: ls -la /tmp/_MEI*/scan/ ls -la /tmp/_MEI*/img/ ls -la "/tmp/_MEI*/knoe-app/dist/Knoe Tools.app" ``` ## Troubleshooting ### Binary Not Found Error **Error:** `Scan binary not found at /var/folders/.../scan/network-agent` **Cause:** Binary not included in package or path not using `get_resource_path()` **Solution:** 1. Verify spec includes binary: `grep network-agent installer.spec` 2. Check code uses `get_resource_path()`: `grep "get_resource_path.*network-agent" install.py` 3. Rebuild: `make clean && make package` ### Binary Not Executable **Error:** `Permission denied` when running network-agent **Cause:** Binary permissions not preserved in package **Solution:** Ensure binary is in `binaries` list (not `datas`) in spec file: ```python binaries = [ ('scan/network-agent', 'scan'), # Correct - preserves +x ] # NOT in datas: # datas = [('scan/network-agent', 'scan')] # Wrong - loses +x ``` ### App Bundle Not Found **Error:** `Knoe Tools.app not found` **Cause:** App not built before packaging installer **Solution:** 1. Build Knoe Tools.app first (in knoe-app directory) 2. Verify it exists: `ls "knoe-app/dist/Knoe Tools.app"` 3. Then build installer: `make package` ### Large Bundle Size **Cause:** Including large binary files significantly increases bundle size **Optimization Options:** 1. **Compress app bundle:** ```bash cd knoe-app/dist zip -r "Knoe Tools.zip" "Knoe Tools.app" # Include zip instead of .app ``` 2. **Download on demand:** Instead of embedding, download from server when needed 3. **Exclude debug symbols:** Strip binaries before packaging: ```bash strip scan/network-agent ``` ## Build Process When running `make package`: 1. **Icon conversion:** PNG → ICNS 2. **Spec generation:** Creates installer.spec with all resources 3. **PyInstaller:** - Analyzes install.py - Collects dependencies - Copies data files (preserves directory structure) - Copies binaries (preserves execute permissions) - Creates single-file executable 4. **Bundle creation:** Packages into .app with icon ### Resource Extraction at Runtime When the packaged installer runs: 1. PyInstaller extracts resources to `/tmp/_MEI/` 2. Sets `sys._MEIPASS` to extraction directory 3. `get_resource_path()` uses `_MEIPASS` to find resources 4. Resources deleted automatically when installer exits ## Security Considerations ### Code Signing Embedded binaries should be code signed: ```bash codesign --force --sign "Developer ID Application: Your Name" scan/network-agent ``` Then build the installer - the signed binary will be included. ### Verification Users can verify embedded binaries: ```bash # Check signature of network-agent after extraction codesign --verify --verbose /tmp/_MEI*/scan/network-agent # Check installer bundle signature codesign --verify --verbose "dist/Knoe Installer.app" ``` ## Future Enhancements ### Lazy Loading For large resources, consider lazy loading: ```python def get_knoe_tools_app(): """Download or extract Knoe Tools.app only when needed.""" app_path = get_resource_path('knoe-app/dist/Knoe Tools.app') if not app_path.exists(): # Download from server download_knoe_tools(app_path) return app_path ``` ### Compression Compress large resources: ```python # In spec file datas = [ ('knoe-app/dist/Knoe Tools.zip', 'knoe-app/dist'), # Compressed ] # In code def extract_knoe_tools(): zip_path = get_resource_path('knoe-app/dist/Knoe Tools.zip') extract_dir = Path(tempfile.mkdtemp()) shutil.unpack_archive(zip_path, extract_dir) return extract_dir / 'Knoe Tools.app' ``` ## References - [PyInstaller Data Files](https://pyinstaller.org/en/stable/spec-files.html#adding-data-files) - [PyInstaller Binaries](https://pyinstaller.org/en/stable/spec-files.html#adding-binaries) - [macOS App Bundle Structure](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html)