๐Ÿ“ฆ Argo Backend PyInstaller Packaging Guide

July 16, 2025 ยท View on GitHub

This directory is used to package the entire backend service into a standalone executable, suitable for local deployment, offline use, desktop integration, etc.


๐Ÿ“ Directory Structure

deploy/
    โ””โ”€โ”€ pyinstaller/
        โ”œโ”€โ”€ argo.spec              # Main PyInstaller entry file
        โ””โ”€โ”€ hooks/                 # Custom hooks (to fix dynamic import issues)
            โ””โ”€โ”€ hooks.py

โœ… Pre-Packaging Preparation

1. Install PyInstaller

Recommended installation via Poetry:

poetry add --dev pyinstaller

Or using system pip:

pip install pyinstaller

2. Environment Preparation

Before packaging, make sure the following are met to ensure the program runs correctly before packaging:

  • โœ… Backend entry is backend/main.py and can start the service successfully

  • โœ… Backend dependencies are installed:

    make install
    
  • โœ… If the project includes frontend code, ensure the frontend has been built (e.g., Vue, React app):

    make build-web
    
  • โœ… The unbundled program runs correctly locally (verify it responds to requests):

    make run
    

๐Ÿ’ก If make run fails or the frontend build doesn't succeed, fix issues before packaging to avoid producing a broken executable.


๐Ÿš€ Quick Build with Make Command

It's recommended to use make build-exe for one-click packaging:

# From project root
make build-exe

Equivalent to manually executing:

cd backend && poetry run pyinstaller ../deploy/pyinstaller/argo_build.spec \
		--distpath ../build/output \
		--workpath ../build

๐Ÿงน Clean Build Files

Use:

make cleanup clean

This will delete:

  • build/
  • __pycache__/

๐Ÿงฉ Resource Packaging Guide

You can use get_data_files() in utils.py to include resources in the executable:

def get_data_files():
    return [
        ("resources", "backend/build/pyinstaller/resources"),
        ("configs", "backend/configs"),
        ("templates", "backend/templates"),
    ]

These resources will be extracted by PyInstaller at runtime and accessed like this:

from sys import _MEIPASS
import os

resource_path = os.path.join(getattr(sys, "_MEIPASS", "."), "resources", "node", "bin", "node")

๐Ÿ› ๏ธ About Hook Files (hooks/)

The deploy/pyinstaller/hooks/ directory contains runtime and import hook scripts needed for PyInstaller to ensure all dependencies load correctly after packaging.


โœ… 1. Runtime Environment Variable Injection (runtime_env_hook.py)

Since PyInstaller can't read .env files after packaging, it's recommended to inject necessary env variables in a runtime hook.

File path: deploy/pyinstaller/hooks/runtime_env_hook.py

import os
import sys

# Enable or disable features
os.environ["ENABLE_MULTI_USER"] = "false"
os.environ["USE_ARGO_OLLAMA"] = "true"
os.environ["USE_ARGO_TRACKING"] = "true"
os.environ["USE_REMOTE_MODELS"] = "true"

# Bind resources to _MEIPASS
os.environ["HUGGINGFACE_HUB_CACHE"] = os.path.join(sys._MEIPASS, "resources", "huggingface", "hub")
os.environ["TIKTOKEN_CACHE_DIR"] = os.path.join(sys._MEIPASS, "resources", "tiktoken_cache")
os.environ["LLAMA_CPP_LIB_PATH"] = os.path.join(sys._MEIPASS, "llama_cpp", "lib")

# Prevent proxy interference for localhost
os.environ["NO_PROXY"] = "http://127.0.0.1,localhost"

โœ… Enable in .spec:

In Analysis(...) config, add:

runtime_hooks=[
    os.path.join(spec_dir, 'hooks', 'runtime_env_hook.py'),
]

โœ… Run the Executable

Go to the build directory:

cd build/output/argo-darwin_arm64
./argo  # Linux/macOS

# On Windows
argo.exe

๐Ÿงช Debugging Tips

  • Use --clean to avoid cache issues
  • Use --log-level=DEBUG for detailed logs
  • Check _MEIPASS path to ensure resources are copied correctly
  • If import fails, consider adding a hook script for that module

๐Ÿ“Œ Common Issues

Problem DescriptionPossible CauseSolution
โŒ ModuleNotFoundError after startupPyInstaller missed dynamically imported modulesAdd a hook file using collect_submodules to specify hiddenimports
โŒ Missing resources (like provider.yaml, frontend dist)datas not set or files not copiedEnsure .spec uses datas = get_data_files() and resources are included
โŒ Node.js not executablePermissions not setUse os.chmod(path, 0o775) in utils.prepare_node()
โŒ .env not working.env path changes after packagingLoad from ARGO_STORAGE_PATH at runtime or load manually in startup
โŒ llama.cpp library not foundMissing LLAMA_CPP_LIB_PATH env varSet it in hook script and ensure path exists
โŒ Slow or failed buildUnclean cacheRun make cleanup then retry
โŒ HuggingFace models not loadingCache not mappedSet HUGGINGFACE_HUB_CACHE to _MEIPASS/resources/huggingface/hub