آموزش میزبانی شخصی OpenAnalytics روی VPS
برای نصب OpenAnalytics به 4 گیگابایت رم و 25 گیگابایت فضای دیسک نیاز دارید. در این راهنما جزئیات اجرای Docker Compose، تنظیمات ClickHouse و Valkey را بررسی میکنیم.
پیشنیازها، پیش از گام نخست
برای میزبانی شخصی OpenAnalytics، به یک VPS لینوکسی با حدود 4 گیگابایت رم، 25 گیگابایت فضای دیسک آزاد، Docker به همراه افزونه Compose و چهار رکورد DNS که از قبل به سرور اشاره میکنند، نیاز دارید. این واقعیتِ ماجراست و باید پیش از اجرای اولین دستور مطرح شود.
این پشته شامل شش سرویس نرمافزاری و سه پایگاه داده است. Postgres وظیفه مدیریت کنترلپلن را بر عهده دارد: حسابهای کاربری، سایتها، کلیدهای API و لینکهای اشتراکگذاری. ClickHouse رویدادهای خام و دادههای تجمیعی که داشبورد میخواند را ذخیره میکند. Valkey دو بار اجرا میشود؛ یکبار بهعنوان صف رویدادهای پایدار و بار دیگر بهعنوان حافظه کش که سیستم میتواند از دست رفتن دادههای آن را تحمل کند، زیرا این دو وظیفه به سیاستهای تخلیه (eviction) متفاوتی نیاز دارند. تنها یک پردازش، یعنی درگاه پرسوجو (query gateway)، اجازه خواندن از ClickHouse را دارد و پیش از اجرای هر پرسوجو، امضای Ed25519 روی پاکتِ پرسوجو را تأیید میکند.
اگر به دنبال یک فایل اجرایی واحد و یک فایل پیکربندی هستید، این راهنما مناسب شما نیست. GoatCounter گزینه تکفایلی در این دستهبندی است: یک فایل اجرایی Go، بهصورت پیشفرض با SQLite و بدون نیاز به هیچ پایگاه داده خارجی. پشته سنگینترِ OpenAnalytics، قابلیتهایی نظیر تحلیل قیفهای تبدیل (funnels)، شاخصهای حیاتی وب (web vitals)، انتساب درآمد از حساب Stripe شخصی شما و یک سرور MCP (پروتکل زمینه مدل) را در اختیار شما میگذارد. انتخاب بین ابزارهای تحلیل خود-میزبان پستی است که این تفاوتها را بررسی میکند. این راهنما فرض را بر این میگذارد که شما قبلاً تصمیم خود را گرفتهاید.
ابتدا رکوردهای DNS را به سمت سرور تنظیم کنید
پیش از شروع هر کاری، چهار زیردامنه باید به IP عمومی سرور اشاره کنند. دلیل این امر آن است که Caddy در اولین اجرا درخواست صدور گواهی Let's Encrypt را ارسال میکند و اگر نام دامنه هنوز به IP مقصد اشاره نکند، چالش (challenge) با شکست مواجه میشود.
app.example.comبرای سرویسدهی به داشبورد استفاده میشود.api.example.comبرای سرویسدهی به API و callbackهای OAuth استفاده میشود.c.example.comبرای سرویسدهی به collector و اسکریپت tracker استفاده میشود.rt.example.comبرای سرویسدهی به stream لحظهای استفاده میشود.
از چهار رکورد A یا یک رکورد A و سه رکورد CNAME که به آن اشاره میکنند استفاده کنید. پیش از ادامه، تنظیمات را با دستور dig +short app.example.com تأیید کنید. نامی که همین یک دقیقه پیش اضافه کردهاید ممکن است همچنان توسط resolver مورد استفادهٔ Let's Encrypt به عنوان NXDOMAIN کش شده باشد؛ بنابراین اگر اولین تلاش برای دریافت گواهی شکست خورد، کمی صبر کنید و لاگهای Caddy را بررسی نمایید. اجرای مجدد مراحل نصب، سرعت انتشار (propagation) تغییرات DNS را افزایش نمیدهد.
نحوه میزبانی OpenAnalytics با استفاده از Docker Compose
یک نسخه تگشده (tagged release) را دریافت کنید. شاخه پیشفرض (default branch) محل توسعه است، در حالی که تگهای نسخه با ایمیجهای منتشرشده مطابقت دارند. دستورات زیر فرض میکنند که Docker و افزونه Compose از قبل نصب شدهاند، که در اجرای سرویسهای Docker Compose روی یک VPS به آن پرداخته شده است.
git clone https://github.com/OpenLabs-so/openanalytics
cd openanalytics
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./generate-secrets.sh --domain example.com --email you@example.com --with-geoip
docker compose pull && docker compose up -dعبارت sed '/-/d' در دستور checkout، تگهای پیشانتشار را نادیده میگیرد تا شما به جای نسخه کاندید، روی جدیدترین نسخه پایدار قرار بگیرید. --with-geoip پایگاه داده شهر DB-IP را در حین تولید دریافت میکند. اگر از این مرحله بگذرید، هر رویداد دارای کشور null خواهد بود و در نتیجه نمای جغرافیایی چیزی را نشان نخواهد داد. شما میتوانید بعداً با اجرای infra/selfhost/geoip/fetch-dbip.sh، تنظیم GEOIP_DB_PATH=/geoip/dbip-city-lite.mmdb در env/collector.env و سپس بازسازی collector با docker compose up -d --force-recreate collector این مورد را اضافه کنید. این پایگاه داده بهصورت ماهانه بهروزرسانی میشود، بنابراین دریافت آن را ماهانه تکرار کنید تا دادههای شهر شما دچار خطا نشوند.
پیش از ادامه، از secretهای تولیدشده نسخه پشتیبان تهیه کنید
ابزار تولیدکننده سه مورد را ایجاد میکند. .env شامل نام دامنهها و ارجاعات image است. env/*.env برای هر سرویس یک فایل secret در بر دارد. docker-compose.override.yml شامل سه جفت کلید Ed25519 به صورت YAML block scalar است، زیرا یک PEM چندخطی نمیتواند در فایل env قرار بگیرد. تمام این موارد در git-ignore لحاظ شدهاند و هیچکدام از آنها قابل تولید مجدد با مقادیر یکسان نیستند.
همین حالا این فایلها را از روی ماشین کپی کنید. از دست دادن هر کدام هزینه خاصی دارد:
- اگر رمزهای عبور store را از دست بدهید، دسترسی شما به Postgres و ClickHouse قطع میشود و بازنشانی آنها فقط از داخل containerها امکانپذیر است.
- اگر
OA_CREDENTIAL_KEYRINGرا از دست بدهید، تمام credentialهای شخص ثالث ذخیرهشده غیرقابل بازیابی خواهند بود؛ بنابراین هر کسی که حساب Stripe متصل کرده باشد، باید دوباره آن را متصل کند. - اگر
ANONYMOUS_IDENTITY_SECRETرا از دست بدهید، هویت بازدیدکنندگان بازنشانی میشود: تمام بازدیدکنندگان دیروز به عنوان کاربر جدید شمارش میشوند و این گسست در نمودارها قابل مشاهده خواهد بود. - اگر
AUTH_SECRETرا از دست بدهید، تمام نشستها (session) باطل میشوند و همه باید دوباره وارد سیستم شوند. - اگر یک کلید خصوصی امضا (signing private key) را از دست بدهید، باید جفتکلید را تغییر دهید (rotate). در این حالت چیزی از دست نمیرود.
دو secret باید در دو فایل مختلف، دقیقاً از نظر بایت یکسان باشند. ANONYMOUS_IDENTITY_SECRET در collector.env و worker.env ظاهر میشود، زیرا collector هش بازدیدکننده را محاسبه میکند و worker آن را مینویسد. OA_CREDENTIAL_KEYRING در api.env و worker.env ظاهر میشود. تمام موارد دیگر عمداً فقط به یک سرویس محدود شدهاند و اگر سرویسی secretای را دریافت کند که نباید داشته باشد، به جای اجرا شدن، متوقف میشود.
بالا آوردن استک و بررسی آن
grep OA_IMAGE .env
docker compose pull
docker compose up -d
docker compose logs -f migrate
docker compose psmigrate اسکیماهای Postgres و ClickHouse را اعمال کرده و سپس خارج میشود، بنابراین وضعیت توقف کانتینر migrate، وضعیت نهایی صحیح است. tracker-build فایل oa.js را در یک volume که توسط Caddy سرو میشود کامپایل کرده و آن هم خارج میشود. وضعیت سایر سرویسها باید در docker compose ps به صورت healthy باشد. سرویسی که در یک حلقه مدام restart میشود، تقریباً همیشه در اعتبارسنجی محیط (environment validation) شکست میخورد و لاگ، تمام مشکلات را به جای نمایش یکی در هر بار restart، در یک لیست واحد چاپ میکند. دو دلیل معمول برای این اتفاق عبارتند از: متغیری که خالی رها شده (که به جای در نظر گرفته شدن به عنوان unset، رد میشود) و secretای که در فایل سرویس اشتباه قرار گرفته است.
در معماری arm64 یا هنگام استفاده از یک branch، هیچ image منتشرشدهای وجود ندارد و باید ساخت را به صورت محلی با docker compose up -d --build انجام دهید. یک میزبان با 4 GB رم در میانه این فرآیند build با کمبود حافظه مواجه میشود. ابتدا swap اضافه کنید که فقط در زمان build مورد نیاز است:
fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabفرآیند ساخت تقریباً ده دقیقه طول میکشد. دریافت (pull) کردن imageها چند دقیقه زمان میبرد، به همین دلیل است که release imageها وجود دارند.
ایجاد اولین حساب کاربری بلافاصله پس از نصب
https://app.example.com را باز کنید. در استقراری که هنوز هیچ کاربری به آن وارد نشده است، فرم ورود نمایش داده نمیشود؛ در عوض، سیستم پیشنهاد میدهد که اولین حساب کاربری ایجاد شود. این حساب بهطور دائمی دارای دسترسیهای مدیریتی (privileged) است و تنها حسابی است که به صفحه تنظیمات استقرار دسترسی دارد. بهمحض ایجاد این حساب، مسیر 409 دیگر فرم ثبتنام را نشان نمیدهد، بنابراین هیچکس دیگری نمیتواند پس از شما به سیستم دسترسی پیدا کند. این کار را دقیقاً در لحظهای که stack آماده و سالم است انجام دهید، نه یک هفته بعد.
نصب ردیاب
یک سایت را در داشبورد اضافه کنید تا تگ مربوطه به شما داده شود. ساختار آن ثابت است:
<script
async
src="https://c.example.com/oa.js"
data-key="YOUR_TRACKING_KEY"
data-collector="https://c.example.com"
></script>آن را در بخش head صفحه قرار دهید. کلید ردیابی (tracking key) ذاتاً عمومی است، بنابراین قرار دادن آن در HTML که برای همه قابل خواندن است، مشکلی ندارد. این اسکریپت window.oa را نصب میکند و فراخوانیهایی مانند oa("track", ...) توسط یک stub در صف قرار میگیرند و پس از بارگذاری فایل، ارسال میشوند؛ بنابراین یک رویداد سفارشی که زودتر اجرا شود، از دست نمیرود. اگر بخش دیگری از صفحه از قبل مالک window.oa باشد، ردیاب به عنوان window.openanalytics نصب میشود. اگر همان سایت به عنوان یک سرویس پیازی (onion service) نیز در دسترس است، تگ را در آن نسخه قرار ندهید، زیرا اسکریپتی که از c.example.com فراخوانی میشود، بازدیدکننده Tor Browser را به شبکه عمومی (clearnet) بازمیگرداند و دو آدرس را در یک بارگذاری صفحه به هم مرتبط میکند.
سپس کل مسیر را از ابتدا تا انتها بررسی کنید:
curl -s https://c.example.com/oa.js -o /dev/null -w '%{http_code} %{size_download}\n'
curl -s https://api.example.com/health | head -c 200
docker compose logs --tail=50 worker | grep -i batchدستور اول باید 200 و چند کیلوبایت داده چاپ کند. صفحهای از سایت خود را بارگذاری کنید و سپس ظرف چند ثانیه به دنبال یک خط batch در لاگ worker بگردید. جمعآوریکننده (collector) در لحظه پذیرش رویداد، 202 پاسخ میدهد و 202 به معنای قرار گرفتن در صف است، نه ذخیره شدن. worker همان بخشی است که رویدادها را به ClickHouse منتقل میکند. اگر رویدادها پذیرفته میشوند اما در داشبورد ظاهر نمیشوند، به این معناست که worker مسدود شده است؛ افزایش مداوم عمق صف Valkey این موضوع را تأیید میکند. دلایل معمول این مشکل، اشتباه بودن اعتبارنامههای ClickHouse در worker.env یا نبود مجوز (grant) روی جدولی است که بهتازگی توسط یک migration اضافه شده است.
عمومی نگهداشتن جمعآوریکننده و محدود کردن دسترسی به داشبورد
نرمافزار Caddy درون فایل compose قرار دارد و بهطور خودکار برای هر چهار نام دامنه گواهی دریافت میکند، بنابراین مسیر پیشفرض نیازی به تنظیمات پروکسی از سوی شما ندارد. اگر سرور در حال حاضر از یک reverse proxy مبتنی بر nginx استفاده میکند، این پشته را با infra/selfhost/nginx.conf.example ارائهشده در مقابل آن قرار دهید و تنظیمات مربوط به headerها را دستنخورده باقی بگذارید:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header CF-Connecting-IP "";
proxy_set_header True-Client-IP "";
proxy_set_header Fly-Client-IP "";جمعآوریکننده (collector)، هش بازدیدکننده روزانه را از روی IP کلاینت استخراج میکند، بنابراین باید این آدرس را مستقیماً از اتصال دریافت کند و نه از headerها. عبور دادن CF-Connecting-IP از یک واسط غیرقابلاعتماد به هر تماسگیرندهای اجازه میدهد تا هر آدرسی را جعل کند؛ این کار باعث مخدوش شدن موقعیت جغرافیایی و افزایش کاذب آمار بازدیدکنندگان میشود.
دسترسیها بهطور تمیز بر اساس نام میزبان (hostname) تفکیک میشوند. c. و rt. باید برای تمام بازدیدکنندگانِ هر سایتی که اندازهگیری میکنید در دسترس باشند، بنابراین هرگز روی این دو مورد basic auth یا لیست سفید IP قرار ندهید. app. و api. فقط باید برای افرادی که وارد سیستم میشوند در دسترس باشند. احراز هویت خودِ برنامه همان چیزی است که از داشبورد محافظت میکند: ورود با رمز عبور بهصورت پیشفرض از طریق AUTH_PASSWORD_SIGNIN=enabled در env/api.env فعال است و دکمههای Google یا GitHub تنها زمانی ظاهر میشوند که client ID و client secret برای آن ارائهدهنده موجود باشد. لینکهای جادویی (Magic links) به یک سرویس انتقال ایمیل نیاز دارند؛ بدون آن، API فقط ارسال را در outbox مینویسد، بنابراین چیزی تحویل داده نمیشود و خطایی هم رخ نمیدهد. اگر سایر برنامههای self-hosted شما در حال حاضر پشت یک ورود واحد Authentik قرار دارند، از همان ابتدا تصمیم بگیرید که آیا این داشبورد به آنها ملحق میشود یا حسابهای کاربری مستقل خود را حفظ میکند، زیرا اولین حسابی که در اینجا ایجاد میکنید، برای همیشه حساب دارای دسترسی ویژه (privileged) باقی میماند.
یک تنظیم تعیین میکند که آیا داشبورد اصلاً کار میکند یا خیر. AUTH_TRUSTED_ORIGINS در env/api.env باید دقیقاً با مبدأ (origin) داشبورد مطابقت داشته باشد. در صورت اشتباه بودن یا نبودن آن، API هیچ header مربوط به CORS (اشتراکگذاری منابع بینمبدأ) ارسال نمیکند، مرورگر تمام درخواستها را رد میکند و شما با داشبوردی مواجه میشوید که ظاهر آن بارگذاری میشود اما هیچ دادهای نمایش نمیدهد، در حالی که docker compose ps وضعیت همه چیز را سالم گزارش میکند.
هنگامی که در حال تنظیم پیکربندی پروکسی هستید، ترافیک خودکار را مدیریت کنید. خزندهها (crawlers) نیز مانند هر چیز دیگری به جمعآوریکننده متصل میشوند و بازدیدهای آنها در ClickHouse و آمار شما ثبت میشود. مسدود کردن خزندههای هوش مصنوعی در سطح سرور باعث میشود بخشی از این ترافیک پیش از آنکه دقت آمار شما را کاهش دهد و فضای دیسک را اشغال کند، از دیتابیس حذف شود.
معنای بدون کوکی (cookieless) در اینجا چیست و چه هزینهای برای شما دارد
در اینجا هیچ کوکی وجود ندارد. هویت بازدیدکننده یک هش نمکزده (salted hash) است، نمک (salt) هر روز تغییر میکند و آدرسهای IP خام هرگز ذخیره نمیشوند. موقعیت جغرافیایی بهصورت محلی و با استفاده از فایل DB-IP روی دیسک خودتان تعیین میشود، بنابراین هیچ جستجویی درباره بازدیدکننده از میزبان خارج نمیشود. محلی نگهداشتن جستجوها، فروشنده را حذف میکند نه دادهها را؛ این همان محدودیتی است که هنگام اجرای نمونه شخصی SearXNG با آن مواجه میشوید، جایی که IP سرور شما همان چیزی است که موتورهای جستجو میبینند.
مزیت این روش، نبودِ شناسهای است که روی دستگاه بازدیدکننده باقی بماند؛ این دقیقاً همان عاملی است که یک ردیاب را مشمول قوانین رضایت ePrivacy اتحادیه اروپا میکند. به همین دلیل، تنظیمات مبتنی بر دادههای تجمیعی (Aggregate-only) معمولاً بدون بنر رضایت اجرا میشوند. GDPR همچنان بر هر آنچه ذخیره میکنید و مدت زمان نگهداری آن حاکم است و تصمیمگیرنده نهایی در پرونده شما، مشاور حقوقی خودتان است، نه یک فایل README.
هزینه این روش، از دست دادن امکان شناسایی هویت در طول چند روز است. چرخش روزانه نمک به این معناست که فردی که در روز دوشنبه و دوباره در روز چهارشنبه بازدید میکند، طبق طراحی و بدون هیچ راهکار جایگزینی، دو بازدیدکننده مجزا شمرده میشود. آمار بازدیدکنندگان منحصربهفرد روزانه دقیق است. آمار هفتگی و ماهانه از مجموع آمار روزانه ساخته میشوند و تعداد بازدیدها را بیش از حد واقعی نشان میدهند، بنابراین هرگونه شاخص "بازدیدکننده بازگشتی" در بازههای زمانی طولانی، چیزی را که نامش نشان میدهد اندازهگیری نمیکند. نشستها (Sessions) و مسیرهای حرکت کاربر در طول یک روز قابلاعتماد هستند. چرخش ANONYMOUS_IDENTITY_SECRET اثری مشابه مرز روزانه دارد، بنابراین با این چرخش بهعنوان یک تغییر در دادهها برخورد کنید، نه بهعنوان یک اقدام معمول نظافتی.
جمعآوریکننده به Do Not Track و Global Privacy Control احترام میگذارد؛ این سیگنال مرورگر به سایت میگوید که دادههای شخصی را نفروشد یا به اشتراک نگذارد. تگ اسکریپت سوئیچهای خاص خود را برای همین منظور دارد: data-respect-gpc، data-respect-dnt و data-require-consent که تا زمان اخذ رضایت، تمام عملیات جمعآوری را متوقف میکند و پاسخ را در localStorage تحت کلید oa.consent به خاطر میسپارد. تنظیم data-storage="none" ذخیرهسازی در مرورگر را بهطور کامل غیرفعال میکند.
چرا دیسک پس از شش ماه پر میشود
این موضوع عامل اصلی از کار افتادن یک سرور تحلیلگر (analytics) خودمیزبان است و معمولاً رویدادها (events) دلیل آن نیستند.
با تصاویر (images) شروع کنید. هر release ده تصویر منتشر میکند که حجم آنها روی دیسک به حدود 13 گیگابایت میرسد. یک ارتقا (upgrade) نسل جدید را پیش از حذف نسل قدیمی دانلود میکند، بنابراین برای مدتی شما دو نسل را همزمان نگه میدارید. این بخش بزرگی از نیاز 25 گیگابایتی است، حتی پیش از آنکه اولین بازدید صفحه ثبت شود.
سپس نوبت به اسنپشاتها میرسد. snapshot.sh استک را متوقف میکند، از هر دو volume داده به همراه تمام secretها آرشیو میگیرد و دوباره راهاندازی میکند. در اینجا فقط کپیهای سرد (cold copies) ایمن هستند، زیرا ClickHouse بخشها را در پسزمینه ادغام میکند و کپیبرداری در حین ادغام، دادههای منسجمی ایجاد نمیکند. upgrade.sh بهطور خودکار پیش از هر ارتقا یک اسنپشات میگیرد، بنابراین آرشیوها روی همان دیسک انباشته میشوند تا زمانی که شما محدودیتی برای آنها تعیین کنید.
./snapshot.sh create --label before-something-risky
./snapshot.sh list
./snapshot.sh --keep 3روی میزبانی که به محدودیت ظرفیت نزدیک است، پیش از ارتقا، نسل قبلی را پاکسازی کنید. این کار در حین اجرای استک ایمن است، زیرا تصاویری که containerهای در حال اجرا را پشتیبانی میکنند، همچنان ارجاع داده میشوند:
docker image prune -a -fسپس خود رویدادها. ClickHouse دادههای ستونی را بهشدت فشرده میکند، بنابراین حجم رویدادهای خام کندتر از آنچه اکثر افراد انتظار دارند رشد میکند و جداول rollup که داشبورد میخواند، در مقایسه با جدول خام کوچک هستند. بهجای حدس زدن، اندازهگیری کنید:
docker system df -v
docker compose exec clickhouse df -h /var/lib/clickhouseبرای مشاهده آمار هر جدول، این دستور را با اعتبارنامههای ClickHouse که generator در مسیر infra/selfhost/env/ نوشته است، اجرا کنید:
SELECT table, formatReadableSize(sum(bytes_on_disk)) AS size, sum(rows) AS row_count
FROM system.parts
WHERE active
GROUP BY table
ORDER BY sum(bytes_on_disk) DESC;این اندازهگیری را در هفته اول و دوباره در هفته چهارم انجام دهید. دو نقطه به شما نرخ رشد میدهد و نرخ رشد به شما میگوید که چه زمانی نیاز به تغییر اندازه volume دارید. راهنمای خودمیزبانی تا اوت 2026 هیچ تنظیماتی برای retention یا time-to-live رویدادهای خام ارائه نمیدهد، بنابراین دیسک را بر اساس نرخ اندازهگیریشده خود تنظیم کنید و فرض نکنید که ردیفهای قدیمی خودبهخود منقضی میشوند.
یک تله در حذف دادهها وجود دارد که پیش از مواجهه با آن باید بدانید. حذف یک سایت یا حساب، کارهایی را برای worker در صف قرار میدهد و آن worker نیاز دارد که CLICKHOUSE_MAINTENANCE_USER و CLICKHOUSE_MAINTENANCE_PASSWORD تنظیم شده باشند و یک کاربر oa_maintenance منطبق در ClickHouse وجود داشته باشد. بدون این موارد، حذف برای همیشه در صف باقی میماند. سایت از داشبورد ناپدید میشود اما تمام ردیفها روی دیسک باقی میمانند، بنابراین ظاهر پاکسازی ایجاد میشود اما هیچ فضایی آزاد نمیشود.
ارتقاها و سه هزینه مرتبط
git fetch --tags
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./upgrade.shابزار upgrade.sh پیش از انجام هر عملیاتی، سه هزینه را اعلام میکند. Downtime واقعی است: رویدادهایی که در زمان از دسترس خارج بودن collector تلاش برای ارسال آنها صورت میگیرد، از دست میروند، زیرا tracker مجدداً برای ارسال آنها تلاش نمیکند. Rollback باعث از دست رفتن دادهها میشود، زیرا rollback.sh --to backups/<snapshot> هر دو ذخیرهگاه را بهطور کامل جایگزین کرده و تمام ردیفهایی که پس از گرفتن آن snapshot نوشته شدهاند را حذف میکند. دیسک، هزینه سوم است که به همان انباشت snapshot که پیشتر توضیح داده شد، اشاره دارد.
دو قانون مربوط به restart وجود دارد که اشتباه در آنها رایج است. query gateway را پیش از API بالا بیاورید، زیرا API جدیدتر فیلدهایی را برای query ارسال میکند که gateway قدیمیتر آنها را رد میکند. همچنین ClickHouse به جای restart، نیاز به recreate دارد، زیرا docker compose restart محیط اصلی container را مجدداً استفاده میکند و تغییرات شما را بدون اطلاع نادیده میگیرد:
docker compose up -d --force-recreate clickhouseداشبورد نیز تلههای مشابهی دارد. سه مبدأ NEXT_PUBLIC_* در env/web.env درون bundle مرورگر کامپایل میشوند و هنگام شروع container جایگزین میگردند؛ بنابراین، داشبوردی که با hostname اشتباه فراخوانی میشود، با docker compose up -d --force-recreate web اصلاح میشود و نه با restart. لاگ web container مبدأهایی که با آنها شروع شده را چاپ میکند؛ این سریعترین راه برای تأیید اعمال اصلاحات است.
اگر ClickHouse پس از ویرایش فایل پیکربندی از شروع خودداری کرد، خط اول لاگ آن را بخوانید. خطی که با oa-entrypoint: شروع میشود، نشاندهنده این است که entrypoint مقداری که تعیین کردهاید را رد میکند. هر پیام دیگری معمولاً به این معنی است که فایل پیکربندی XML نامعتبر است؛ رایجترین علت آن، وجود دو خط تیره (double hyphen) در داخل یک کامنت XML است که در آنجا غیرمجاز محسوب میشود.
مجوز AGPL-3.0 و نام پروژه
این کد تحت مجوز AGPL-3.0 منتشر شده است. اجرای نسخهٔ بدون تغییر آن برای وبسایتهای شخصی، هیچگونه تعهدی برای انتشار کد ایجاد نمیکند. تعهد زمانی آغاز میشود که شما کد را تغییر دهید و آن نسخهٔ اصلاحشده را بهعنوان یک سرویس شبکه اجرا کنید: در این صورت، مجوز شما را ملزم میکند که سورسکد تغییریافته را به کاربران آن سرویس ارائه دهید. این موضوع شامل ارائه داشبورد به کلاینتها در نمونهٔ (instance) شما و همچنین گنجاندن آن در محصولی که به فروش میرسانید میشود. نگهداری تغییرات در یک fork عمومی، بدون نیاز به طی کردن مراحل اضافی، این تعهد را برآورده میکند.
برند پروژه از کد آن جداست. نام "OpenAnalytics" و دامنهٔ میزبانیشدهٔ پروژه، معرف نمونهای است که توسط نویسندگان آن اداره میشود و بخشی از امتیازات اعطاشده در مجوز نیستند. استقرار (deployment) شما، نرمافزار را بدون استفاده از این برند اجرا میکند؛ بنابراین پیش از ارائهٔ سرویس به مشتریان، نامی اختصاصی برای آن انتخاب کنید.
FAQ
آیا میتوانم OpenAnalytics را روی یک VPS با 1 GB رم اجرا کنم؟
خیر. این پروژه به حدود 4 GB رم و 25 GB فضای دیسک خالی نیاز دارد، زیرا هر استقرار شامل شش سرویس برنامه در کنار Postgres، ClickHouse و دو نمونه Valkey است. ClickHouse به تنهایی پردازش سبکی نیست. روی یک سرور 1 GB، کانتینرها شروع به کار میکنند اما قابلیت out-of-memory killer در هسته سیستمعامل یکی از آنها، معمولاً ClickHouse، را متوقف میکند. اگر محدودیت سختافزاری شما 1 GB است، از ابزارهای تکفایلی مانند GoatCounter استفاده کنید که روی SQLite اجرا میشوند و به دیتابیس خارجی نیاز ندارند.
آیا با OpenAnalytics به بنر کوکی نیاز دارم؟
این پرسشی حقوقی است، اما واقعیتهای فنی به نفع شماست. هیچ کوکی استفاده نمیشود، هویت بازدیدکننده یک هش نمکزده (salted hash) است که روزانه تغییر میکند و آدرسهای IP خام هرگز ذخیره نمیشوند؛ بنابراین هیچ داده پایداری برای شناسایی بازدیدکننده ثبت نمیشود. با این حال، مقررات GDPR همچنان بر آنچه ذخیره میکنید و مدت زمان نگهداری آن حاکم است. اگر میخواهید جمعآوری دادهها صریحاً مشروط به اجازه کاربر باشد، ویژگی data-require-consent را در تگ اسکریپت تنظیم کنید: در این حالت، ردیاب تا زمانی که رضایت داده نشود هیچ دادهای جمعآوری نمیکند و پاسخ کاربر را در localStorage تحت oa.consent نگه میدارد.
چرا رویدادها کد 202 برمیگردانند اما در داشبورد ظاهر نمیشوند؟
202 به این معنی است که جمعآوریکننده رویداد را پذیرفته و در صف قرار داده است، نه اینکه آن را ذخیره کرده باشد. Worker این صف را به ClickHouse منتقل میکند، بنابراین داشبورد خالی با وجود درخواستهای موفق، نشاندهنده مشکل در Worker است. docker compose logs --tail=50 worker را بخوانید و عمق صف Valkey را بررسی کنید. صفی که مدام رشد میکند به این معنی است که Worker مسدود شده است؛ دلایل معمول آن، اشتباه بودن اعتبارنامههای ClickHouse در worker.env یا نبود مجوز دسترسی روی جدولی است که در مهاجرت (migration) اخیر ایجاد شده است.
چرا با وجود سلامت همه کانتینرها، داشبورد خالی است؟
ابتدا AUTH_TRUSTED_ORIGINS را در env/api.env بررسی کنید. این مقدار باید دقیقاً با مبدأ (origin) داشبورد مطابقت داشته باشد؛ در غیر این صورت API هدرهای CORS را ارسال نمیکند، مرورگر تمام درخواستها را رد میکند و شما طرحبندی داشبورد را میبینید اما دادهای نمایش داده نمیشود. مورد دوم، بررسی سه مقدار NEXT_PUBLIC_* در env/web.env است که هنگام شروع کانتینر وب جایگذاری میشوند. اصلاح آنها مستلزم docker compose up -d --force-recreate web است، زیرا یک restart ساده مقادیر قدیمی را حفظ میکند.
آیا مجوز AGPL-3.0 مانع ارائه این سرویس به مشتریان میشود؟
خیر، این مجوز تنها یک شرط دارد. اگر کد را بدون تغییر اجرا کنید، به کسی بدهکار نیستید. اگر آن را تغییر دهید و نسخه اصلاحشده را به عنوان سرویسی ارائه دهید که دیگران از آن استفاده میکنند، باید سورسکد تغییریافته خود را به آن کاربران ارائه دهید که با ایجاد یک fork عمومی برآورده میشود. بهطور جداگانه، نام "OpenAnalytics" همراه با کد مجوزدهی نشده است، بنابراین هر چیزی که میفروشید باید نام اختصاصی خود را داشته باشد.