prole/docs/EMBEDDED-RESOURCES.md

7.6 KiB

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:

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:

bg_path = get_resource_path('img/knoeLogoSepia.png')
if bg_path.exists():
    image = Image.open(str(bg_path))

Binary Execution:

scan_binary = get_resource_path("scan/network-agent")
if scan_binary.exists():
    process = subprocess.Popen([str(scan_binary)], ...)

App Bundle Access:

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:

datas = [
    ('installer', 'installer'),
    ('conf', 'conf'),
    ('etc', 'etc'),
    ('img', 'img'),
    ('docs', 'docs'),
    ('knoe-app/dist/Knoe Tools.app', 'knoe-app/dist/Knoe Tools.app'),
]

Binaries:

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:

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:

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

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:

# 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:

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:

    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:

    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<random>/
  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:

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:

# 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:

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:

# 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