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
|
||||||
*.db-journal
|
*.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",
|
"request": "launch",
|
||||||
"python": "${workspaceFolder}/env/Scripts/python.exe",
|
"python": "${workspaceFolder}/env/Scripts/python.exe",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"program": "${workspaceFolder}/run.py",
|
"program": "${workspaceFolder}/app/run.py",
|
||||||
"env": {
|
"env": {
|
||||||
"FLASK_APP": "run.py",
|
"FLASK_APP": "run.py",
|
||||||
"FLASK_ENV": "development",
|
"FLASK_ENV": "development",
|
||||||
|
|
|
||||||
|
|
@ -387,7 +387,7 @@ def regenerate_message():
|
||||||
return jsonify({"error": str(e)}, 500)
|
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)
|
# 1. Создаем экземпляр (обязательно укажите путь к вашей модели Vosk)
|
||||||
voice_inst = VoiceService()
|
voice_inst = VoiceService()
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,13 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(level
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# ----------------------------------------------- Main ------------------------------------------------------------
|
# ----------------------------------------------- Main ------------------------------------------------------------
|
||||||
if __name__ == "__main__":
|
# Создаем явную точку входа
|
||||||
|
def start_app():
|
||||||
# Добавляем папку app в Python path
|
# Добавляем папку app в Python path
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
||||||
from app.api import api, voice_inst, current_obsidian_settings
|
from api import api, voice_inst, current_obsidian_settings
|
||||||
from app.heartbeat_monitor import HeartbeatMonitor
|
from heartbeat_monitor import HeartbeatMonitor
|
||||||
from app.workflows import graph_history_manager
|
from workflows import graph_history_manager
|
||||||
|
|
||||||
logger.info("🚀 Запуск LLM Agent Backend сервера...")
|
logger.info("🚀 Запуск LLM Agent Backend сервера...")
|
||||||
logger.info("📍 API будет доступно на: http://localhost:5000/api")
|
logger.info("📍 API будет доступно на: http://localhost:5000/api")
|
||||||
|
|
@ -33,7 +34,7 @@ if __name__ == "__main__":
|
||||||
logger.info("HeartbeatMonitor запущен.")
|
logger.info("HeartbeatMonitor запущен.")
|
||||||
|
|
||||||
|
|
||||||
from app.heartbeat_monitor import VoiceHeartbeatMonitor
|
from heartbeat_monitor import VoiceHeartbeatMonitor
|
||||||
|
|
||||||
v_monitor = VoiceHeartbeatMonitor(
|
v_monitor = VoiceHeartbeatMonitor(
|
||||||
voice_inst,
|
voice_inst,
|
||||||
|
|
@ -49,3 +50,7 @@ if __name__ == "__main__":
|
||||||
host='localhost',
|
host='localhost',
|
||||||
threaded=True
|
threaded=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Оставляем этот блок для локальных тестов, если запускаете исходник напрямую
|
||||||
|
if __name__ == "__main__":
|
||||||
|
start_app()
|
||||||
|
|
@ -20,7 +20,7 @@ QUEUE_POLLING_INTERVAL = 4
|
||||||
TITLE_TEMPERATURE = 0.1
|
TITLE_TEMPERATURE = 0.1
|
||||||
MAX_TITLE_LENGTH = 100
|
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:
|
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/)
|
|
||||||
|
|
@ -20,3 +20,11 @@ numpy==2.1.0
|
||||||
|
|
||||||
matplotlib==3.8.4
|
matplotlib==3.8.4
|
||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
|
|
||||||
|
|
||||||
|
# обфускация, сборка
|
||||||
|
|
||||||
|
# stickytape # сшивка в один файл?
|
||||||
|
# python-minifier
|
||||||
|
cython
|
||||||
|
setuptools
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
chcp 65001
|
chcp 65001
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
call "env\Scripts\activate.bat"
|
call "env\Scripts\activate.bat"
|
||||||
python run.py
|
python "app/run.py"
|
||||||
pause
|
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