فارسی
معماری
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 رد میشود.
