правки для настройки локальных nats smb minio
This commit is contained in:
parent
72b32d790f
commit
8b83f155c2
1
.gitignore
vendored
1
.gitignore
vendored
@ -322,6 +322,7 @@ dist
|
|||||||
|
|
||||||
# Custom ignore
|
# Custom ignore
|
||||||
.local/
|
.local/
|
||||||
|
/dev-servers/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
# MinIO на Mac (Apple Silicon)
|
|
||||||
|
|
||||||
MinIO запускается отдельным процессом, данные сохраняются в `.local/minio/data`
|
|
||||||
в корне проекта. Папка `.local/` и `api/.env` исключены из Git.
|
|
||||||
|
|
||||||
## Запуск
|
|
||||||
|
|
||||||
Из папки `api`, в активированном окружении:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python run_minio.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Остановка — Ctrl+C. Данные сохраняются после перезапуска.
|
|
||||||
|
|
||||||
При первоначальной настройке сервер уже запущен в фоне. Его журнал находится
|
|
||||||
в `.local/minio/server.log`, PID — в `.local/minio/minio.pid` (пути от корня
|
|
||||||
проекта). Не запускай второй экземпляр на тех же портах. Фоновый процесс не
|
|
||||||
настроен на автоматический запуск после перезагрузки Mac.
|
|
||||||
|
|
||||||
- S3 API: http://127.0.0.1:9000
|
|
||||||
- Консоль: http://127.0.0.1:9001
|
|
||||||
- Бакет: `dkb-files`
|
|
||||||
- Логин и пароль: `OPENBAO__MINIO__MINIO_ADMIN_LOGIN` и
|
|
||||||
`OPENBAO__MINIO__MINIO_ADMIN_PASSWORD` в `api/.env`.
|
|
||||||
|
|
||||||
Названия переменных окружения регистрозависимы; используй написание из примеров.
|
|
||||||
|
|
||||||
MinIO слушает только локальный интерфейс. Для локального HTTP в `.env` задано:
|
|
||||||
|
|
||||||
```dotenv
|
|
||||||
OPENBAO__MINIO__MINIO_URL=127.0.0.1:9000
|
|
||||||
OPENBAO__MINIO__MINIO_BUCKET=dkb-files
|
|
||||||
OPENBAO__MINIO__MINIO_SECURE=false
|
|
||||||
```
|
|
||||||
|
|
||||||
Адрес указывается без `http://`. Для TLS используется `MINIO_SECURE=true`
|
|
||||||
(значение по умолчанию).
|
|
||||||
|
|
||||||
После запуска MinIO запусти API в другом терминале:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uvicorn src.app:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
MinIO работает независимо от NATS и SMB. Загруженные файлы сохраняются, но
|
|
||||||
задания распознавания остаются в очереди до настройки остальных интеграций.
|
|
||||||
|
|
||||||
## Повторная сборка бинарника
|
|
||||||
|
|
||||||
Из корня проекта, после установки Go (`brew install go`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p .local/minio/bin
|
|
||||||
GOBIN="$PWD/.local/minio/bin" go install github.com/minio/minio@RELEASE.2025-10-15T17-29-55Z
|
|
||||||
```
|
|
||||||
|
|
||||||
Исходники: https://github.com/minio/minio
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
"""Run local MinIO on macOS: python run_minio.py (Ctrl+C to stop)."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from dotenv import dotenv_values
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
api_dir = Path(__file__).resolve().parent
|
|
||||||
local_dir = api_dir.parent / ".local" / "minio"
|
|
||||||
binary = local_dir / "bin" / "minio"
|
|
||||||
if not binary.is_file():
|
|
||||||
raise SystemExit("MinIO не собран. См. LOCAL_MINIO.md")
|
|
||||||
values = {**dotenv_values(api_dir / ".env"), **os.environ}
|
|
||||||
prefix = "OPENBAO__MINIO__"
|
|
||||||
credentials = [prefix + "MINIO_ADMIN_LOGIN", prefix + "MINIO_ADMIN_PASSWORD"]
|
|
||||||
if any(not values.get(key) for key in credentials):
|
|
||||||
raise SystemExit("Укажи логин и пароль MinIO в api/.env")
|
|
||||||
data_dir = local_dir / "data"
|
|
||||||
data_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
env = dict(os.environ)
|
|
||||||
env.update(
|
|
||||||
MINIO_ROOT_USER=values[credentials[0]],
|
|
||||||
MINIO_ROOT_PASSWORD=values[credentials[1]],
|
|
||||||
)
|
|
||||||
os.execve(binary, [str(binary), "server", str(data_dir),
|
|
||||||
"--address", "127.0.0.1:9000",
|
|
||||||
"--console-address", "127.0.0.1:9001"], env)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -24,8 +24,8 @@ from src.core.clients import S3Storage, PollWorker
|
|||||||
from src.core.async_cache import AsyncInMemoryCache
|
from src.core.async_cache import AsyncInMemoryCache
|
||||||
# from src.core.logger import setup_logger
|
# from src.core.logger import setup_logger
|
||||||
from nats.aio.client import Client as NATS
|
from nats.aio.client import Client as NATS
|
||||||
import nkeys
|
|
||||||
import base64
|
import base64
|
||||||
|
import nkeys
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -42,18 +42,6 @@ VERSION = "1.0.0"
|
|||||||
UP_TIME = datetime.now(timezone.utc)
|
UP_TIME = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def jwt_cb():
|
|
||||||
return settings.OPENBAO.NATS.NATS_JWT.encode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def sig_cb(nonce):
|
|
||||||
if (isinstance(nonce, str)):
|
|
||||||
nonce = nonce.encode('utf-8')
|
|
||||||
kp = nkeys.from_seed(settings.OPENBAO.NATS.NATS_SEED.encode('utf-8'))
|
|
||||||
signed = kp.sign(nonce)
|
|
||||||
return base64.b64encode(signed)
|
|
||||||
|
|
||||||
|
|
||||||
async def sub_cb(msg):
|
async def sub_cb(msg):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -110,22 +98,35 @@ async def lifespan(app):
|
|||||||
app.state.storage = S3Storage(s3_client, minio.MINIO_BUCKET)
|
app.state.storage = S3Storage(s3_client, minio.MINIO_BUCKET)
|
||||||
logger.info("MinIO подключён, бакет: {}", minio.MINIO_BUCKET)
|
logger.info("MinIO подключён, бакет: {}", minio.MINIO_BUCKET)
|
||||||
|
|
||||||
|
smb = settings.OPENBAO.SMB
|
||||||
|
if smb is not None:
|
||||||
|
smbclient.register_session(smb.SMB_HOST, smb.SMB_LOGIN, smb.SMB_PASSWORD,
|
||||||
|
port=smb.SMB_PORT)
|
||||||
|
smb_registered = True
|
||||||
|
logger.info("SMB подключён")
|
||||||
|
|
||||||
|
nats_settings = settings.OPENBAO.NATS
|
||||||
|
if nats_settings is not None:
|
||||||
|
nc = NATS()
|
||||||
|
app.state.nats = nc
|
||||||
|
if not settings.DEBUG:
|
||||||
|
keypair = nkeys.from_seed(nats_settings.NATS_SEED.encode("utf-8"))
|
||||||
|
|
||||||
|
def user_jwt_cb():
|
||||||
|
return nats_settings.NATS_JWT.encode("utf-8")
|
||||||
|
|
||||||
|
def signature_cb(nonce):
|
||||||
|
return base64.b64encode(keypair.sign(nonce.encode("utf-8")))
|
||||||
|
|
||||||
|
await nc.connect(nats_settings.NATS_URL, user_jwt_cb=user_jwt_cb,
|
||||||
|
signature_cb=signature_cb, max_reconnect_attempts=2)
|
||||||
|
logger.info("NATS подключён")
|
||||||
|
|
||||||
missing = [name for name in ("MINIO", "NATS", "SMB")
|
missing = [name for name in ("MINIO", "NATS", "SMB")
|
||||||
if getattr(settings.OPENBAO, name) is None]
|
if getattr(settings.OPENBAO, name) is None]
|
||||||
if missing:
|
if missing:
|
||||||
logger.info("Распознавание файлов отключено: не настроены {}", ", ".join(missing))
|
logger.info("Распознавание файлов отключено: не настроены {}", ", ".join(missing))
|
||||||
else:
|
else:
|
||||||
smb = settings.OPENBAO.SMB
|
|
||||||
smbclient.register_session(smb.SMB_HOST, smb.SMB_LOGIN, smb.SMB_PASSWORD)
|
|
||||||
smb_registered = True
|
|
||||||
nc = NATS()
|
|
||||||
app.state.nats = nc
|
|
||||||
if not settings.DEBUG:
|
|
||||||
await nc.connect(
|
|
||||||
settings.OPENBAO.NATS.NATS_URL,
|
|
||||||
user_jwt_cb=jwt_cb, signature_cb=sig_cb,
|
|
||||||
max_reconnect_attempts=2,
|
|
||||||
)
|
|
||||||
cache = AsyncInMemoryCache()
|
cache = AsyncInMemoryCache()
|
||||||
app.state.poller = PollWorker(nc, cache, app.state.storage)
|
app.state.poller = PollWorker(nc, cache, app.state.storage)
|
||||||
await app.state.poller.start()
|
await app.state.poller.start()
|
||||||
@ -138,10 +139,11 @@ async def lifespan(app):
|
|||||||
for worker in (app.state.poller, app.state.pdf_poller):
|
for worker in (app.state.poller, app.state.pdf_poller):
|
||||||
if worker is not None:
|
if worker is not None:
|
||||||
await worker.stop()
|
await worker.stop()
|
||||||
for sub in app.state.nats_subs:
|
if not settings.DEBUG:
|
||||||
await sub.unsubscribe()
|
for sub in app.state.nats_subs:
|
||||||
if app.state.nats is not None:
|
await sub.unsubscribe()
|
||||||
await app.state.nats.close()
|
if app.state.nats is not None:
|
||||||
|
await app.state.nats.close()
|
||||||
if s3_client is not None:
|
if s3_client is not None:
|
||||||
await s3_client.close_session()
|
await s3_client.close_session()
|
||||||
if smb_registered:
|
if smb_registered:
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class PDFPollWorker:
|
|||||||
logger.exception("Не получилось достать данные из кэша")
|
logger.exception("Не получилось достать данные из кэша")
|
||||||
jobs = []
|
jobs = []
|
||||||
|
|
||||||
all_files = smbclient.listdir(self.smb_path)
|
all_files = smbclient.listdir(self.smb_path, port=settings.OPENBAO.SMB.SMB_PORT)
|
||||||
|
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
search_fname = f"{job.file_id}"
|
search_fname = f"{job.file_id}"
|
||||||
@ -85,8 +85,9 @@ class PDFPollWorker:
|
|||||||
|
|
||||||
logger.info("Начинаю обработку файла по пути smb_dir={smb_dir}", smb_dir=new_dir)
|
logger.info("Начинаю обработку файла по пути smb_dir={smb_dir}", smb_dir=new_dir)
|
||||||
files = []
|
files = []
|
||||||
for fname in smbclient.listdir(new_dir):
|
for fname in smbclient.listdir(new_dir, port=settings.OPENBAO.SMB.SMB_PORT):
|
||||||
with smbclient.open_file(f'{new_dir}/{fname}', 'r') as f:
|
with smbclient.open_file(f'{new_dir}/{fname}', 'r',
|
||||||
|
port=settings.OPENBAO.SMB.SMB_PORT) as f:
|
||||||
files.append(json.loads(f.read()))
|
files.append(json.loads(f.read()))
|
||||||
s3_json_dict = {
|
s3_json_dict = {
|
||||||
'file_id': job.file_id,
|
'file_id': job.file_id,
|
||||||
|
|||||||
@ -108,7 +108,8 @@ class PollWorker:
|
|||||||
|
|
||||||
if row['subject'] == 'file.img_uploaded':
|
if row['subject'] == 'file.img_uploaded':
|
||||||
last_part = row['row_payload']['s3_key'].split('/')[-1]
|
last_part = row['row_payload']['s3_key'].split('/')[-1]
|
||||||
with smbclient.open_file(f'{settings.OPENBAO.SMB.SMB_PATH}/Файлы/{last_part}', 'wb') as f:
|
with smbclient.open_file(f'{settings.OPENBAO.SMB.SMB_PATH}/Файлы/{last_part}', 'wb',
|
||||||
|
port=settings.OPENBAO.SMB.SMB_PORT) as f:
|
||||||
data = await self.storage.get_object(row['row_payload']['s3_key'])
|
data = await self.storage.get_object(row['row_payload']['s3_key'])
|
||||||
content = await data.read()
|
content = await data.read()
|
||||||
f.write(content)
|
f.write(content)
|
||||||
@ -177,4 +178,4 @@ class PollWorker:
|
|||||||
file_stmt = update(StoredFile).where(StoredFile.current_recognition_job_id == row_id).values(status=None)
|
file_stmt = update(StoredFile).where(StoredFile.current_recognition_job_id == row_id).values(status=None)
|
||||||
await session.execute(stmt)
|
await session.execute(stmt)
|
||||||
await session.execute(file_stmt)
|
await session.execute(file_stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
|
||||||
class DBSettings(BaseSettings):
|
class DBSettings(BaseSettings):
|
||||||
@ -13,11 +14,12 @@ class MinioSettings(BaseSettings):
|
|||||||
|
|
||||||
class NATSSettings(BaseSettings):
|
class NATSSettings(BaseSettings):
|
||||||
NATS_URL: str
|
NATS_URL: str
|
||||||
NATS_SEED: str
|
NATS_JWT: str = Field(min_length=1)
|
||||||
NATS_JWT: str
|
NATS_SEED: str = Field(min_length=1)
|
||||||
|
|
||||||
class SMBSettings(BaseSettings):
|
class SMBSettings(BaseSettings):
|
||||||
SMB_HOST: str
|
SMB_HOST: str
|
||||||
|
SMB_PORT: int = 445
|
||||||
SMB_PATH: str
|
SMB_PATH: str
|
||||||
SMB_LOGIN: str
|
SMB_LOGIN: str
|
||||||
SMB_PASSWORD: str
|
SMB_PASSWORD: str
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user