Skip to content

معماری ​

W-UI یک فایل اجرایی Go است. همه‌چیز را در یک دیتابیس نگه می‌دارد و یک حلقه کرنل را با آن یکی می‌کند. این صفحه نقشه است: هر چیز کجاست و چه قاعده‌هایی آن را همان‌جا نگه می‌دارد. قاعده‌ها را internal/archtest در هر go test ./... می‌سنجد، پس تغییری که یکی را بشکند در CI رد می‌شود، نه در بازبینی.

لایه‌ها ​

هر لایه فقط از لایه‌های پایین‌تر استفاده می‌کند.

 درگاه‌ها           api (HTTP)   tgbot   cmd/wui (CLI و شروع)   web (Vue)
 ─────────────────────────────────────────────────────────────────────────
 گزارش              oversight: ممیزی امنیتی، گزارش روزانه، یادآوری‌ها
 ─────────────────────────────────────────────────────────────────────────
 سرویس‌های دامنه    service: مشتری‌ها، تونل‌ها، اشتراک، گروه‌ها، engine
                    operators: مالک، مدیرها و نماینده‌ها، ورود
                    fleet: نودها آن‌طور که پنل نگهشان می‌دارد، و آنچه برای هر کدام فرستاده می‌شود
                    egress: خروجی‌ها، سرویس‌دهنده‌ها، بالانسرها، قوانین مسیریابی
                    nodes: بررسی و همگام‌سازی سرورهای دیگر
                    endpoints: آدرس‌هایی که فایل مشتری به آن‌ها اشاره می‌کند (هاست‌ها)
                    panelconf: تنظیمات خود پنل
 ─────────────────────────────────────────────────────────────────────────
 اعمال              reconciler: حلقه‌ای که کرنل را با دیتابیس یکی می‌کند
                    backend و یک درایور برای هر پروتکل، با agentkit مشترک
                    enforce (nftables)، shaper (tc)، routing (fwmark و hop)
 ─────────────────────────────────────────────────────────────────────────
 داده               database (با model و مهاجرت‌های شماره‌دار)، scope، ipam، backup
 ─────────────────────────────────────────────────────────────────────────
 قطعه‌های کوچک      fault، listfield، tunnelport، idset، optional، membership،
                    keyfile، wgkey، totp، certfile، پکیج conf هر پروتکل، devguide

عامل root (l2tpagent، سرویس wui-l2tp) جدا از پنل و با دسترسی root اجرا می‌شود، چون pppd و strongSwan و ocserv به root نیاز دارند؛ خود پنل هیچ‌وقت root نیست. این دو از راه فایل‌های پوشهٔ داده با هم حرف می‌زنند: پنل می‌نویسد هر تونل چه باشد و عامل هر ثانیه گزارش برمی‌گرداند. agentkit سمت پنلِ همین قرارداد است.

هر لایه چه کاری می‌کند ​

  • درگاه می‌خواند، می‌پرسد چه کسی است و جواب می‌دهد. handler درخواست را باز می‌کند، سرویس را صدا می‌زند و نتیجه را می‌نویسد. قاعده‌ای که در handler باشد را ربات، CLI و همگام‌سازی نود رعایت نمی‌کنند. api هیچ‌وقت مستقیم از دیتابیس نمی‌خواند؛ فقط api.go دیتابیس را می‌شناسد تا سرویس‌ها را بسازد.
  • هر قاعده مالک دارد. شروع مدت نماینده در اولین ورود مال operators.Admins است؛ خاموش کردن همهٔ تونل‌ها مال service.DataPlane. هر قاعده، یک جا.
  • خطای سرویس نوع دارد. fault.ErrInvalid، fault.ErrNotFound و fault.FieldError (که نام فیلد فرم را دارد) همان‌هایی‌اند که API به ۴۰۰ و ۴۰۴ و پیام زیر فیلد درست تبدیل می‌کند.
  • پرس‌وجوهای نماینده برای خودش محدود می‌شود. scope مالک را به هر پرس‌وجو روی مشتری‌ها، دستگاه‌ها و گروه‌ها اضافه می‌کند. SQL خام روی این جدول‌ها رد می‌شود، مگر جایی که با scope.Vouch ضمانت شده و خودش بر اساس مالک محدود می‌کند.
  • دیتابیس فقط بزرگ‌تر می‌شود. هر بار شروع، جدول‌ها از روی مدل‌ها ستون و index تازه می‌گیرند و چیزی که نسخهٔ قبلی ساخته حذف نمی‌شود (additive_test.go، روی دیتابیس هر نسخه از ۱.۰ به بعد). هر چیز بیشتر یک مهاجرت شماره‌دار در database/migrate.go است که یک بار اجرا و در schema_migrations ثبت می‌شود.
  • JSON یک تصمیم است. مدل‌ها همان پاسخ API هستند، پس JSON هر مدل در model/testdata/json-shape.golden ثابت شده، مسیرها و اینکه چه کسی به هر کدام می‌رسد در api/testdata/routes.golden، و پاسخ‌های کامل در cmd/wui/testdata/contract. هر تغییری در این‌ها تا وقتی فایل golden با -update بازنویسی و diff بازبینی نشود رد می‌شود.

قاعده‌هایی که تست‌ها اجرا می‌کنند ​

