SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

آموزش میزبانی شخصی OpenAnalytics روی VPS

برای نصب OpenAnalytics به 4 گیگابایت رم، 25 گیگابایت فضای دیسک و 4 رکورد DNS نیاز دارید. این راهنما نحوه استقرار سرویس‌های ClickHouse، Postgres و Valkey را شرح می‌دهد.

پیش‌نیازها، پیش از گام نخست

برای میزبانی شخصی OpenAnalytics، به یک VPS لینوکسی با حدود 4 گیگابایت رم، 25 گیگابایت فضای دیسک آزاد، Docker به همراه افزونه Compose و چهار رکورد DNS که از قبل به سرور اشاره می‌کنند، نیاز دارید. این واقعیتِ ماجراست و باید پیش از اجرای اولین دستور مطرح شود.

این پشته شامل شش سرویس نرم‌افزاری و سه ذخیره‌ساز داده است. Postgres وظیفه مدیریت کنترل‌پلن را بر عهده دارد: حساب‌های کاربری، سایت‌ها، کلیدهای API و لینک‌های اشتراک‌گذاری. ClickHouse رویدادهای خام و داده‌های تجمیعی که داشبورد می‌خواند را ذخیره می‌کند. Valkey دو بار اجرا می‌شود؛ یک بار به عنوان صف رویدادهای پایدار و بار دیگر به عنوان کش که سیستم می‌تواند از دست دادن داده‌های آن را تحمل کند، زیرا این دو وظیفه به سیاست‌های تخلیه (eviction) متفاوتی نیاز دارند. تنها یک پردازش، یعنی درگاه پرس‌وجو (query gateway)، اجازه خواندن از ClickHouse را دارد و پیش از اجرای هر پرس‌وجو، امضای Ed25519 روی پاکتِ (envelope) آن را تأیید می‌کند.

اگر به دنبال یک فایل اجرایی واحد و یک فایل پیکربندی هستید، این راهنما مناسب شما نیست. GoatCounter گزینه تک‌فایلی در این دسته‌بندی است: یک فایل اجرایی Go، به صورت پیش‌فرض با SQLite و بدون نیاز به هیچ پایگاه داده خارجی. پشته سنگین‌تر در اینجا، قابلیت‌هایی مانند قیف‌های تبدیل (funnels)، معیارهای حیاتی وب (web vitals)، انتساب درآمد از حساب Stripe شخصی و یک سرور MCP (پروتکل زمینه مدل) را در اختیار شما قرار می‌دهد. انتخاب بین ابزارهای تحلیل خود-میزبان پستی است که این تفاوت‌ها را بررسی می‌کند. این راهنما فرض را بر این می‌گذارد که شما قبلاً تصمیم خود را گرفته‌اید.

ابتدا چهار رکورد DNS را به سمت سرور تنظیم کنید

