Configure admin title and deploy docs

This commit is contained in:
Your Name
2026-07-28 12:37:04 +05:00
parent bb2c58e02f
commit 3ab8a7b4f2
7 changed files with 68 additions and 88 deletions
+1
View File
@@ -1,5 +1,6 @@
APP_ENV=production APP_ENV=production
APP_SECRET_KEY=change-me APP_SECRET_KEY=change-me
ADMIN_APP_TITLE=Редакторская
ADMIN_BOOTSTRAP_LOGIN=admin ADMIN_BOOTSTRAP_LOGIN=admin
ADMIN_BOOTSTRAP_PASSWORD=change-me-long-password ADMIN_BOOTSTRAP_PASSWORD=change-me-long-password
+40 -57
View File
@@ -31,9 +31,11 @@
- Telegram: `aiogram`. - Telegram: `aiogram`.
- AI: `litellm`, сейчас используется Anthropic через LiteLLM. - AI: `litellm`, сейчас используется Anthropic через LiteLLM.
- Логи: `loguru`. - Логи: `loguru`.
- Системный запуск на сервере: `systemd`. - Runtime/deploy: локальная инфраструктура Proxmox + Coolify + Docker.
- Reverse proxy / TLS: nginx + сертификат для `https://sw.exostring.xyz`. - Reverse proxy / TLS: edge-proxy Caddy из локальной инфраструктуры.
- Репозиторий: GitHub `exostring/n8_parser`. - Публичная админка: `https://panel.f-n8.ru` (`https://admin.f-n8.ru` после обновления DNS).
- Deploy-репозиторий: Gitea `http://192.168.1.135:3000/exostring/new_vk_parser.git`, remote `gitea`.
- GitHub `origin` используется как upstream/backup, не как основной deploy remote.
## 3. Структура проекта ## 3. Структура проекта
@@ -74,6 +76,8 @@
Если меняется поведение системы, эксплуатационная схема, публикационный формат, БД, деплой или админка, это нужно отражать в `PROJECT.md` или другом релевантном `.md` рядом с кодом. Если меняется поведение системы, эксплуатационная схема, публикационный формат, БД, деплой или админка, это нужно отражать в `PROJECT.md` или другом релевантном `.md` рядом с кодом.
Инфраструктурная карта вне репозитория: `D:\DEVELOPMENT\.infra.md`. Перед любыми deploy/server действиями сначала читать её. Для этого проекта актуален контур Gitea -> Coolify -> LXC 105; старые инструкции про `sw.exostring.xyz`, `109.120.156.205`, `/opt/vk-parser` и ручной `systemctl` не использовать без явной команды пользователя.
Текущие зафиксированные изменения: Текущие зафиксированные изменения:
- Публичный сайт вынесен в самостоятельное приложение `forma_n8_site/` и разворачивается на отдельном VPS. Сайт хранит собственные статьи и медиа, отдаёт серверный HTML, RSS, sitemap, robots.txt, canonical и JSON-LD `NewsArticle`. - Публичный сайт вынесен в самостоятельное приложение `forma_n8_site/` и разворачивается на отдельном VPS. Сайт хранит собственные статьи и медиа, отдаёт серверный HTML, RSS, sitemap, robots.txt, canonical и JSON-LD `NewsArticle`.
@@ -1230,47 +1234,42 @@ $env:PYTHONPYCACHEPREFIX="$env:TEMP\n8_pycache"
python -m py_compile src\vk_parser_app\admin.py python -m py_compile src\vk_parser_app\admin.py
``` ```
## 16. Сервер ## 16. Runtime / Deploy
Рабочий путь: Актуальный runtime живёт в локальной инфраструктуре. Источник правды по хостам,
ключам, Coolify, Gitea и edge-proxy: `D:\DEVELOPMENT\.infra.md`.
```bash Текущая схема:
/opt/vk-parser
```text
local repo
-> git push gitea main
-> Gitea webhook on 192.168.1.135
-> Coolify on 192.168.1.135
-> Docker app on LXC 105 / 192.168.1.105
-> edge-proxy / Caddy
-> https://panel.f-n8.ru
``` ```
Основные команды: Важно:
```bash - Не использовать для этого проекта старый VPS `109.120.156.205`.
cd /opt/vk-parser - Не использовать `https://sw.exostring.xyz` как проверку текущей админки.
git pull --ff-only - Не выполнять `/opt/vk-parser`, `git pull` на VPS и `systemctl restart ...`
PYTHONPATH=src .venv/bin/python scripts/apply_migrations.py без явного указания пользователя, что речь именно о старом VPS.
systemctl restart vk-parser-admin.service - Основной deploy remote: `gitea`.
``` - URL репозитория в Coolify должен резолвиться из build/helper container на
deploy-сервере. Не использовать `http://coolify.lan:3000/...`, если это имя
не настроено внутри LXC/Docker; рабочий локальный URL Gitea:
`http://192.168.1.135:3000/exostring/new_vk_parser.git`.
- Coolify должен собирать и деплоить приложение после push/webhook; при ручном
деплое использовать Coolify API/UI из `D:\DEVELOPMENT\.infra.md`.
Проверка сервисов: Публичная админка:
```bash ```text
systemctl is-active vk-parser-admin.service https://panel.f-n8.ru
systemctl is-active vk-parser.service https://admin.f-n8.ru
systemctl is-active vk-storage-uploader.service
systemctl is-active ai-qualifier.service
systemctl is-active ai-writer.service
systemctl is-active tg-poster.service
systemctl is-active max-poster.service
systemctl is-active telegram-bot-api.service
```
Логи:
```bash
journalctl -u vk-parser-admin.service -n 100 --no-pager
journalctl -u vk-parser.service -n 100 --no-pager
journalctl -u vk-storage-uploader.service -n 100 --no-pager
journalctl -u ai-qualifier.service -n 100 --no-pager
journalctl -u ai-writer.service -n 100 --no-pager
journalctl -u tg-poster.service -n 100 --no-pager
journalctl -u max-poster.service -n 100 --no-pager
journalctl -u telegram-bot-api.service -n 100 --no-pager
``` ```
## 17. Git workflow ## 17. Git workflow
@@ -1281,28 +1280,12 @@ journalctl -u telegram-bot-api.service -n 100 --no-pager
git status git status
git add ... git add ...
git commit -m "Message" git commit -m "Message"
git push git pull --rebase gitea main
git push gitea main
``` ```
На сервере origin настроен через deploy key. `origin` остаётся GitHub/upstream/backup. Для deploy использовать `gitea`,
потому что Gitea webhook запускает Coolify.
Обычный деплой:
```bash
cd /opt/vk-parser
git pull --ff-only
PYTHONPATH=src .venv/bin/python scripts/apply_migrations.py
systemctl restart vk-parser-admin.service
```
Если менялся конкретный worker, перезапустить его:
```bash
systemctl restart ai-writer.service
systemctl restart ai-qualifier.service
systemctl restart vk-parser.service
systemctl restart vk-storage-uploader.service
```
## 18. Контролируемый тест AI-райтера ## 18. Контролируемый тест AI-райтера
+16 -22
View File
@@ -1,28 +1,22 @@
# N8 Parser: current state # N8 Parser: current state
Last updated: 2026-06-17 Last updated: 2026-07-28
This file is the short handoff state for future Codex threads. Read this first before touching the project, so the whole chat history does not need to be carried forward. This file is the short handoff state for future Codex threads. Read this first before touching the project, so the whole chat history does not need to be carried forward.
## Server ## Runtime / Deploy
- Production host: `109.120.156.205` Current runtime is local infrastructure, not the old VPS.
- Domain: `https://sw.exostring.xyz`
- App directory on server: `/opt/vk-parser` - Infrastructure source of truth: `D:\DEVELOPMENT\.infra.md`.
- Timezone: `Asia/Yekaterinburg` - Git deploy remote: `gitea` -> `http://192.168.1.135:3000/exostring/new_vk_parser.git`.
- Main services: - Deploy path: push to `gitea/main` -> Gitea webhook -> Coolify -> Docker build/deploy.
- `vk-parser-admin.service` - Coolify + Gitea LXC: `192.168.1.135` (CTID 102).
- `vk-parser.service` - Application LXC: `192.168.1.105` (CTID 105, `new-vk-parser`).
- `vk-storage-uploader.service` - Public admin URL: `https://panel.f-n8.ru` (also `https://admin.f-n8.ru` when DNS is current).
- `ai-qualifier.service` - Coolify repository URL must be reachable from the deployment container; use `http://192.168.1.135:3000/exostring/new_vk_parser.git` or another resolvable Gitea URL, not `http://coolify.lan:3000/...`.
- `ai-writer.service` - Do not deploy this project through `sw.exostring.xyz`, `109.120.156.205`, `/opt/vk-parser`, or manual `systemctl` unless the user explicitly says the old VPS is being restored.
- `site-poster.service` - Old `sw.exostring.xyz` / `109.120.156.205` notes are stale for this project.
- Useful commands:
- `cd /opt/vk-parser`
- `systemctl status vk-parser-admin.service ai-qualifier.service ai-writer.service`
- `journalctl -u ai-writer.service -n 100 --no-pager`
- `journalctl -u ai-qualifier.service -n 100 --no-pager`
- `PYTHONPATH=src .venv/bin/python -m py_compile src/vk_parser_app/admin.py src/vk_parser_app/workers/ai_writer.py`
## Local Project ## Local Project
@@ -102,9 +96,9 @@ On 2026-06-17 the hashtag flow was fixed:
## Working Rules For Future Codex Runs ## Working Rules For Future Codex Runs
- Before any deploy/server operation, read `D:\DEVELOPMENT\.infra.md` first.
- For this project, push deployable changes to `gitea/main`; Coolify handles deployment.
- Prefer small targeted file reads with `rg` and narrow ranges. - Prefer small targeted file reads with `rg` and narrow ranges.
- Avoid dumping long post texts from DB unless explicitly needed. - Avoid dumping long post texts from DB unless explicitly needed.
- For server checks, return counts/statuses first. - For runtime checks, use the current Coolify/Gitea/LXC 105 path from `D:\DEVELOPMENT\.infra.md`.
- Be careful: both local and server worktrees may be dirty.
- Do not `git reset --hard` or revert user/server hotfixes. - Do not `git reset --hard` or revert user/server hotfixes.
- When fixing production, patch server and then mirror the same change locally.
+2 -2
View File
@@ -45,7 +45,7 @@ BASE_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = BASE_DIR.parents[1] PROJECT_ROOT = BASE_DIR.parents[1]
FAVICON_PATH = PROJECT_ROOT / "favi.png" FAVICON_PATH = PROJECT_ROOT / "favi.png"
templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
app = FastAPI(title="VK Parser Admin") app = FastAPI(title=settings.display_admin_title)
LOCAL_TZ = ZoneInfo("Asia/Yekaterinburg") LOCAL_TZ = ZoneInfo("Asia/Yekaterinburg")
MODEL_CACHE: dict[str, Any] = {"key": "", "at": 0.0, "models": []} MODEL_CACHE: dict[str, Any] = {"key": "", "at": 0.0, "models": []}
UPLOAD_ROOT = Path("uploads").resolve() UPLOAD_ROOT = Path("uploads").resolve()
@@ -444,7 +444,7 @@ def require_csrf(user: dict, csrf_token: str) -> None:
def base_context(request: Request, user: dict | None, **extra: Any) -> dict[str, Any]: def base_context(request: Request, user: dict | None, **extra: Any) -> dict[str, Any]:
ctx = {"request": request, "user": user, "app_env": settings.app_env} ctx = {"request": request, "user": user, "app_env": settings.app_env, "app_title": settings.display_admin_title}
ctx.update(extra) ctx.update(extra)
return ctx return ctx
+5
View File
@@ -6,6 +6,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): class Settings(BaseSettings):
app_env: str = "production" app_env: str = "production"
app_secret_key: str = "change-me" app_secret_key: str = "change-me"
admin_app_title: str = "Редакторская"
admin_bootstrap_login: str = "admin" admin_bootstrap_login: str = "admin"
admin_bootstrap_password: str = "" admin_bootstrap_password: str = ""
@@ -37,6 +38,10 @@ class Settings(BaseSettings):
f"@{self.db_host}:{self.db_port}/{self.db_name}" f"@{self.db_host}:{self.db_port}/{self.db_name}"
) )
@property
def display_admin_title(self) -> str:
return self.admin_app_title.strip() or "Редакторская"
@property @property
def vk_storage_owner_id(self) -> int: def vk_storage_owner_id(self) -> int:
return -abs(int(self.vk_storage_group_id)) return -abs(int(self.vk_storage_group_id))
+3 -5
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favi.png"> <link rel="icon" type="image/png" href="/favi.png">
<title>{{ title or "VK Parser Admin" }}</title> <title>{{ title or app_title or "Редакторская" }}</title>
<!-- Fonts: Fira Sans (UI) & Fira Code (Data) --> <!-- Fonts: Fira Sans (UI) & Fira Code (Data) -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -162,8 +162,7 @@
<div class="p-4"> <div class="p-4">
<div class="flex items-center gap-3 text-app-textMain font-bold text-lg tracking-wide" :class="expanded ? 'justify-between' : 'justify-center'"> <div class="flex items-center gap-3 text-app-textMain font-bold text-lg tracking-wide" :class="expanded ? 'justify-between' : 'justify-center'">
<div class="flex items-center gap-3 min-w-0"> <div class="flex items-center gap-3 min-w-0">
<i data-lucide="zap" class="w-6 h-6 text-app-primary flex-shrink-0"></i> <span x-show="expanded" x-cloak class="truncate">{{ app_title or "Редакторская" }}</span>
<span x-show="expanded" x-cloak class="truncate">Parser Admin</span>
</div> </div>
<button type="button" class="btn btn-ghost btn-icon flex-shrink-0" @click="expanded = !expanded" :aria-label="expanded ? 'Свернуть меню' : 'Развернуть меню'" :title="expanded ? 'Свернуть меню' : 'Развернуть меню'"> <button type="button" class="btn btn-ghost btn-icon flex-shrink-0" @click="expanded = !expanded" :aria-label="expanded ? 'Свернуть меню' : 'Развернуть меню'" :title="expanded ? 'Свернуть меню' : 'Развернуть меню'">
<i data-lucide="panel-left" class="w-5 h-5"></i> <i data-lucide="panel-left" class="w-5 h-5"></i>
@@ -223,8 +222,7 @@
<!-- Mobile Topbar (Visible only on small screens) --> <!-- Mobile Topbar (Visible only on small screens) -->
<div class="md:hidden fixed top-0 left-0 right-0 h-16 bg-app-surface border-b border-app-border z-50 flex items-center justify-between px-4"> <div class="md:hidden fixed top-0 left-0 right-0 h-16 bg-app-surface border-b border-app-border z-50 flex items-center justify-between px-4">
<div class="flex items-center gap-2 font-bold text-app-textMain"> <div class="flex items-center gap-2 font-bold text-app-textMain">
<i data-lucide="zap" class="w-5 h-5 text-app-primary"></i> {{ app_title or "Редакторская" }}
Parser
</div> </div>
<button class="btn btn-ghost btn-icon" onclick="document.getElementById('mobile-menu').classList.toggle('hidden')"> <button class="btn btn-ghost btn-icon" onclick="document.getElementById('mobile-menu').classList.toggle('hidden')">
<i data-lucide="menu" class="w-6 h-6"></i> <i data-lucide="menu" class="w-6 h-6"></i>
+1 -2
View File
@@ -205,8 +205,7 @@
<div> <div>
<button class="btn btn-ghost btn-sm text-app-error hover:bg-app-errorBg hover:text-app-error" type="button" <button class="btn btn-ghost btn-sm text-app-error hover:bg-app-errorBg hover:text-app-error" type="button"
hx-post="/editor/{{ p.id }}/reject" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML" hx-post="/editor/{{ p.id }}/reject" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML"
hx-vals='{"csrf_token": "{{ user.csrf_token }}"}' hx-vals='{"csrf_token": "{{ user.csrf_token }}"}'>
onclick="return confirm('Отклонить пост?')">
<i data-lucide="x" class="w-4 h-4"></i> Отклонить <i data-lucide="x" class="w-4 h-4"></i> Отклонить
</button> </button>
</div> </div>