قاعدهچرا
هیچ پکیجی متغیر صادرشده‌ای که تابع نگه دارد نداردhookی که از بیرون تنظیم شود رفتار پکیج را تعیین می‌کند و آخرین تنظیم‌کننده برنده است. وابستگی به خود نمونه داده می‌شود.
فقط cmd/wui متغیر پکیج دیگری را می‌نویسدیک تنظیم ساده (جای فایل‌ها) یک بار در شروع و پیش از هر خواندنی تنظیم می‌شود.
هیچ درایور تونلی درایور دیگری را import نمی‌کندآنچه مشترک است در backend/agentkit است، تا تغییر یک پروتکل پروتکل دیگری را نشکند.
api از دیتابیس نمی‌خواندhandlerها از سرویس می‌پرسند.
egress، fault، listfield، tunnelport، backend، idset، optional، membership، endpoints و panelconf هیچ‌کدام از service، operators و fleet را import نمی‌کننددامنه‌ها روی این‌ها ساخته شده‌اند، نه برعکس.
service، operators و fleet همدیگر را import نمی‌کننداین‌ها هم‌سطح‌اند. آنچه یکی از دیگری لازم دارد یا یک interface کوچک است که خودش تعریف می‌کند (operators.Customers، که service.Clients جوابش را می‌دهد)، یا در پکیجی زیر همه است (membership، که توابعش تراکنشِ صدازننده را می‌گیرند، تا مشتری‌های یک نماینده در همان تراکنشی تحویل داده شوند که نماینده را حذف می‌کند).
service و egress، oversight را import نمی‌کنندآنچه از پنل گزارش می‌دهد آن را می‌خواند و هیچ‌وقت صدا زده نمی‌شود.
هیچ خطای دیتابیسی دور ریخته نمی‌شود_ = db.Find(&x).Error وقتی دیتابیس مشغول است جوابی از هیچ می‌سازد.

آنچه هنوز یک پکیج است، و چرا ​

service هنوز مشتری‌ها، تونل‌ها، اشتراک‌ها، گروه‌ها، صفحهٔ engine و سمت 3x-ui و Pasarguard است، حدود ۱۰٬۵۰۰ خط. مشتری‌ها و تونل‌ها بعدی‌اند که جدا شوند، و با جابه‌جا کردن فایل جدا نمی‌شوند:

  • سمت مشتری‌ها در یک جا خود سرویس تونل‌ها را به کار می‌گیرد (Interfaces، CreateInterfaceInput) و با خطاهای خود تونل‌ها (ErrPoolExhausted، ErrDeviceLimit) و پیش‌تنظیم AmneziaWG آن‌ها جواب می‌دهد.
  • سمت تونل‌ها می‌خواند چه کسی آنلاین است (onlineWithin) و مصرف فایل‌های مشتری را.
  • xui.go و subscription.go میان هر دو هستند: هر سمت بخشی از آن‌ها را به کار می‌برد.

جدا کردنشان یعنی یک interface کوچک از هر طرف و دو نیم شدن کد 3x-ui، روی مسیرهایی که پیکربندی مشتری از آن ساخته می‌شود. این تغییری است که باید جدا طراحی و سنجیده شود، در برابر ارتقای بایت‌به‌بایت یکسان از 2.6.4، نه اینکه با یک جابه‌جایی انجام شود.

سیم‌کشی ​

تابع run در cmd/wui/main.go همان شروع است، به ترتیب، و هر مرحله تابعی در start.go. هر شیء ماندگار یک بار همان‌جا ساخته و به استفاده‌کننده‌هایش داده می‌شود: یک service.Clients، یک service.Interfaces، یک egress.Tuning (آنچه صفحهٔ engine تنظیم می‌کند)، و یک دسته بالانسر. تست‌ها نمونهٔ خودشان را می‌سازند و هر چه داده نشود (SetLive، SetRemote) به رفتار CLI برمی‌گردد.

پایش در حال اجرا ​

GET /api/health برای سرویس پایش آپ‌تایم 200 یا 503 می‌دهد؛ GET /api/metrics برای کسی که وارد شده متن Prometheus می‌دهد: هر تونل، آخرین اجرای حلقه، نوشتن‌های ناموفق مصرف، نودها و بکاپ‌ها.

چیز تازه را کجا بگذاریم ​

  • قاعدهٔ تازه دربارهٔ مشتری، تونل یا اپراتور: متدی روی سرویسی که مالک آن جدول است، با تست در همان پکیج.
  • endpoint تازه: یک ردیف در جدول api/routes.go (خودش مستند می‌شود)، handlerی که سرویس را صدا می‌زند، و go test ./internal/api -run TestTheRouteTable -update.
  • پروتکل تازه: بستهٔ درایوری در internal/backend کنار wgdriver و بقیه که backend.Backend را پیاده می‌کند، ثبت‌شده در cmd/wui، و اگر عامل root اجرایش می‌کند با agentkit.
  • ستون تازه: روی مدل، با برچسب JSON فقط اگر API باید نشانش دهد، بعد go test ./internal/database/model -update و نگاهی به diff.
  • تغییر دادهٔ قدیمی: یک مهاجرت شماره‌دار تازه در انتهای فهرست، طوری نوشته شده که دو بار اجرا شدنش چیزی را عوض نکند.

پوشش تست پکیج‌هایی که ورود، جداسازی نماینده‌ها، بکاپ و حساب مصرف را نگه می‌دارند کفی دارد (scripts/coverage-floor.sh)؛ زیر آن CI رد می‌شود.

Released under the AGPL-3.0 License.