پیش از شروع هر کاری، چهار زیردامنه باید به IP عمومی سرور اشاره کنند؛ زیرا Caddy در اولین اجرا درخواست صدور گواهی Let's Encrypt را ارسال می‌کند و اگر نام دامنه هنوز به سرور اشاره نکند، چالش (challenge) با شکست مواجه می‌شود.

  • app.example.com برای سرویس‌دهی به داشبورد.
  • api.example.com برای سرویس‌دهی به API و callbackهای OAuth.
  • c.example.com برای سرویس‌دهی به جمع‌آوری‌کننده (collector) و اسکریپت ردیاب (tracker).
  • rt.example.com برای سرویس‌دهی به جریان داده‌های بلادرنگ (realtime 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، تگ‌های پیش‌انتشار (pre-release) را نادیده می‌گیرد تا شما به جای نسخه‌های کاندیدای انتشار، روی جدیدترین نسخه پایدار قرار بگیرید. --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 ps

migrate اسکیماهای Postgres و ClickHouse را اعمال کرده و سپس خارج می‌شود، بنابراین وضعیت متوقف‌شده برای کانتینر migrate وضعیت نهایی درستی است. tracker-build نیز oa.js را در یک volume که توسط Caddy سرو می‌شود کامپایل کرده و خارج می‌شود. وضعیت سایر سرویس‌ها در docker compose ps باید healthy باشد. اگر سرویسی در یک حلقه مدام در حال restart شدن است، تقریباً همیشه به دلیل عدم تأیید اعتبار محیط (environment validation) است و لاگ‌ها تمام مشکلات را در یک لیست واحد نمایش می‌دهند، نه به صورت یک مشکل در هر بار restart. دو دلیل معمول برای این اتفاق عبارتند از: خالی ماندن یک متغیر که به جای در نظر گرفته شدن به عنوان unset، رد می‌شود، و قرار گرفتن یک secret در فایل سرویس اشتباه.

در معماری arm64 یا هنگام استفاده از یک branch، هیچ image منتشرشده‌ای وجود ندارد و باید build را به صورت محلی با 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

فرآیند build حدود ده دقیقه زمان می‌برد. دریافت (pull) کردن imageها چند دقیقه طول می‌کشد، به همین دلیل است که release imageها ارائه شده‌اند.

ایجاد فوری اولین حساب کاربری

https://app.example.com را باز کنید. در استقراری که هنوز کسی به آن وارد نشده است، فرم ورود نمایش داده نمی‌شود؛ در عوض، امکان ایجاد اولین حساب کاربری فراهم می‌گردد. این حساب به‌طور دائمی دارای دسترسی‌های مدیریتی است و تنها حسابی است که صفحه تنظیمات استقرار را مشاهده می‌کند. پس از ایجاد این حساب، مسیر 409 پاسخ‌گو خواهد بود، بنابراین هیچ‌کس دیگری نمی‌تواند پس از شما به سیستم دسترسی پیدا کند. این کار را بلافاصله پس از اطمینان از سلامت stack انجام دهید و آن را به هفته‌های بعد موکول نکنید.

نصب ردیاب

یک سایت در داشبورد اضافه کنید تا تگ مربوطه به شما ارائه شود. ساختار آن ثابت است:

<script
  async
  src="https://c.example.com/oa.js"
  data-key="YOUR_TRACKING_KEY"
  data-collector="https://c.example.com"
></script>

آن را در بخش head صفحه قرار دهید. کلید ردیابی ذاتاً عمومی است، بنابراین قرار دادن آن در HTML که برای همه قابل خواندن است، مشکلی ندارد. این اسکریپت window.oa را نصب می‌کند و فراخوانی‌هایی مانند oa("track", ...) توسط یک stub در صف قرار می‌گیرند و پس از بارگذاری فایل، ارسال می‌شوند؛ بنابراین رویدادهای سفارشی که زودتر اجرا می‌شوند، از دست نمی‌روند. اگر بخش دیگری از صفحه از قبل مالک window.oa باشد، ردیاب به عنوان window.openanalytics نصب می‌شود.

سپس کل مسیر را از ابتدا تا انتها بررسی کنید:

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 ارائه‌شده در مقابل آن قرار دهید و مدیریت هدرها را دست‌نخورده باقی بگذارید:

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 "";

جمع‌آوری‌کننده، هش بازدیدکننده روزانه را از IP کلاینت استخراج می‌کند، بنابراین باید آن آدرس را مستقیماً از اتصال دریافت کند و نه از هدرها. عبور دادن CF-Connecting-IP از یک واسط غیرقابل‌اعتماد به هر تماس‌گیرنده‌ای اجازه می‌دهد هر آدرسی را جعل کند که این کار باعث مخدوش شدن موقعیت جغرافیایی و افزایش کاذب تعداد بازدیدکنندگان می‌شود.

دسترسی‌ها به‌طور دقیق بر اساس نام دامنه تفکیک می‌شوند. c. و rt. باید برای تمام بازدیدکنندگانِ هر سایتی که اندازه‌گیری می‌کنید در دسترس باشند، بنابراین هرگز از basic auth یا لیست سفید IP برای این دو استفاده نکنید. app. و api. فقط باید برای افرادی که وارد سیستم می‌شوند در دسترس باشند. احراز هویت خودِ برنامه همان چیزی است که از داشبورد محافظت می‌کند: ورود با رمز عبور به‌صورت پیش‌فرض از طریق AUTH_PASSWORD_SIGNIN=enabled در env/api.env فعال است و دکمه‌های Google یا GitHub تنها زمانی ظاهر می‌شوند که client ID و client secret برای آن ارائه‌دهنده وجود داشته باشد. لینک‌های جادویی (Magic links) به یک سرویس انتقال ایمیل نیاز دارند؛ بدون آن، API فقط ارسال را در outbox ثبت می‌کند، بنابراین هیچ ایمیلی تحویل داده نمی‌شود و خطایی هم رخ نمی‌دهد.

یک تنظیم تعیین می‌کند که آیا داشبورد اصلاً کار می‌کند یا خیر. AUTH_TRUSTED_ORIGINS در env/api.env باید دقیقاً با مبدأ (origin) داشبورد مطابقت داشته باشد. در صورت اشتباه بودن یا نبودن آن، API هیچ هدر CORS (اشتراک‌گذاری منابع متقاطع) ارسال نمی‌کند، مرورگر تمام درخواست‌ها را رد می‌کند و شما با داشبوردی مواجه می‌شوید که طرح‌بندی آن بارگذاری می‌شود اما هیچ داده‌ای نمایش نمی‌دهد، در حالی که docker compose ps وضعیت را سالم گزارش می‌کند.

هنگامی که در حال تنظیم پیکربندی پروکسی هستید، ترافیک خودکار را مدیریت کنید. خزنده‌ها (Crawlers) نیز مانند هر چیز دیگری به جمع‌آوری‌کننده متصل می‌شوند و بازدیدهای صفحات آن‌ها در ClickHouse و آمار شما ثبت می‌شود. مسدود کردن خزنده‌های هوش مصنوعی در سطح سرور باعث می‌شود بخشی از این ترافیک پیش از آنکه دقت آمار شما را کاهش دهد و فضای دیسک را اشغال کند، از پایگاه داده حذف شود.

معنای cookieless در اینجا چیست و چه هزینه‌ای برای شما دارد

در اینجا هیچ کوکی‌ای وجود ندارد. هویت بازدیدکننده یک هش نمک‌زده (salted hash) است، نمک (salt) هر روز تغییر می‌کند و آدرس‌های IP خام هرگز ذخیره نمی‌شوند. موقعیت جغرافیایی به‌صورت محلی و با استفاده از فایل DB-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) شخصی را از کار می‌اندازد و معمولاً دلیل آن، حجم رویدادها نیست.

