مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
128 lines
7.8 KiB
Markdown
128 lines
7.8 KiB
Markdown
# زیرساخت فعلی — یادداشت مرجع
|
||
|
||
> این سند خلاصهٔ بررسی کد فعلی ربات تلگرام است، فقط برای درک زیرساخت موجود.
|
||
> **نسخهٔ وب جدید هیچکدام از این فایلها را import نمیکند.** هر قابلیت با یک
|
||
> adapter مستقل داخل `web/` بازنویسی میشود. این سند صرفاً قراردادها و محدودیتهایی
|
||
> را ثبت میکند که باید در adapterهای جدید حفظ شوند.
|
||
|
||
---
|
||
|
||
## ۱. Domain Routing و Preview Domain
|
||
|
||
منابع بررسیشده: `caddy_manager.py` (۲۵۹ خط)، `port_registry.py` (۵۳۱ خط).
|
||
|
||
### ۱.۱ Caddy
|
||
|
||
- مسیر فایل: `CADDYFILE_PATH = /etc/caddy/Caddyfile` (یک فایل مشترک سیستمی).
|
||
- دامنهٔ wildcard: `WILDCARD_DOMAIN = msa.artacloud.ir`.
|
||
- **allowlist سختگیرانهٔ host:** فقط hostهای تکlabel زیر دامنهٔ wildcard مجازند.
|
||
الگوی فعلی: `[a-z0-9\-]{1,63}\.msa\.artacloud\.ir`. هر کاراکتری که بتواند از بلوک
|
||
سایت خارج شود (آکولاد، کوتیشن، فاصله، newline) رد میشود.
|
||
- **target فقط local:** الگوی فعلی `(https?://)?(localhost|127\.0\.0\.1):\d{1,5}`.
|
||
این برای جلوگیری از SSRF است — نباید هیچوقت به host دیگر proxy شود.
|
||
- **چرخهٔ امن نوشتن:** خواندن فایل → ساخت/جایگزینی بلوک → نوشتن → `caddy validate`
|
||
→ در صورت خطا **بازگردانی به نسخهٔ قبلی** → `systemctl reload caddy` و در صورت
|
||
شکست `caddy reload --config ...`.
|
||
- پیدا کردن بلوک سایت با شمارش عمق آکولاد انجام میشود (نه regex)، چون بلوکها
|
||
تودرتو هستند (`handle`, `route`, `tls`, `header`).
|
||
|
||
### ۱.۲ Port Registry
|
||
|
||
دو جدول در SQLite (`data/memory.db`):
|
||
|
||
| جدول | نقش |
|
||
|---|---|
|
||
| `port_registry` | `port` (PK)، `user_id`، `project_name`، `service_name`، `subdomain`، `description`، `created_at` — یکتا روی `(user_id, project_name, service_name)` |
|
||
| `subdomain_registry` | `host` (PK)، `user_id`، `project_name`، `port`، `protected`، `created_at` — ledger مالکیت دامنه |
|
||
|
||
قواعد کلیدی که باید در `DomainRouterPort` جدید حفظ شوند:
|
||
|
||
- محدودهٔ تخصیص port: `8080-8999`.
|
||
- `SYSTEM_RESERVED_PORTS = {22, 53, 80, 443, 2019, 3000, 5432, 6379, 8008}`.
|
||
- `SYSTEM_PROTECTED_PORTS = {3000, 35555, 35556, 35557} ∪ reserved` — یک پروژهٔ
|
||
کاربر هرگز نباید به اینها proxy کند، وگرنه سرویس tenant دیگر عمومی میشود.
|
||
- `MAX_SUBDOMAINS_PER_PROJECT = 5`.
|
||
- `SYSTEM_OWNER_PROJECT = "__system__"` برای hostهای بدون مالک شناختهشده.
|
||
- snapshot پورتهای listening با `ss -tlnH` گرفته میشود. اگر snapshot ممکن نبود
|
||
(`None`)، تخصیص **رد میشود** — تفاوت «چیزی گوش نمیدهد» با «نمیدانیم» حفظ میشود.
|
||
- در تخصیص همزمان، `IntegrityError` گرفته و تا ۵ بار rescan/retry میشود.
|
||
- هر host دقیقاً یک مالک دارد؛ بدون این ledger هر پروژه میتوانست دامنهٔ پروژهٔ دیگر را
|
||
حذف یا hijack کند.
|
||
- hostهایی که قابل انتساب به پروژه نیستند `protected = 1` میشوند (فقط admin).
|
||
|
||
### ۱.۳ تصمیم برای نسخهٔ وب
|
||
|
||
- `DomainRouterPort` با متدهای `allocate_port` / `claim_preview_domain` /
|
||
`configure_reverse_proxy` / `remove_preview_domain` / `list_project_domains`.
|
||
- دو adapter: `ManagedDomainRouterAdapter` (mock، بدون لمس Caddy) و
|
||
`CaddyDomainRouterAdapter` (واقعی، پشت env flag `DOMAIN_ROUTER=caddy`).
|
||
- مالکیت دامنه به `organization_id` + `project_id` در PostgreSQL منتقل میشود،
|
||
نه `user_id` تلگرام.
|
||
|
||
---
|
||
|
||
## ۲. Git / Gitea
|
||
|
||
منابع بررسیشده: `git_manager.py` (۱۰۶۴ خط)، `gitea_api.py` (۲۱۵ خط).
|
||
|
||
### ۲.۱ تنظیمات فعلی (از `config.py`)
|
||
|
||
| متغیر | مقدار پیشفرض |
|
||
|---|---|
|
||
| `GITEA_URL` | `https://git.msa.artacloud.ir` (عمومی، برای نمایش) |
|
||
| `GITEA_INTERNAL_URL` | `http://127.0.0.1:3000` (برای تماس API) |
|
||
| `GITEA_ADMIN_USER` | `root` |
|
||
| `GITEA_ADMIN_TOKEN` | خالی — بدون آن همهٔ قابلیتهای Gitea غیرفعال است |
|
||
|
||
### ۲.۲ رفتار GitManager
|
||
|
||
- `ensure_gitea_repo(repo_name, owner, description, private=True)`: اگر repo نبود
|
||
میسازد، اگر بود و private نبود آن را PATCH میکند به private. `auto_init=False`،
|
||
`default_branch=main`.
|
||
- `generate_project_credentials(project_name, owner_id)`: نام کاربری به شکل
|
||
`u{owner_id}_{clean_name[:room]}_{sha1(f"{owner_id}:{project_name}")[:8]}` و رمز
|
||
`secrets.token_urlsafe(12)`. هش کوتاه برای جلوگیری از collision دو پروژه با نام
|
||
سانیتایز یکسان ضروری است (وگرنه PATCH رمز کاربر مشترک، credential پروژهٔ اول را
|
||
بیصدا خراب میکند). سقف ۳۶ کاراکتر Gitea رعایت میشود.
|
||
- قفل per-repo (`_RepoLock`) که برای همان asyncio task قابل re-enter است؛
|
||
`git_publish`/`git_sync` داخل بخش بحرانی خودشان دوباره lock میگیرند، پس
|
||
`asyncio.Lock` ساده deadlock میداد.
|
||
- پاکسازی `index.lock` فقط اگر قدیمیتر از `STALE_LOCK_AGE_SECONDS = 30` باشد
|
||
(حذف lock جوان میتواند index را خراب کند).
|
||
|
||
### ۲.۳ GiteaApiClient
|
||
|
||
متدها: `create_pr`، `pr_status`، `merge_pr`، `list_open_prs`،
|
||
`list_labeled_issues(label="agybot")`، `comment_issue`، `close_issue`.
|
||
نکات:
|
||
|
||
- transport تزریقپذیر است (`request` callable) تا تست بدون شبکه اجرا شود.
|
||
- token هرگز در هیچ مقدار برگشتی یا prompt قرار نمیگیرد؛ `base_url` امن برای نمایش است.
|
||
- صفحهبندی با cursor `page` انجام میشود تا نتایج در یک صفحه بریده نشوند.
|
||
- خواندن پیام خطا از `message` / `error` / `errors` (نه فقط `error`).
|
||
- برچسب `agybot` روی issue آن را به task سازمانی تبدیل میکند.
|
||
|
||
### ۲.۴ تصمیم برای نسخهٔ وب
|
||
|
||
- `GitProviderPort` با متدهای `ensure_project_repo` / `create_branch` /
|
||
`commit_files` / `open_pull_request` / `merge_pull_request` / `get_repo_status`.
|
||
- `GiteaGitAdapter` (واقعی) و `MockGitAdapter` (پیشفرض dev).
|
||
- نام repo در نسخهٔ وب: `orgslug-projectslug-shortid` (collision-free).
|
||
- credential هر پروژه **encrypted at rest** در `project_git_credentials` ذخیره میشود؛
|
||
token/password هرگز خام به frontend برنمیگردد.
|
||
- admin token فقط سمت backend میماند.
|
||
|
||
---
|
||
|
||
## ۳. جمعبندی
|
||
|
||
| قابلیت فعلی | منبع فعلی | جایگزین در نسخهٔ وب |
|
||
|---|---|---|
|
||
| Caddy subdomain routing | `caddy_manager.py` | `DomainRouterPort` + `CaddyDomainRouterAdapter` |
|
||
| Port/subdomain ledger | `port_registry.py` (SQLite) | جداول `preview_ports` / `preview_domains` (PostgreSQL) |
|
||
| Gitea repo + credentials | `git_manager.py` | `GitProviderPort` + `GiteaGitAdapter` |
|
||
| PR / issue workflow | `gitea_api.py` | همان port، متدهای PR/issue |
|
||
|
||
**هیچکدام از این فایلها در runtime نسخهٔ وب import نمیشوند.** فقط قراردادها،
|
||
الگوهای اعتبارسنجی و قواعد امنیتی بالا به عنوان مرجع استفاده شدهاند.
|