t-261002193628-1d1e5f: установка одной командой + визард первого запуска
- scripts/install.sh: clone → venv → deps → обёртка ~/.local/bin/simbiot (чистый env, как hermes) → тихий init/визард → проверка simbiot status; флаги --dir/--repo/--wizard/--no-wizard/--systemd/-q - cli.py wizard: интерактивный конфиг .env (TG-токен, чаты, ключ 9router, модель SIMBIOT_MODEL) + init стора; --noninteractive и флаги - harness/levels.py: SIMBIOT_MODEL переопределяет модель уровней (env/.env) - requirements.txt: pinned-зависимости для установщика - scripts/simbiot-harness.service: systemd-юнит (опция --systemd) - README.md: секция «Установка одной командой»
This commit is contained in:
parent
e435289e55
commit
4c4ac34560
6 changed files with 360 additions and 0 deletions
24
README.md
24
README.md
|
|
@ -3,6 +3,30 @@
|
||||||
Ритмичный агент: живёт тактами, как человек. CQRS + Event Sourcing на спине,
|
Ритмичный агент: живёт тактами, как человек. CQRS + Event Sourcing на спине,
|
||||||
Strands Harness SDK как исполнительное ядро, модели через 9router.
|
Strands Harness SDK как исполнительное ядро, модели через 9router.
|
||||||
|
|
||||||
|
## Установка одной командой (t-261002193628-1d1e5f, как hermes)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.cil.by/simbiot/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Что делает `scripts/install.sh`: клонирует репо в `~/simbiot-harness`,
|
||||||
|
создаёт venv (Python 3.11+), ставит зависимости, кладёт обёртку
|
||||||
|
`~/.local/bin/simbiot` (чистый env: unset PYTHONPATH/PYTHONHOME →
|
||||||
|
`exec venv/bin/python cli.py`), тихо инициализирует стор и проверяет
|
||||||
|
`simbiot status`. Флаги: `--dir DIR`, `--repo URL`, `--wizard` /
|
||||||
|
`--no-wizard`, `--systemd` (юнит, только root), `-q` (тихо).
|
||||||
|
|
||||||
|
Первый запуск — визард настройки (токен TG-бота, ключ 9router, модель):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
simbiot wizard # интерактивно
|
||||||
|
simbiot wizard --noninteractive --router-key KEY --model auto
|
||||||
|
```
|
||||||
|
|
||||||
|
`.env` в корне репо читается стартапом (SIMBIOT_TG_TOKEN,
|
||||||
|
SIMBIOT_TG_ALLOWED, ROUTER_KEY, SIMBIOT_MODEL и др.); в systemd-режиме
|
||||||
|
`scripts/simbiot-harness.service` подхватывает его сам.
|
||||||
|
|
||||||
## Главная цель (фиксация Alex, 2026-09-30)
|
## Главная цель (фиксация Alex, 2026-09-30)
|
||||||
|
|
||||||
Метрика харнеса — **максимизация долгосрочной AUM** (доход/ресурс всех
|
Метрика харнеса — **максимизация долгосрочной AUM** (доход/ресурс всех
|
||||||
|
|
|
||||||
128
cli.py
128
cli.py
|
|
@ -121,6 +121,21 @@ def main():
|
||||||
p_init = sub.add_parser("init", help="инициализация стора")
|
p_init = sub.add_parser("init", help="инициализация стора")
|
||||||
p_init.add_argument("--identity", default="Simbiot Harness — ритмичный агент кооператива")
|
p_init.add_argument("--identity", default="Simbiot Harness — ритмичный агент кооператива")
|
||||||
|
|
||||||
|
# Визард первого запуска (t-261002193628-1d1e5f, аналогично hermes):
|
||||||
|
# интерактивный конфиг .env (токен TG, ключ 9router, модель) + init стора.
|
||||||
|
p_wiz = sub.add_parser("wizard", help="визард первого запуска: настройка .env и стора")
|
||||||
|
p_wiz.add_argument("--noninteractive", action="store_true",
|
||||||
|
help="без вопросов: значения из ENV/--флагов, init без TG-настройки")
|
||||||
|
p_wiz.add_argument("--tg-token", default=None, help="токен TG-бота (SIMBIOT_TG_TOKEN)")
|
||||||
|
p_wiz.add_argument("--tg-allowed", default=None,
|
||||||
|
help="разрешённые чат-ids через запятую (SIMBIOT_TG_ALLOWED)")
|
||||||
|
p_wiz.add_argument("--router-key", default=None, help="ключ 9router (ROUTER_KEY)")
|
||||||
|
p_wiz.add_argument("--router-base-url", default=None,
|
||||||
|
help="база 9router (ROUTER_BASE_URL, по умолчанию http://127.0.0.1:20128/v1)")
|
||||||
|
p_wiz.add_argument("--model", default=None,
|
||||||
|
help="модель уровней (по умолчанию auto — как в harness/levels.py)")
|
||||||
|
p_wiz.add_argument("--no-init", action="store_true", help="не инициализировать стор")
|
||||||
|
|
||||||
p_rag = sub.add_parser("rag-query", help="семантический поиск по базе кооператива (вендор /root/rag)")
|
p_rag = sub.add_parser("rag-query", help="семантический поиск по базе кооператива (вендор /root/rag)")
|
||||||
p_rag.add_argument("question")
|
p_rag.add_argument("question")
|
||||||
p_rag.add_argument("-k", type=int, default=5, help="сколько чанков (<=5)")
|
p_rag.add_argument("-k", type=int, default=5, help="сколько чанков (<=5)")
|
||||||
|
|
@ -133,6 +148,9 @@ def main():
|
||||||
es.init_db()
|
es.init_db()
|
||||||
commands.set_policy(identity=args.identity)
|
commands.set_policy(identity=args.identity)
|
||||||
print("OK: стор инициализирован, политика записана")
|
print("OK: стор инициализирован, политика записана")
|
||||||
|
|
||||||
|
elif args.cmd == "wizard":
|
||||||
|
_run_wizard(args)
|
||||||
elif args.cmd == "task":
|
elif args.cmd == "task":
|
||||||
tid, size = commands.submit_task(args.title, args.details, args.minutes)
|
tid, size = commands.submit_task(args.title, args.details, args.minutes)
|
||||||
if args.await_delivery:
|
if args.await_delivery:
|
||||||
|
|
@ -416,5 +434,115 @@ def main():
|
||||||
print(f"{ev['id']:4} {ev['timestamp'][11:19]} {ev['aggregate']:8} {ev['event_type']:16} {d}")
|
print(f"{ev['id']:4} {ev['timestamp'][11:19]} {ev['aggregate']:8} {ev['event_type']:16} {d}")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Визард первого запуска (t-261002193628-1d1e5f, аналогично hermes): конфиг
|
||||||
|
# .env атомарно (0600) + init стора. Дополнен шагом «модель» — она реально
|
||||||
|
# переключает уровни через SIMBIOT_MODEL (env-оверрайд в harness/levels.py).
|
||||||
|
# ============================================================================
|
||||||
|
def _mask(v: str) -> str:
|
||||||
|
v = (v or "").strip()
|
||||||
|
return "(не задано)" if not v else (v[:4] + "…" + v[-4:] if len(v) > 12 else "***")
|
||||||
|
|
||||||
|
|
||||||
|
def _dotenv_get(env_path: str, key: str) -> str:
|
||||||
|
"""Значение key из .env (env процесса сильнее, как в runtime._env_or_file)."""
|
||||||
|
val = os.environ.get(key, "")
|
||||||
|
if val:
|
||||||
|
return val
|
||||||
|
try:
|
||||||
|
with open(env_path, encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith(key + "="):
|
||||||
|
return line.split("=", 1)[1].strip().strip('"').strip("'")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _dotenv_set(env_path: str, key: str, value: str) -> None:
|
||||||
|
"""Атомарная запись key=value в .env: обновить/добавить, остальное — как было."""
|
||||||
|
lines = []
|
||||||
|
if os.path.exists(env_path):
|
||||||
|
with open(env_path, encoding="utf-8") as f:
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
out, replaced = [], False
|
||||||
|
for ln in lines:
|
||||||
|
if ln.startswith(key + "="):
|
||||||
|
if not replaced:
|
||||||
|
out.append(f"{key}={value}")
|
||||||
|
replaced = True # повторные вхождения key выкидываем
|
||||||
|
else:
|
||||||
|
out.append(ln)
|
||||||
|
if not replaced:
|
||||||
|
out.append(f"{key}={value}")
|
||||||
|
tmp = env_path + ".tmp"
|
||||||
|
with open(tmp, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(out).rstrip("\n") + "\n")
|
||||||
|
os.replace(tmp, env_path)
|
||||||
|
os.chmod(env_path, 0o600)
|
||||||
|
|
||||||
|
|
||||||
|
def _ask(prompt: str, current: str, secret: bool = False) -> str:
|
||||||
|
"""Ответ пользователя; Enter = оставить текущее; EOF (не-tty) = ничего."""
|
||||||
|
hint = _mask(current) if secret else (current if current else "пусто")
|
||||||
|
try:
|
||||||
|
raw = input(f"{prompt} [{hint}]: ").strip()
|
||||||
|
except EOFError:
|
||||||
|
raw = ""
|
||||||
|
return raw if raw else current
|
||||||
|
|
||||||
|
|
||||||
|
def _tg_selftest(token: str) -> str:
|
||||||
|
"""getMe-самотест (только проверка токена, ничего не отправляет)."""
|
||||||
|
if not token:
|
||||||
|
return ""
|
||||||
|
import urllib.request
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(
|
||||||
|
f"https://api.telegram.org/bot{token}/getMe", timeout=5) as r:
|
||||||
|
who = json.loads(r.read().decode()).get("result", {}).get("username")
|
||||||
|
return f"bot @{who}" if who else "ok"
|
||||||
|
except Exception as e:
|
||||||
|
return f"ОШИБКА ({e.__class__.__name__}) — токен не принят, но запись в .env сделал"
|
||||||
|
|
||||||
|
|
||||||
|
def _run_wizard(args) -> None:
|
||||||
|
env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
||||||
|
vals = {
|
||||||
|
"SIMBIOT_TG_TOKEN": args.tg_token if args.tg_token is not None else None,
|
||||||
|
"SIMBIOT_TG_ALLOWED": args.tg_allowed,
|
||||||
|
"ROUTER_KEY": args.router_key,
|
||||||
|
"ROUTER_BASE_URL": args.router_base_url,
|
||||||
|
"SIMBIOT_MODEL": args.model,
|
||||||
|
}
|
||||||
|
if not getattr(args, "noninteractive", False):
|
||||||
|
print("⬢ Simbiot Harness — визард первого запуска.")
|
||||||
|
print(" Enter = оставить текущее значение. Всё пишется в .env (0600).")
|
||||||
|
tok = _ask("Токен TG-бота", _dotenv_get(env_path, "SIMBIOT_TG_TOKEN"), secret=True)
|
||||||
|
allowed = _ask("Разрешённые chat-id через запятую",
|
||||||
|
_dotenv_get(env_path, "SIMBIOT_TG_ALLOWED"))
|
||||||
|
key = _ask("Ключ 9router", _dotenv_get(env_path, "ROUTER_KEY"), secret=True)
|
||||||
|
base = _ask("База 9router (URL)",
|
||||||
|
_dotenv_get(env_path, "ROUTER_BASE_URL")
|
||||||
|
or "http://127.0.0.1:20128/v1")
|
||||||
|
model = _ask("Модель уровней (или auto)", _dotenv_get(env_path, "SIMBIOT_MODEL") or "auto")
|
||||||
|
vals = {"SIMBIOT_TG_TOKEN": tok, "SIMBIOT_TG_ALLOWED": allowed,
|
||||||
|
"ROUTER_KEY": key, "ROUTER_BASE_URL": base, "SIMBIOT_MODEL": model}
|
||||||
|
if tok:
|
||||||
|
print(f" TG-токен: {_tg_selftest(tok)}")
|
||||||
|
for key, val in vals.items():
|
||||||
|
if val not in (None, "",):
|
||||||
|
if key == "ROUTER_BASE_URL" and val == "http://127.0.0.1:20128/v1" \
|
||||||
|
and _dotenv_get(env_path, "ROUTER_BASE_URL") == val:
|
||||||
|
continue # дефолт уже такой — .env не засоряем
|
||||||
|
_dotenv_set(env_path, key, str(val))
|
||||||
|
print(f" .env ← {key}={_mask(str(val)) if 'KEY' in key or 'TOKEN' in key else val}")
|
||||||
|
if not args.no_init:
|
||||||
|
es.init_db()
|
||||||
|
print("OK: стор инициализирован.")
|
||||||
|
print("Дальше: simbiot status — проверить; запустить харнес: scripts/install.sh --systemd "
|
||||||
|
"или ./venv/bin/python -m harness.scheduler (см. README).")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
Контекст ∝ масштабу: чем крупнее цикл, тем больше контекст и модель.
|
Контекст ∝ масштабу: чем крупнее цикл, тем больше контекст и модель.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
import os
|
||||||
|
|
||||||
SEC = 1
|
SEC = 1
|
||||||
MIN = 60
|
MIN = 60
|
||||||
|
|
@ -14,6 +15,25 @@ QUARTER = 91 * DAY
|
||||||
YEAR = 366 * DAY # округляем год как 366 дней для простоты выравнивания
|
YEAR = 366 * DAY # округляем год как 366 дней для простоты выравнивания
|
||||||
|
|
||||||
|
|
||||||
|
def _cfg_model(default: str = "auto") -> str:
|
||||||
|
"""SIMBIOT_MODEL (визард первого запуска, t-261002193628-1d1e5f):
|
||||||
|
одна модель для всех LLM-уровней вместо auto — env сильнее .env
|
||||||
|
(как runtime._env_or_file, без circular import — свой мини-ридер).
|
||||||
|
Пустое/absent значение = дефолт конфига (как было)."""
|
||||||
|
val = os.environ.get("SIMBIOT_MODEL", "").strip()
|
||||||
|
if val:
|
||||||
|
return val
|
||||||
|
try:
|
||||||
|
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env")
|
||||||
|
with open(env_path, encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith("SIMBIOT_MODEL="):
|
||||||
|
return line.split("=", 1)[1].strip().strip('"').strip("'").strip() or default
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CycleLevel:
|
class CycleLevel:
|
||||||
name: str
|
name: str
|
||||||
|
|
|
||||||
23
requirements.txt
Normal file
23
requirements.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Simbiot Harness — runtime-зависимости (strands-ядро + телеметрия).
|
||||||
|
# Версии = эталонный venv кооператива (2026-10-02); установка: scripts/install.sh
|
||||||
|
strands-agents==1.57.1
|
||||||
|
strands-harness==0.1.2
|
||||||
|
openai==3.20.0
|
||||||
|
pydantic==2.13.5
|
||||||
|
PyJWT==2.15.1
|
||||||
|
PyYAML==6.0.3
|
||||||
|
# event store / CLI — stdlib (sqlite3, argparse); audit-цепочка: cryptography
|
||||||
|
cryptography==50.0.1
|
||||||
|
# 9router-клиент (OpenAI-совместимый) + телеметрия Strands
|
||||||
|
botocore==1.43.104
|
||||||
|
boto3==1.43.104
|
||||||
|
opentelemetry-sdk==1.45.0
|
||||||
|
opentelemetry-exporter-otlp-proto-http==1.45.0
|
||||||
|
opentelemetry-semantic-conventions==0.66b0
|
||||||
|
# дашборд и HTTP-эндпоинты
|
||||||
|
starlette==1.7.0
|
||||||
|
uvicorn==0.54.0
|
||||||
|
sse-starlette==3.5.0
|
||||||
|
python-multipart==0.0.32
|
||||||
|
py-spy==0.4.2
|
||||||
|
watchdog==6.0.0
|
||||||
150
scripts/install.sh
Normal file
150
scripts/install.sh
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================================
|
||||||
|
# Simbiot Harness — установка одной командой
|
||||||
|
# ============================================================================
|
||||||
|
# curl -fsSL https://raw.cil.by/simbiot/install.sh | bash
|
||||||
|
# # или из клона репо:
|
||||||
|
# bash scripts/install.sh
|
||||||
|
#
|
||||||
|
# Что делает:
|
||||||
|
# 1. Клонирует (или обновляет) репо в ~/simbiot-harness
|
||||||
|
# 2. Создаёт Python 3.11+ venv, ставит зависимости (requirements.txt)
|
||||||
|
# 3. Кладёт обёртку `simbiot` в ~/.local/bin (аналог hermes: чистый env,
|
||||||
|
# exec venv/bin/python cli.py — вне репо работает отовсюду)
|
||||||
|
# 4. Флаги: --dir DIR (куда ставить, по умолчанию ~/simbiot-harness),
|
||||||
|
# --repo URL (по умолчанию https://git.cil.by/symbiot/simbiot-harness.git),
|
||||||
|
# --wizard (визард первого запуска даже в интерактиве не нужен),
|
||||||
|
# --no-wizard (тихий режим: без вопросов), -q (минимум вывода).
|
||||||
|
# Опционально ставит systemd-юниты (если root и systemd), иначе — как
|
||||||
|
# запустить руками, печатает подсказку симулятивно.
|
||||||
|
# ============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_URL="https://git.cil.by/symbiot/simbiot-harness.git"
|
||||||
|
INSTALL_DIR="$(eval echo '~')/simbiot-harness"
|
||||||
|
WIZARD_AUTO="" # "", "yes", "no"
|
||||||
|
QUIET=0
|
||||||
|
FORCE_SYSTEMD=""
|
||||||
|
INTERACTIVE=0
|
||||||
|
[ -t 0 ] && [ -t 1 ] && INTERACTIVE=1
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--dir) INSTALL_DIR="$2"; shift 2 ;;
|
||||||
|
--repo) REPO_URL="$2"; shift 2 ;;
|
||||||
|
--wizard) WIZARD_AUTO="yes"; shift ;;
|
||||||
|
--no-wizard) WIZARD_AUTO="no"; shift ;;
|
||||||
|
--systemd) FORCE_SYSTEMD="yes"; shift ;;
|
||||||
|
-q|--quiet) QUIET=1; shift ;;
|
||||||
|
-h|--help) grep '^#' "$0" | sed -n '2,20p' | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||||
|
*) echo "Неизвестный аргумент: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[0;33m'; NC='\033[0m'
|
||||||
|
say() { [ "$QUIET" = 1 ] || echo -e "$1"; }
|
||||||
|
|
||||||
|
say "${CYAN}⬢ Simbiot Harness — установка${NC}"
|
||||||
|
say ""
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 1. Репозиторий
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||||
|
say "${CYAN}→${NC} Обновляю существующий клон: $INSTALL_DIR"
|
||||||
|
git -C "$INSTALL_DIR" fetch --depth=1 origin >/dev/null 2>&1 || true
|
||||||
|
git -C "$INSTALL_DIR" reset --hard origin/HEAD >/dev/null 2>&1 || \
|
||||||
|
git -C "$INSTALL_DIR" reset --hard origin/main >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
say "${CYAN}→${NC} Клонирую репозиторий → $INSTALL_DIR"
|
||||||
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
git clone --depth=1 "$REPO_URL" "$INSTALL_DIR" && break
|
||||||
|
[ "$i" = 3 ] && { echo "Ошибка: клонирование не удалось ($REPO_URL)" >&2; exit 1; }
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 2. Python venv + зависимости
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo "python3 не найден. Установи Python 3.11+: apt install -y python3 python3-venv python3-pip" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PYV=$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')
|
||||||
|
PYMAJOR=${PYV%%.*}; PYMINOR=${PYV#*.}
|
||||||
|
if [ "$PYMAJOR" -lt 3 ] || { [ "$PYMAJOR" = 3 ] && [ "$PYMINOR" -lt 11 ]; }; then
|
||||||
|
echo "Нужен Python ≥ 3.11, найден $PYV" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x venv/bin/python ]; then
|
||||||
|
say "${CYAN}→${NC} Создаю venv (python $PYV)…"
|
||||||
|
python3 -m venv venv
|
||||||
|
fi
|
||||||
|
say "${CYAN}→${NC} Ставлю зависимости…"
|
||||||
|
./venv/bin/pip install --upgrade pip >/dev/null
|
||||||
|
./venv/bin/pip install -r requirements.txt >/dev/null 2>&1 || \
|
||||||
|
./venv/bin/pip install -r requirements.txt
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 3. Обёртка `simbiot` (аналог /usr/local/bin/hermes)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
BIN_DIR="$(bash -c 'echo -n "${XDG_BIN_HOME:-$HOME/.local/bin}"')"
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
cat > "$BIN_DIR/simbiot" <<EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Simbiot Harness CLI (установлен scripts/install.sh в $INSTALL_DIR)
|
||||||
|
unset PYTHONPATH
|
||||||
|
unset PYTHONHOME
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
exec ./venv/bin/python cli.py "\$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$BIN_DIR/simbiot"
|
||||||
|
case ":$PATH:" in
|
||||||
|
*":$BIN_DIR:"*) ;;
|
||||||
|
*) export PATH="$BIN_DIR:$PATH" ;;
|
||||||
|
esac
|
||||||
|
say "${GREEN}→${NC} Команда simbiot готова: $BIN_DIR/simbiot"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 4. Первый запуск: init стора (тихо) + визард (если просили/интерактив)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ ! -f harness.db ]; then
|
||||||
|
if [ "$WIZARD_AUTO" = "no" ] || { [ -z "$WIZARD_AUTO" ] && [ "$INTERACTIVE" = 0 ]; }; then
|
||||||
|
say "${CYAN}→${NC} Тихий режим: ./venv/bin/python cli.py init"
|
||||||
|
./venv/bin/python cli.py init
|
||||||
|
say "${YELLOW}→${NC} Настройки (токен TG, ключ 9router): ${BIN_DIR}/simbiot wizard — когда будешь готов."
|
||||||
|
else
|
||||||
|
./venv/bin/python cli.py wizard || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 5. systemd (только если root и просили явно)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ "${FORCE_SYSTEMD:-}" = "yes" ]; then
|
||||||
|
if [ "$(id -u)" = "0" ] && command -v systemctl >/dev/null 2>&1 && systemctl is-system-running >/dev/null 2>&1; then
|
||||||
|
cp "$INSTALL_DIR/scripts/simbiot-harness.service" /etc/systemd/system/ 2>/dev/null || sed -e "s|/root/simbiot-harness|$INSTALL_DIR|g" -e "s|root|$USER|g" "$INSTALL_DIR/scripts/simbiot-harness.service" > /etc/systemd/system/simbiot-harness.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now simbiot-harness
|
||||||
|
say "${GREEN}→${NC} systemd: simbiot-harness запущен (systemctl status simbiot-harness)"
|
||||||
|
else
|
||||||
|
say "${YELLOW}→${NC} systemd-юнит пропущен (нужны root и systemd)."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 6. Проверка одной проверяемой командой
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
say ""
|
||||||
|
if simbiot status >/dev/null 2>&1; then
|
||||||
|
say "${GREEN}✅ Установка завершена: simbiot status работает.${NC}"
|
||||||
|
else
|
||||||
|
echo "⚠ simbiot status вернул ошибку — см. вывод выше." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
say "Дальше: simbiot wizard (настройка), simbiot --help (команды)."
|
||||||
15
scripts/simbiot-harness.service
Normal file
15
scripts/simbiot-harness.service
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Simbiot Harness - tact ladder (Strands + CQRS/ES)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=__INSTALL_DIR__
|
||||||
|
ExecStart=__INSTALL_DIR__/venv/bin/python -m harness.scheduler
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Loading…
Reference in a new issue