با imageها شروع کنید. هر release شامل ده image است که مجموعاً حدود 13 گیگابایت از فضای دیسک را اشغال می‌کنند. هنگام ارتقا، نسخه جدید پیش از حذف نسخه قدیمی دانلود می‌شود؛ بنابراین برای مدتی، شما دو نسل از imageها را نگه می‌دارید. این بخش عمده‌ای از نیاز 25 گیگابایتی است، حتی پیش از آنکه اولین بازدید صفحه ثبت شود.

سپس نوبت به snapshotها می‌رسد. snapshot.sh سرویس را متوقف کرده، از هر دو volume داده به همراه تمام secretها آرشیو تهیه می‌کند و دوباره سرویس را بالا می‌آورد. در اینجا فقط کپی‌های سرد (cold copies) ایمن هستند، زیرا ClickHouse بخش‌های داده را در پس‌زمینه ادغام می‌کند و کپی‌برداری در حین ادغام، منجر به داده‌های ناسازگار می‌شود. upgrade.sh به‌طور خودکار پیش از هر ارتقا یک snapshot می‌گیرد، بنابراین این آرشیوها تا زمانی که محدودیتی برای آن‌ها تعیین نکنید، روی همان دیسک انباشته می‌شوند.

./snapshot.sh create --label before-something-risky
./snapshot.sh list
./snapshot.sh --keep 3

