Add build scripts

This commit is contained in:
dimitrievgs 2026-06-06 19:29:23 +03:00
parent c1665f3788
commit f27a84c670
19 changed files with 218 additions and 124 deletions

7
.gitignore vendored
View File

@ -8,6 +8,9 @@ __pycache__
*.db
*.db-journal
build/
audio-logs/
audio-logs/
dist/
*.pyd

2
.vscode/launch.json vendored
View File

@ -7,7 +7,7 @@
"request": "launch",
"python": "${workspaceFolder}/env/Scripts/python.exe",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/run.py",
"program": "${workspaceFolder}/app/run.py",
"env": {
"FLASK_APP": "run.py",
"FLASK_ENV": "development",

View File

@ -387,7 +387,7 @@ def regenerate_message():
return jsonify({"error": str(e)}, 500)
from app.voice_service import VoiceService, VOICE_COMMANDS_RESPONSE_TO_STORE
from voice_service import VoiceService, VOICE_COMMANDS_RESPONSE_TO_STORE
# 1. Создаем экземпляр (обязательно укажите путь к вашей модели Vosk)
voice_inst = VoiceService()

View File

@ -9,12 +9,13 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(level
logger = logging.getLogger(__name__)
# ----------------------------------------------- Main ------------------------------------------------------------
if __name__ == "__main__":
# Создаем явную точку входа
def start_app():
# Добавляем папку app в Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
from app.api import api, voice_inst, current_obsidian_settings
from app.heartbeat_monitor import HeartbeatMonitor
from app.workflows import graph_history_manager
from api import api, voice_inst, current_obsidian_settings
from heartbeat_monitor import HeartbeatMonitor
from workflows import graph_history_manager
logger.info("🚀 Запуск LLM Agent Backend сервера...")
logger.info("📍 API будет доступно на: http://localhost:5000/api")
@ -33,7 +34,7 @@ if __name__ == "__main__":
logger.info("HeartbeatMonitor запущен.")
from app.heartbeat_monitor import VoiceHeartbeatMonitor
from heartbeat_monitor import VoiceHeartbeatMonitor
v_monitor = VoiceHeartbeatMonitor(
voice_inst,
@ -48,4 +49,8 @@ if __name__ == "__main__":
port=5000,
host='localhost',
threaded=True
)
)
# Оставляем этот блок для локальных тестов, если запускаете исходник напрямую
if __name__ == "__main__":
start_app()

View File

@ -20,7 +20,7 @@ QUEUE_POLLING_INTERVAL = 4
TITLE_TEMPERATURE = 0.1
MAX_TITLE_LENGTH = 100
from app.voice_service import VOICE_COMMANDS_RESPONSE_TO_STORE
from voice_service import VOICE_COMMANDS_RESPONSE_TO_STORE
class TitleGenerator:
"""Сервис для асинхронной генерации заголовков."""

28
build.bat Normal file
View File

@ -0,0 +1,28 @@
@echo off
chcp 65001 > nul
:: 1. Включаем виртуальное окружение перед стартом
if exist env\Scripts\activate.bat (
echo 🔌 Активация виртуального окружения env...
call env\Scripts\activate.bat
) else (
echo ⚠️ Предупреждение: Папка окружения env\Scripts\activate.bat не найдена!
echo Попытка сборки в глобальном окружении...
)
:: 2. Запуск компиляции (скрипт сам создаст папку dist)
python build/compile.py build_ext --inplace
:: 3. Сохранение зависимостей в папку dist
pip freeze > dist/requirements_all.txt
:: 4. Копирование деплой-скрипта из папки build в папку dist
if exist build\deploy.bat (
copy build\deploy.bat dist\deploy.bat /y
echo ✅ Файл deploy.bat успешно скопирован в dist/
) else (
echo ⚠️ Предупреждение: Файл build\deploy.bat не найден!
)
echo ===== СБОРКА ПОЛНОСТЬЮ ЗАВЕРШЕНА =====
pause

93
build/compile.py Normal file
View File

@ -0,0 +1,93 @@
import os
import shutil
from setuptools import setup, Extension
from Cython.Build import cythonize
def build_project():
# 1. Сначала определяем все абсолютные пути, пока мы в исходной директории
build_dir = os.path.dirname(os.path.abspath(__file__)) # папка build/
project_root = os.path.abspath(os.path.join(build_dir, "..")) # корень проекта
source_app = os.path.join(project_root, "app")
dist_dir = os.path.join(project_root, "dist")
dist_app = os.path.join(dist_dir, "app")
build_main_file = os.path.join(build_dir, "main.py")
dist_main_file = os.path.join(dist_dir, "main.py")
print("🚀 Старт сборки проекта...")
# 2. Пересоздаем чистую папку dist/
if os.path.exists(dist_dir):
shutil.rmtree(dist_dir)
os.makedirs(dist_dir)
# 3. Копируем исходный код app/ в dist/app/
shutil.copytree(source_app, dist_app)
# Сохраняем текущую директорию корня, чтобы вернуться в неё в конце
original_cwd = os.getcwd()
# СИСТЕМНЫЙ СДВИГ: Переносим рабочую директорию процесса внутрь dist/app/
# Теперь для компилятора корнем является именно dist/app/
os.chdir(dist_app)
# 4. Собираем модули для компиляции (сканируем текущую папку, так как мы уже внутри dist/app)
extensions = []
for root, _, files in os.walk("."):
for file in files:
if file.endswith('.py') and not file.startswith('__'):
full_path = os.path.join(root, file)
# Вычисляем путь относительно dist/app
clean_path = os.path.relpath(full_path, ".")
# Формируем имя модуля для Python (например, workflows.graph_manager)
module_name = clean_path.replace(os.sep, '.').rstrip('.py')
if module_name == "run":
extensions.append(Extension("run", [clean_path]))
else:
extensions.append(Extension(module_name, [clean_path]))
# 5. Запускаем компиляцию (теперь --inplace положит бинарники строго в dist/app/)
setup(
name="AppBinary",
script_args=["build_ext", "--inplace"],
ext_modules=cythonize(
extensions,
language_level="3",
compiler_directives={
'annotation_typing': False, # Отключаем строгую типизацию (фиксит max_tokens)
'binding': True, # Сохраняем Python-совместимость методов
'always_allow_keywords': True # Разрешаем передачу None в именованные аргументы
}
),
)
# 6. Очищаем dist/app/ от исходного кода
print("\n🧹 Удаление исходного кода из папки dist/app/...")
for root, _, files in os.walk("."):
for file in files:
if (file.endswith('.py') and not file.startswith('__')) or file.endswith('.c'):
try:
os.remove(os.path.join(root, file))
except OSError:
pass
# Удаляем временную папку сборки компилятора C++
if os.path.exists("build"):
shutil.rmtree("build")
# Возвращаем рабочую директорию обратно в корень проекта
os.chdir(original_cwd)
# 7. Копируем файл build/main.py в dist/main.py
if os.path.exists(build_main_file):
shutil.copy(build_main_file, dist_main_file)
print("📄 Файл build/main.py успешно скопирован в dist/main.py")
else:
print("⚠️ Ошибка: Файл build/main.py не найден, нечего копировать!")
print("\n✅ Сборка успешно завершена! Папка dist/ готова к деплою.")
if __name__ == "__main__":
build_project()

56
build/deploy.bat Normal file
View File

@ -0,0 +1,56 @@
@echo off
chcp 65001 > nul
echo ===================================================
echo 🚀 Начинаем автоматическое развертывание проекта...
echo ===================================================
:: 1. Проверяем, установлен ли Python в системе
python --version >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Ошибка: Python не найден в системе!
echo Установите Python той же мажорной версии, на которой собирался проект.
goto error
)
:: 2. Создаем изолированное виртуальное окружение prod_env
if not exist prod_env (
echo 📦 Создание виртуального окружения prod_env...
python -m venv prod_env
if %errorlevel% neq 0 (
echoНе удалось создать виртуальное окружение!
goto error
)
echo ✅ Окружение prod_env успешно создано.
) else (
echo Окружение prod_env уже существует, пропускаем создание.
)
:: 3. Обновляем pip и устанавливаем зависимости в prod_env без его активации
echo 🗂️ Установка внешних библиотек из requirements_all.txt...
prod_env\Scripts\python.exe -m pip install --upgrade pip > nul
prod_env\Scripts\python.exe -m pip install -r requirements_all.txt
if %errorlevel% neq 0 (
echo ❌ Ошибка при установке зависимостей! Проверьте интернет-соединение.
goto error
)
echoВсе внешние библиотеки успешно установлены.
:: 4. Формируем удобный ярлык/скрипт для быстрого запуска сервера в будущем
echo @echo off > run_server.bat
echo chcp 65001 ^> nul >> run_server.bat
echo echo 🔌 Запуск защищенного бэкенд сервера... >> run_server.bat
echo prod_env\Scripts\python.exe main.py >> run_server.bat
echo pause >> run_server.bat
echo ===================================================
echo 🎉 ДЕПЛОЙ УСПЕШНО ЗАВЕРШЕН!
echo ===================================================
echo 💡 Для запуска сервера используйте созданный файл: run_server.bat
echo ===================================================
pause
exit /b 0
:error
echo 🟥 Деплой завершился с ошибкой!
pause
exit /b 1

