SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

استضافة OpenAnalytics ذاتياً على خادم VPS

تعرّف إلى المتطلبات الفعلية قبل البدء: ClickHouse وPostgres وValkey، وذاكرة 4 GB، ومساحة خالية 25 GB، وأربعة سجلات DNS، ثم ثبّت النظام وتابع ما يملأ القرص.

البصمة قبل الخطوة الأولى

لاستضافة OpenAnalytics ذاتياً، تحتاج إلى VPS يعمل بنظام Linux بذاكرة RAM سعتها نحو 4 GB، ومساحة قرص خالية قدرها 25 GB، وDocker مع إضافة Compose، وأربعة سجلات DNS تشير مسبقاً إلى الخادم. هذه هي المتطلبات الفعلية، ويجب توضيحها قبل تنفيذ الأمر الأول لا بعده.

تتكون الحزمة من ست خدمات تطبيق وثلاثة مخازن بيانات. يحتفظ Postgres بمستوى التحكم: الحسابات، والمواقع، ومفاتيح API، وروابط المشاركة. يحتفظ ClickHouse بالأحداث الخام وعمليات التجميع التي تقرؤها لوحة المعلومات. يعمل Valkey مرتين: مرة كقائمة انتظار دائمة للأحداث، ومرة كذاكرة تخزين مؤقت يمكن للنظام فقدانها، لأن هاتين الوظيفتين تحتاجان إلى سياستي إخلاء متعاكستين. لا يُسمح إلا لعملية واحدة، وهي بوابة الاستعلامات، بقراءة ClickHouse، وتتحقق من توقيع Ed25519 في كل غلاف استعلام قبل تنفيذه.

إذا كنت تريد ملفاً تنفيذياً واحداً وملف إعداد واحداً، فهذا المنتج لا يحقق ذلك. يُعد GoatCounter الخيار ذا الملف التنفيذي الواحد في هذه الفئة: ملف Go تنفيذي واحد، وSQLite افتراضياً، ومن دون أي قاعدة بيانات خارجية. تتيح لك الحزمة الأثقل إنشاء مسارات التحويل، وقياس مؤشرات الويب، وإسناد الإيرادات من حساب Stripe الخاص بك، وخادم MCP (بروتوكول سياق النموذج). تتناول مقالة اختيار أدوات التحليلات المستضافة ذاتياً هذه المفاضلة. يفترض هذا الدليل أنك اتخذت القرار مسبقاً.

وجّه سجلات DNS الأربعة إلى الخادم أولاً

يجب أن تُحلّ أربعة نطاقات فرعية إلى عنوان IP العام للخادم قبل أن تبدأ، لأن Caddy يطلب شهادات Let's Encrypt عند التشغيل الأول، ويفشل التحدي إذا لم يكن الاسم قابلاً للحل بعد.

  • app.example.com يقدّم لوحة التحكم.
  • api.example.com يقدّم API واستدعاءات OAuth الراجعة.
  • c.example.com يقدّم الجامع والبرنامج النصي للتتبّع.
  • rt.example.com يقدّم البث الآني.

استخدم أربعة سجلات A، أو سجلاً واحداً من نوع A وثلاثة سجلات CNAME تشير إليه. أكّد ذلك باستخدام dig +short app.example.com قبل المتابعة. قد يظل الاسم الذي أضفته قبل دقيقة مخزّناً مؤقتاً كـNXDOMAIN لدى محلّل الأسماء الذي تستخدمه Let's Encrypt، لذلك يجدر بك انتظار فشل محاولة الشهادة الأولى وقراءة سجلات Caddy. إعادة تشغيل التثبيت لا تسرّع انتشار DNS.

كيفية استضافة OpenAnalytics ذاتياً باستخدام Docker Compose

تحقق من إصدار موسوم. الفرع الافتراضي مخصص للتطوير، بينما يطابق الوسم الخاص بالإصدار الصور المنشورة فعلياً. تفترض الأوامر أدناه أن 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' في سطر جلب المستودع الوسوم الخاصة بالإصدارات التجريبية، لذلك تصل إلى أحدث إصدار مستقر بدلاً من إصدار مرشح للإصدار. يجلب --with-geoip قاعدة بيانات المدن من DB-IP أثناء التوليد. إذا تخطيته، فستحمل كل فعالية قيمة بلد فارغة، وبالتالي لن يعرض منظور البيانات الجغرافية أي شيء. يمكنك إضافتها لاحقاً بتشغيل infra/selfhost/geoip/fetch-dbip.sh، وضبط GEOIP_DB_PATH=/geoip/dbip-city-lite.mmdb في env/collector.env، ثم إعادة إنشاء الجامع باستخدام docker compose up -d --force-recreate collector. تُحدَّث قاعدة البيانات شهرياً، لذلك كرر عملية الجلب شهرياً، وإلا ستصبح بيانات المدن لديك قديمة وغير دقيقة.

