Add build scripts
This commit is contained in:
parent
c1665f3788
commit
f27a84c670
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -8,6 +8,9 @@ __pycache__
|
|||
*.db
|
||||
*.db-journal
|
||||
|
||||
build/
|
||||
audio-logs/
|
||||
|
||||
audio-logs/
|
||||
dist/
|
||||
|
||||
|
||||
*.pyd
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
28
build.bat
Normal 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
93
build/compile.py
Normal 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
56
build/deploy.bat
Normal 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
12
build/main.py
Normal 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()
|
||||
BIN
geckodriver.exe
BIN
geckodriver.exe
Binary file not shown.
|
|
@ -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/)
|
||||
|
|
@ -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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
chcp 65001
|
||||
cd /d "%~dp0"
|
||||
call "env\Scripts\activate.bat"
|
||||
python run.py
|
||||
python "app/run.py"
|
||||
pause
|
||||
38
run.spec
38
run.spec
|
|
@ -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,
|
||||
)
|
||||
8
src/types/electron.d.ts
vendored
8
src/types/electron.d.ts
vendored
|
|
@ -1,8 +0,0 @@
|
|||
// Добавьте этот файл, если его нет
|
||||
interface Window {
|
||||
electron?: {
|
||||
shell: {
|
||||
openPath: (path: string) => Promise<string>;
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user