Configure admin title and deploy docs
This commit is contained in:
@@ -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
@@ -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-райтера
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user