انسخ الأسرار المُولَّدة احتياطياً قبل المتابعة

يكتب المولِّد ثلاثة أشياء. يحتوي .env على أسماء النطاقات ومراجع الصور. ويحتوي env/*.env على ملف أسرار واحد لكل خدمة. ويحتوي docker-compose.override.yml على ثلاثة أزواج من مفاتيح Ed25519 بصيغة YAML block scalars، لأن PEM متعدد الأسطر لا يمكن وضعه في ملف env. كل هذه الملفات مستثنى من Git، ولا يمكن إعادة توليدها بالقيم نفسها.

انسخ هذه الملفات خارج الجهاز الآن. يترتب على فقدان كل منها أثر محدد:

  • إذا فقدت كلمات مرور مخازن البيانات، فلن تتمكن من تسجيل الدخول إلى Postgres وClickHouse. ولا يمكن إعادة تعيينها إلا من داخل الحاويات.
  • إذا فقدت OA_CREDENTIAL_KEYRING، فلن يمكن استرداد أي بيانات اعتماد مخزنة لخدمات خارجية، ولذلك يجب على كل من ربط حساب Stripe أن يربطه مرة أخرى.
  • إذا فقدت ANONYMOUS_IDENTITY_SECRET، فستُعاد تهيئة هوية الزوار: سيُحتسب جميع زوار الأمس كزوار جدد، وسيظهر الانقطاع في المخططات.
  • إذا فقدت AUTH_SECRET، فستُلغى صلاحية كل جلسة، وسيتعين على الجميع تسجيل الدخول مرة أخرى.
  • إذا فقدت مفتاحاً خاصاً للتوقيع، فأعد تدوير زوج المفاتيح. لا يضيع أي شيء.

يجب أن يكون سِرّان متطابقين بايتاً ببايت في ملفين لكل منهما. يظهر ANONYMOUS_IDENTITY_SECRET في collector.env وworker.env، لأن جامع البيانات يحسب تجزئة الزائر، بينما يكتبها العامل. ويظهر OA_CREDENTIAL_KEYRING في api.env وworker.env. أما كل ما عدا ذلك، فهو محصور عمداً في خدمة واحدة بالضبط، وإذا مُنحت خدمة سراً لا يُسمح لها بحيازته، فإنها تنهي عملها بدلاً من بدء التشغيل.

شغّل المكدس وتحقق منه

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 داخل وحدة تخزين يقدّمها Caddy، ثم يخرج أيضاً. يجب أن تعرض جميع المكونات الأخرى الحالة healthy في docker compose ps. إعادة تشغيل خدمة في حلقة يعني غالباً أنها تفشل في التحقق من متغيرات البيئة. يعرض السجل جميع المشكلات في قائمة واحدة بدلاً من عرض مشكلة واحدة في كل إعادة تشغيل. السببان المعتادان هما ترك متغير فارغاً، إذ يُرفض ذلك بدلاً من اعتباره غير مضبوط، ووضع سر في ملف الخدمة الخطأ.

على arm64، أو عند استخدام فرع، لا تتوفر صور منشورة، لذلك عليك البناء محلياً باستخدام docker compose up -d --build. ينفد جهاز بسعة 4 GB من الذاكرة أثناء البناء. أضف swap أولاً، ولا تحتاج إليه إلا أثناء البناء:

fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

يستغرق البناء نحو عشر دقائق. أما السحب فيستغرق بضع دقائق، ولذلك تتوفر صور الإصدار.

إنشاء الحساب الأول فوراً

افتح https://app.example.com. لا يعرض النشر الذي لم يسجّل أي شخص الدخول إليه نموذج تسجيل الدخول، بل يتيح إنشاء الحساب الأول. يبقى هذا الحساب صاحب الامتيازات الدائمة، وهو الحساب الوحيد الذي يمكنه رؤية شاشة إعدادات النشر. بعد إنشائه، يعيد المسار الاستجابة 409، لذلك لا يستطيع أي شخص الدخول بعدك. نفّذ ذلك فور سلامة مكونات الحزمة، وليس في الأسبوع التالي.

ثبّت أداة التتبّع

أضف موقعاً في لوحة التحكم، وستحصل على الوسم. صيغته ثابتة:

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

ضعه في رأس الصفحة. مفتاح التتبّع عام بطبيعته، لذلك يجب أن يكون في HTML حيث يمكن لأي شخص قراءته. يثبّت البرنامج النصي window.oa، وتضع الاستدعاءات مثل oa("track", ...) في قائمة انتظار عبر كائن بديل، ثم تُفرَّغ بعد تحميل الملف، لذلك لا يُهمَل أي حدث مخصّص يُطلَق مبكراً. إذا كان هناك مكوّن آخر في الصفحة يستخدم 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 وعدة كيلوبايت. حمّل صفحة على موقعك، ثم ابحث عن سطر دفعة في سجل العامل خلال ثوانٍ. يعيد الجامع 202 فور قبوله حدثاً، بينما يعني 202 أن الحدث موضوع في قائمة الانتظار، وليس أنه مخزّن. العامل هو الذي ينقل الأحداث إلى ClickHouse. إذا قُبلت الأحداث ولم يظهر شيء في لوحة التحكم، فهذا يعني أن العامل متوقّف، ويؤكد ذلك استمرار ارتفاع عمق قائمة انتظار Valkey. الأسباب المعتادة هي بيانات اعتماد ClickHouse الخاطئة في worker.env، أو غياب منح صلاحية على جدول أضافته عملية ترحيل حديثة.

أبقِ الـcollector متاحاً للعامة واجعل الـdashboard خلف المصادقة

يأتي Caddy ضمن ملف compose ويحصل على الشهادات للأسماء الأربعة تلقائياً، لذلك لا يتطلب المسار الافتراضي أي إعداد proxy منك. إذا كان الخادم يشغّل reverse proxy من nginx مسبقاً، فضع الـstack خلف 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 "";

يستخرج الـcollector تجزئة الزوار اليومية من عنوان IP الخاص بالعميل، لذلك يجب أن يأخذ هذا العنوان من الاتصال، وألا يأخذه من أي رأس. إن تمرير CF-Connecting-IP من قفزة غير موثوقة يتيح لأي مستدعٍ ادعاء امتلاك أي عنوان، ما يفسد تحديد الموقع الجغرافي ويضخم أعداد الزوار في الوقت نفسه.

ينقسم الوصول بوضوح حسب اسم المضيف. يجب أن يكون c. وrt. متاحين لكل زائر لكل موقع تقيسه، لذلك لا تضع basic auth أو قائمة سماح لعناوين IP أمام أيٍّ منهما. لا يحتاج app. وapi. إلى أن يكونا متاحين إلا للمستخدمين الذين يسجلون الدخول. تتولى المصادقة المضمنة في التطبيق حماية الـdashboard: يكون تسجيل الدخول بكلمة المرور مفعّلاً افتراضياً عبر AUTH_PASSWORD_SIGNIN=enabled في env/api.env، ولا تظهر زرا Google أو GitHub إلا عند وجود client ID وclient secret الخاصين بذلك المزوّد. تتطلب الروابط السحرية وسيلة نقل للبريد، ومن دونها لا تكتب الـAPI إلا عملية الإرسال في outbox، لذلك لا تصل الرسالة ولا يظهر أي خطأ.

يحدد إعداد واحد ما إذا كان الـdashboard سيعمل أصلاً. يجب أن يطابق AUTH_TRUSTED_ORIGINS في env/api.env أصل الـdashboard تماماً. عند غيابه أو خطئه، لا تُصدر الـAPI أي رؤوس CORS (مشاركة الموارد بين المصادر)، فيرفض المتصفح كل استدعاء، ويظهر لك dashboard يعرض تخطيطه ولا يعرض أي بيانات، بينما يبلغ docker compose ps بأن كل شيء سليم.

أثناء وجودك في إعداد proxy، عالج حركة المرور الآلية. تصل برامج الزحف إلى الـcollector مثل أي زائر آخر، وتُسجَّل مشاهدات صفحاتها في ClickHouse وفي أرقامك. يساعد حظر برامج زحف الذكاء الاصطناعي على الخادم في إبقاء جزء من هذه الحركة خارج قاعدة البيانات قبل أن يكلّفك ذلك الدقة ومساحة القرص معاً.

ما معنى عدم استخدام ملفات تعريف الارتباط هنا وما تكلفته عليك

لا يوجد ملف تعريف ارتباط. هوية الزائر عبارة عن تجزئة مع salt، ويتغير هذا الـsalt يومياً، ولا تُخزَّن عناوين IP الخام مطلقاً. تُحدَّد البيانات الجغرافية محلياً بالرجوع إلى ملف DB-IP الموجود على القرص الخاص بك، لذلك لا تغادر أي عملية بحث عن زائر المضيف.

ما يتيحه ذلك هو عدم وجود معرّف محفوظ على جهاز الزائر، وهذا تحديداً ما يُخضع أداة التتبع لقواعد الموافقة الخاصة بـEU ePrivacy. لذلك تُشغَّل الإعدادات التي تقتصر على البيانات المجمّعة، مثل هذا الإعداد، عادةً من دون شريط موافقة. تظل GDPR سارية على كل ما تخزّنه وعلى مدة تخزينه، ويحدد مستشارك القانوني كيفية انطباقها على حالتك، وليس ملف README.

ما تخسره هو القدرة على تحديد الهوية عبر الأيام. يعني تدوير الـsalt أن الشخص الذي يزور الموقع يوم الإثنين ثم يعود يوم الأربعاء يُحتسب زائرين اثنين، وهذا مقصود ولا توجد طريقة لتجاوزه. أعداد الزوار الفريدين اليومية موثوقة. أما أعداد الزوار الفريدين الأسبوعية والشهرية فتُبنى من الأعداد اليومية، ولذلك ستبالغ في تقدير الوصول. وبناءً عليه، فإن أي رقم طويل المدى لـ"الزوار العائدين" لا يقيس ما يشير إليه اسمه. تكون الجلسات ومسارات الزيارة موثوقة ضمن اليوم الواحد. ويؤدي تدوير ANONYMOUS_IDENTITY_SECRET إلى الأثر نفسه الذي يحدث عند الحد الفاصل بين يومين، لذلك تعامل مع هذا التدوير كتغيير في البيانات، لا كإجراء صيانة روتيني.

تحترم أداة التجميع إشارتَي Do Not Track وGlobal Privacy Control، وهي إشارة المتصفح التي تُخبر الموقع بعدم بيع البيانات الشخصية أو مشاركتها. ويتضمن وسم script مفاتيح التحكم الخاصة به للغرض نفسه: data-respect-gpc وdata-respect-dnt وdata-require-consent، الذي يوقف كل عمليات التجميع إلى أن تُمنح الموافقة، ويحفظ الإجابة في localStorage تحت المفتاح oa.consent. يؤدي ضبط data-storage="none" إلى تعطيل تخزين المتصفح بالكامل.

لماذا تمتلئ مساحة القرص بعد ستة أشهر

هذا ما يؤدي إلى تعطل خادم التحليلات المستضاف ذاتياً، وعادةً لا تكون الأحداث نفسها هي السبب.

ابدأ بالصور. ينشر أحد الإصدارات عشر صور، ويبلغ حجمها الإجمالي نحو 13 GB على القرص. يجلب التحديث الجيل الجديد قبل حذف الجيل القديم، لذلك تحتفظ بالجيلين لفترة من الوقت. وهذا يستهلك معظم متطلب 25 GB قبل وصول مشاهدة صفحة واحدة.

ثم تأتي اللقطات. snapshot.sh يوقف المكدس، ويؤرشف مجلدي البيانات مع كل سر، ثم يعيد التشغيل. النسخ الباردة هي النوع الآمن الوحيد هنا، لأن ClickHouse يدمج الأجزاء في الخلفية، والنسخة التي تُؤخذ أثناء الدمج لا تكون متسقة. ينشئ upgrade.sh نسخة تلقائياً قبل كل تحديث، لذلك تتراكم الأرشيفات على القرص نفسه إلى أن تضع لها حداً.

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

على مضيف اقترب من الحد، استعد الجيل السابق قبل التحديث. هذا آمن أثناء تشغيل المكدس، لأن الصور التي تعتمد عليها الحاويات قيد التشغيل تظل مستخدمة:

docker image prune -a -f

ثم تأتي الأحداث نفسها. يضغط ClickHouse البيانات العمودية بقوة، لذلك ينمو حجم الأحداث الخام ببطء أكثر مما يتوقع معظم الناس، كما أن جداول التجميع التي تقرؤها لوحة المعلومات صغيرة مقارنةً بالجدول الخام. قِس بدلاً من التخمين:

docker system df -v
docker compose exec clickhouse df -h /var/lib/clickhouse

للحصول على حجم كل جدول، نفّذ هذا باستخدام بيانات اعتماد ClickHouse التي أنشأها المولّد ضمن 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;

سجّل هذه القراءة في الأسبوع الأول، ثم كررها في الأسبوع الرابع. تعطيك نقطتان معدل نمو، ويخبرك معدل النمو بموعد الحاجة إلى زيادة حجم وحدة التخزين. لا يوثّق دليل الاستضافة الذاتية أي إعداد للاحتفاظ أو مدة بقاء للفعاليات الخام حتى August 2026، لذلك حدّد حجم القرص وفق معدل النمو الذي قسته، بدلاً من افتراض أن الصفوف القديمة تنتهي صلاحيتها تلقائياً.

هناك فخ في الحذف يستحق معرفته قبل أن يسبب مشكلة. يؤدي حذف موقع أو حساب إلى وضع العمل في قائمة انتظار العامل، ويحتاج هذا العامل إلى ضبط 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 ثلاثة تكاليف قبل أن ينفّذ الإجراء. وقت التوقف حقيقي: تُفقد الأحداث التي تُحاول الخدمة تسجيلها أثناء توقف الجامع، لأن أداة التتبع لا تعيد المحاولة. تؤدي الاستعادة إلى فقدان البيانات، لأن rollback.sh --to backups/<snapshot> يستبدل مخزني البيانات بالكامل ويتخلص من كل صف كُتب بعد إنشاء تلك اللقطة. أما التكلفة الثالثة فهي مساحة القرص، أي تراكم اللقطات الموضح أعلاه.

من السهل تطبيق قاعدتي إعادة التشغيل بشكل خاطئ. شغّل بوابة الاستعلام قبل API، لأن API الأحدث يرسل حقول استعلام ترفضها البوابة الأقدم. ويحتاج ClickHouse إلى إعادة إنشاء الحاوية بدلاً من إعادة تشغيلها، لأن docker compose restart يعيد استخدام بيئة الحاوية الأصلية ويتجاهل تعديلك بصمت:

docker compose up -d --force-recreate clickhouse

تنطوي لوحة المعلومات على الفخ نفسه. تُضمَّن الأصول الثلاثة NEXT_PUBLIC_* في env/web.env داخل حزمة المتصفح، وتُستبدل عند تشغيل الحاوية. لذلك تُصلح لوحة المعلومات التي تستدعي اسم مضيف خاطئ باستخدام docker compose up -d --force-recreate web، وليس باستخدام restart. يطبع سجل حاوية الويب الأصول التي بدأت بها، وهذه أسرع طريقة للتأكد من تطبيق الإصلاح.

إذا رفض ClickHouse البدء بعد تعديل الإعدادات، فاقرأ السطر الأول من سجله. إذا بدأ السطر بـ oa-entrypoint:، فهذا يعني أن نقطة الدخول رفضت قيمة ضبطتها. أما أي شيء آخر فيعني عادةً أن ملف الإعدادات يحتوي على XML غير صالح. والسبب الأكثر شيوعاً هو وجود واصلتين متتاليتين داخل تعليق XML، وهذا غير مسموح به هناك.

AGPL-3.0 والاسم

الشفرة مرخّصة بموجب AGPL-3.0. لا يترتب على تشغيلها دون تعديل لمواقعك الخاصة أي التزام بالنشر. يبدأ الالتزام عندما تعدّل الشفرة وتشغّل النسخة المعدّلة كخدمة شبكية؛ عندها تفرض الرخصة عليك إتاحة الشفرة المصدرية المعدّلة لمستخدمي تلك الخدمة. يشمل ذلك إتاحة لوحات المعلومات للعملاء على نسختك، كما يشمل تضمينها في منتج تبيعه. ويكفي إبقاء تعديلاتك في fork عام للوفاء بهذا الالتزام دون أي إجراء إضافي.

العلامة التجارية منفصلة عن الشفرة. يحدّد اسم "OpenAnalytics" والنطاق المستضاف للمشروع النسخة التي يديرها مؤلفو المشروع، ولا يشكلان جزءاً من منح الرخصة. يشغّل نشرُك البرنامج من دون استخدام العلامة التجارية، لذلك امنح الخدمة اسماً خاصاً بها قبل إتاحتها للعملاء الذين يدفعون.

FAQ

هل يمكنني تشغيل OpenAnalytics على VPS بسعة 1 GB؟

لا. يتطلب المشروع نحو 4 GB من ذاكرة RAM و25 GB من مساحة القرص الحرة، لأن كل عملية نشر تشغّل ست خدمات للتطبيقات إلى جانب Postgres وClickHouse ونسختين من Valkey. ClickHouse وحده ليس عملية صغيرة. على خادم بسعة 1 GB، تبدأ الحاويات ثم يوقف قاتل نفاد الذاكرة في النواة إحداها، وعادةً ما تكون ClickHouse. إذا كانت خطة 1 GB قيداً لا يمكن تغييره، فاستخدم أداة تعمل بملف ثنائي واحد، مثل GoatCounter، إذ تعمل على SQLite من دون قاعدة بيانات خارجية.

هل أحتاج إلى شريط موافقة على ملفات تعريف الارتباط مع OpenAnalytics؟

هذا سؤال موجّه إلى محاميك، والوقائع التقنية تصب في مصلحتك. لا توجد ملفات تعريف ارتباط، وهوية الزائر عبارة عن تجزئة مملّحة تتغير يومياً، ولا تُخزَّن عناوين IP الخام مطلقاً، لذلك لا تُكتب أي بيانات دائمة لتحديد هوية الزائر. يظل GDPR منظماً لما تخزّنه والمدة التي تحتفظ فيها به. إذا أردت تقييد الجمع صراحةً، فاضبط data-require-consent في وسم script: عندها لا يجمع المتتبّع أي بيانات حتى تُمنح الموافقة، ويحتفظ بالإجابة في localStorage ضمن oa.consent.

لماذا تعيد الأحداث الرمز 202 لكنها لا تظهر مطلقاً في لوحة المعلومات؟

يعني 202 أن جامع البيانات قبل الحدث ووضعه في قائمة الانتظار، لا أنه خزّنه. يفرغ العامل قائمة الانتظار في ClickHouse، لذلك تشير لوحة المعلومات الفارغة مع نجاح الطلبات إلى مشكلة في العامل. اقرأ docker compose logs --tail=50 worker وراقب عمق قائمة انتظار Valkey. إذا استمرت القائمة في الازدياد، فهذا يعني أن العامل متوقف، والأسباب المعتادة هي بيانات اعتماد ClickHouse الخاطئة في worker.env أو غياب صلاحية على جدول أنشأته عملية ترحيل حديثة.

لماذا تكون لوحة المعلومات فارغة عندما تكون كل الحاويات سليمة؟

تحقق أولاً من AUTH_TRUSTED_ORIGINS في env/api.env. يجب أن يطابق أصل لوحة المعلومات تماماً. عندما لا يتطابقان، لا تُصدر API رؤوس CORS، لذلك يرفض المتصفح كل استدعاء، وتظهر لك واجهة تعمل من دون بيانات. الشيء الثاني الذي يجب التحقق منه هو قيم NEXT_PUBLIC_* الثلاث في env/web.env، إذ تُستبدل عند بدء حاوية الويب. يتطلب تصحيحها docker compose up -d --force-recreate web، لأن إعادة التشغيل العادية تُبقي القيم القديمة.

هل يمنعني AGPL-3.0 من تقديم هذا البرنامج إلى العملاء؟

لا، لكنه يفرض شرطاً واحداً. شغّل الشفرة من دون تعديل، ولن تدين بشيء لأي جهة. إذا عدّلتها وشغّلت النسخة المعدّلة كخدمة يستخدمها أشخاص آخرون، فيجب أن تتيح لهؤلاء المستخدمين الشفرة المصدرية المعدّلة، وتفي بذلك عملية fork عامة. وبشكل منفصل، فإن اسم "OpenAnalytics" غير مرخّص مع الشفرة، لذلك يحتاج أي شيء تبيعه إلى اسم خاص به.