12
build/main.py Normal file
View File

@ -0,0 +1,12 @@
import sys
import os
if __name__ == "__main__":
# Добавляем папку app в пути поиска, как в вашей оригинальной «голове»
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app'))
# Python сам найдет файл run.cp311-win_amd64.pyd и импортирует его
import run
# Запускаем сервер
run.start_app()

Binary file not shown.

View File

@ -1,65 +0,0 @@
Для создания исполняемого файла (.exe), который запускается без установки, с обфусцированным кодом Python лучше всего использовать связку PyArmor + PyInstaller:
1. Обфускация с PyArmor:
- PyArmor шифрует и обфусцирует исходный Python-код, усложняя его анализ и обратную разработку.
- Используется командой:
```
pyarmor gen -O dist --recursive путь/к/вашему/проекту
```
или
```
pyarmor gen -O dist --recursive .
```
- Это создаст обфусцированные версии скриптов с сохранением структуры.
2. Упаковка в exe с PyInstaller:
- После обфускации используйте PyInstaller, чтобы упаковать проект в единый exe-файл с параметром `--onefile`:
```
pyinstaller --onefile --noconsole ваш_главный_скрипт.py
```
? python -m PyInstaller --onefile --noconsole run.py
? pyinstaller --onefile --noconsole --paths=./app run.py
? pyinstaller --onefile --hidden-import=eventlet.green --hidden-import=eventlet.green.thread --hidden-import=engineio.async_eventlet --hidden-import=engineio.async_drivers.eventlet --noconsole --paths=./app run.py
- Если нужно, добавляйте параметры `--add-data` для ресурсов.
3. Итог:
- Итоговый .exe-файл будет содержать интерпретатор Python, все зависимости и обфусцированный код.
- Запуск exe не требует предварительной установки Python или зависимостей.
- Такой файл удобно выкладывать в релизы GitHub и распространять пользователям.
4. Важные советы:
- Всегда тестируйте exe на чистой системе.
- PyArmor даёт хорошую защиту, но не абсолютную.
- Для повышения безопасности используйте активацию лицензий PyArmor (опционно).
- Настройте spec-файл PyInstaller, чтобы включить все необходимые модули и ресурсы.
Пример типичного workflow:
```bash
# Обфускация
pyarmor obfuscate --recursive your_project/
# Переход в обфусцированную папку
cd dist/your_project
# Создание exe
pyinstaller --onefile --noconsole main.py
```
Тогда в папке `dist` появится самодостаточный исполняемый файл.
Таким образом, PyArmor + PyInstaller позволяет создать exe с обфусцированным кодом, который не требует установки Python и готов к распространению через GitHub релизы.[1][7][9]
[1](https://pressanybutton.ru/post/poleznye-instrumenty/python-proektav-exe-fajl-i-obfuskaciya-koda/)
[2](https://www.youtube.com/watch?v=15ho_Zg_Kq4)
[3](https://www.youtube.com/watch?v=HtiYdCk0_J8)
[4](https://www.reddit.com/r/learnpython/comments/144tdhy/obfuscating_python_script/)
[5](https://stackoverflow.com/questions/58334936/pack-a-pyarmor-obfuscated-script-to-exe)
[6](https://qna.habr.com/q/1055278)
[7](https://pyarmor.readthedocs.io/en/v5.7.0/pack-obfuscated-scripts.html)
[8](https://github.com/dashingsoft/pyarmor)
[9](https://www.cyberforum.ru/python-ide/thread3151287.html)
[10](https://www.reddit.com/r/learnpython/comments/1bdsnce/which_one_is_best_for_code_obfuscation/)

View File

@ -19,4 +19,12 @@ sounddevice==0.5.1
numpy==2.1.0
matplotlib==3.8.4
pandas==2.2.2
pandas==2.2.2
# обфускация, сборка
# stickytape # сшивка в один файл?
# python-minifier
cython
setuptools

View File

@ -1,5 +1,5 @@
chcp 65001
cd /d "%~dp0"
call "env\Scripts\activate.bat"
python run.py
python "app/run.py"
pause

View File

@ -1,38 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['run.py'],
pathex=['./app'],
binaries=[],
datas=[],
hiddenimports=['eventlet.green', 'eventlet.green.thread', 'engineio.async_eventlet', 'engineio.async_drivers.eventlet'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='run',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

@ -1,8 +0,0 @@
// Добавьте этот файл, если его нет
interface Window {
electron?: {
shell: {
openPath: (path: string) => Promise<string>;
};
};
}