# زیرساخت فعلی — یادداشت مرجع > این سند خلاصهٔ بررسی کد فعلی ربات تلگرام است، فقط برای درک زیرساخت موجود. > **نسخهٔ وب جدید هیچ‌کدام از این فایل‌ها را 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 نمی‌شوند.** فقط قراردادها، الگوهای اعتبارسنجی و قواعد امنیتی بالا به عنوان مرجع استفاده شده‌اند.