روی میزبانی که به محدودیت فضا نزدیک است، پیش از ارتقا، نسل قبلی را پاکسازی کنید. این کار در حین اجرای سرویس ایمن است، زیرا imageهایی که 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 دارید. راهنمای self-hosting تا اوت 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 آن‌ها را دوباره تلاش (retry) نمی‌کند. Rollback باعث از دست رفتن داده‌ها می‌شود، زیرا rollback.sh --to backups/<snapshot> هر دو store را به‌طور کامل جایگزین کرده و تمام ردیف‌هایی که پس از گرفتن آن 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) شخصی شما و همچنین گنجاندن آن در محصولی که به فروش می‌رسانید، می‌شود. نگهداری تغییرات در یک فورک عمومی، بدون نیاز به طی کردن مراحل اضافی، این شرط را برآورده می‌کند.

برند پروژه از کد آن جداست. نام "OpenAnalytics" و دامنه میزبانی‌شده پروژه، معرف نمونه‌ای است که توسط نویسندگان آن اداره می‌شود و بخشی از حقوق اعطایی مجوز نیست. استقرار شما، نرم‌افزار را بدون استفاده از برند اصلی اجرا می‌کند؛ بنابراین پیش از ارائه سرویس به مشتریان، نامی اختصاصی برای آن انتخاب کنید.

FAQ

آیا می‌توانم OpenAnalytics را روی یک VPS با 1 گیگابایت رم اجرا کنم؟

خیر. این پروژه به حدود 4 گیگابایت رم و 25 گیگابایت فضای دیسک آزاد نیاز دارد، زیرا هر استقرار شامل اجرای شش سرویس برنامه در کنار Postgres، ClickHouse و دو نمونه Valkey است. ClickHouse به‌تنهایی یک پردازش سبک نیست. روی یک سرور 1 گیگابایتی، کانتینرها شروع به کار می‌کنند اما قابلیت out-of-memory killer در هسته سیستم‌عامل یکی از آن‌ها، معمولاً ClickHouse، را متوقف می‌کند. اگر محدودیت سخت‌افزاری شما 1 گیگابایت است، از ابزارهای تک‌فایلی مانند GoatCounter استفاده کنید که روی SQLite اجرا می‌شوند و به دیتابیس خارجی نیاز ندارند.

آیا با OpenAnalytics به بنر کوکی نیاز دارم؟

این پرسشی است که باید با وکیل خود مطرح کنید، اما واقعیت‌های فنی به نفع شماست. هیچ کوکی‌ای وجود ندارد، هویت بازدیدکننده یک هش نمک‌زده (salted hash) است که روزانه تغییر می‌کند و آدرس‌های IP خام هرگز ذخیره نمی‌شوند؛ بنابراین هیچ داده پایداری برای شناسایی بازدیدکننده نوشته نمی‌شود. با این حال، مقررات GDPR همچنان بر آنچه ذخیره می‌کنید و مدت زمان نگهداری آن حاکم است. اگر می‌خواهید جمع‌آوری داده‌ها صریحاً با اجازه کاربر انجام شود، ویژگی data-require-consent را روی تگ اسکریپت تنظیم کنید: در این صورت، ردیاب تا زمانی که رضایت داده نشود هیچ داده‌ای جمع‌آوری نمی‌کند و پاسخ کاربر را در localStorage تحت oa.consent ذخیره می‌کند.

چرا رویدادها کد 202 برمی‌گردانند اما در داشبورد ظاهر نمی‌شوند؟

202 به این معنی است که جمع‌آوری‌کننده (collector) رویداد را پذیرفته و در صف قرار داده است، نه اینکه آن را ذخیره کرده باشد. پردازشگر (worker) این صف را به ClickHouse منتقل می‌کند، بنابراین داشبورد خالی با وجود درخواست‌های موفق، نشان‌دهنده مشکل در پردازشگر است. docker compose logs --tail=50 worker را بخوانید و عمق صف Valkey را بررسی کنید. صفی که مدام بزرگ‌تر می‌شود به این معنی است که پردازشگر مسدود شده است؛ دلایل معمول آن، اشتباه بودن اعتبارنامه‌های ClickHouse در worker.env یا عدم دسترسی (grant) روی جدولی است که در مهاجرت (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" همراه با کد مجوزدهی نشده است، بنابراین هر چیزی که می‌فروشید باید نام اختصاصی خود را داشته باشد.