কিভাবে নিজের VPS-এ OpenAnalytics সেলফ-হোস্ট করবেন
OpenAnalytics সেলফ-হোস্ট করার আগে 4 GB RAM ও 25 GB ডিস্ক স্পেস নিশ্চিত করুন। ClickHouse, Postgres ও Valkey ব্যবহারের বিস্তারিত গাইড এবং সার্ভারের প্রয়োজনীয়তাগুলো এখানে জানুন।
প্রথম ধাপের আগে প্রয়োজনীয় প্রস্তুতি
OpenAnalytics সেলফ-হোস্ট করার জন্য আপনার 4 GB RAM, 25 GB ফ্রি ডিস্ক স্পেস এবং Docker Compose প্লাগইনসহ একটি Linux VPS প্রয়োজন। এছাড়া চারটি DNS রেকর্ড আগে থেকেই সার্ভারের IP-তে পয়েন্ট করা থাকতে হবে। এটিই মূল প্রয়োজনীয়তা, যা কোনো কমান্ড চালানোর আগেই জেনে রাখা জরুরি।
এই স্ট্যাকটিতে ছয়টি অ্যাপ্লিকেশন সার্ভিস এবং তিনটি ডেটা স্টোর রয়েছে। Postgres কন্ট্রোল প্লেন হিসেবে কাজ করে, যেখানে অ্যাকাউন্ট, সাইট, API কি এবং শেয়ার লিঙ্কগুলো সংরক্ষিত থাকে। ClickHouse কাঁচা ইভেন্ট এবং রোলআপ ডেটা ধরে রাখে, যা ড্যাশবোর্ডে প্রদর্শিত হয়। Valkey এখানে দুইবার চলে: একবার টেকসই ইভেন্ট কিউ হিসেবে এবং অন্যবার ক্যাশ হিসেবে, কারণ এই দুটি কাজের জন্য ভিন্ন ভিন্ন ইভিকশন পলিসি প্রয়োজন। শুধুমাত্র একটি প্রসেস, অর্থাৎ কুয়েরি গেটওয়ে, ClickHouse থেকে ডেটা পড়ার অনুমতি পায়। এটি প্রতিটি কুয়েরি চালানোর আগে তার Ed25519 সিগনেচার যাচাই করে নেয়।
আপনি যদি একটি বাইনারি ফাইল এবং একটি কনফিগারেশন ফাইল দিয়ে কাজ সারতে চান, তবে এটি আপনার জন্য নয়। এই ক্যাটাগরিতে GoatCounter হলো সিঙ্গেল-বাইনারি বিকল্প: একটি Go এক্সিকিউটেবল ফাইল, ডিফল্টভাবে SQLite ব্যবহার করে এবং কোনো এক্সটারনাল ডেটাবেসের প্রয়োজন হয় না। এই ভারী স্ট্যাকটি ব্যবহার করলে আপনি ফানেল, ওয়েব ভাইটালস, আপনার নিজস্ব Stripe অ্যাকাউন্ট থেকে রেভিনিউ অ্যাট্রিবিউশন এবং একটি MCP (মডেল কনটেক্সট প্রোটোকল) সার্ভার ব্যবহারের সুবিধা পাবেন। সেলফ-হোস্টেড অ্যানালিটিক্স টুল নির্বাচনের উপায় পোস্টটিতে এই বিষয়গুলোর তুলনামূলক আলোচনা করা হয়েছে। এই গাইডটি ধরে নিচ্ছে যে আপনি ইতিমধ্যে সিদ্ধান্ত নিয়ে ফেলেছেন।
প্রথমে সার্ভারের দিকে চারটি DNS রেকর্ড নির্দেশ করুন
কাজ শুরু করার আগে চারটি সাবডোমেইনকে অবশ্যই সার্ভারের পাবলিক IP-তে রিজলভ হতে হবে। কারণ Caddy প্রথমবার চালু হওয়ার সময় Let's Encrypt সার্টিফিকেট অনুরোধ করে এবং যে নামের DNS রেকর্ড এখনো রিজলভ হয় না, তার ক্ষেত্রে এই চ্যালেঞ্জ ব্যর্থ হয়।
app.example.comড্যাশবোর্ড পরিবেশন করে।api.example.comAPI এবং OAuth কলব্যাক পরিবেশন করে।c.example.comকালেক্টর এবং ট্র্যাকার স্ক্রিপ্ট পরিবেশন করে।rt.example.comরিয়েল-টাইম স্ট্রিম পরিবেশন করে।
চারটি A রেকর্ড ব্যবহার করুন, অথবা একটি A রেকর্ড এবং তিনটি CNAME রেকর্ড ব্যবহার করুন যা সেটির দিকে নির্দেশ করে। এগিয়ে যাওয়ার আগে dig +short app.example.com দিয়ে নিশ্চিত হয়ে নিন। আপনি এক মিনিট আগে যে নাম যোগ করেছেন, সেটি Let's Encrypt-এর ব্যবহৃত কোনো রিজলভারে এখনো NXDOMAIN হিসেবে ক্যাশ করা থাকতে পারে। তাই প্রথমবার সার্টিফিকেট পাওয়ার চেষ্টা ব্যর্থ হলে কিছুক্ষণ অপেক্ষা করুন এবং Caddy-এর লগগুলো পড়ুন। ইনস্টলেশন পুনরায় চালালে DNS দ্রুত প্রোপাগেট হয় না।
Docker Compose ব্যবহার করে OpenAnalytics সেলফ-হোস্ট করার পদ্ধতি
একটি ট্যাগ করা রিলিজ চেকআউট করুন। ডিফল্ট ব্রাঞ্চে ডেভেলপমেন্টের কাজ চলে, আর রিলিজ ট্যাগগুলো হলো সেই ভার্সন যা প্রকাশিত ইমেজগুলোর সাথে সামঞ্জস্যপূর্ণ। নিচের কমান্ডগুলো ধরে নেয় যে আপনার সিস্টেমে Docker এবং Compose প্লাগইন আগে থেকেই ইনস্টল করা আছে, যা VPS-এ Docker Compose সার্ভিস চালানো নির্দেশিকায় বিস্তারিত আলোচনা করা হয়েছে।
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 সিটি ডেটাবেসটি ডাউনলোড করে। এটি বাদ দিলে প্রতিটি ইভেন্টের কান্ট্রি ফিল্ড নাল (null) থাকবে, ফলে জিওগ্রাফি ভিউতে কোনো তথ্য দেখা যাবে না। আপনি চাইলে পরবর্তীতে infra/selfhost/geoip/fetch-dbip.sh চালিয়ে, env/collector.env ফাইলে GEOIP_DB_PATH=/geoip/dbip-city-lite.mmdb সেট করে এবং তারপর docker compose up -d --force-recreate collector দিয়ে কালেক্টরটি পুনরায় তৈরি করে এটি যোগ করতে পারেন। এই ডেটাবেসটি প্রতি মাসে আপডেট হয়, তাই প্রতি মাসে এটি পুনরায় ফেচ করুন, অন্যথায় আপনার সিটির তথ্যে অসামঞ্জস্য দেখা দিতে পারে।
সামনে অগ্রসর হওয়ার আগে জেনারেট করা সিক্রেটগুলোর ব্যাকআপ নিন
জেনারেটর তিনটি জিনিস তৈরি করে। .env-এ ডোমেইন নাম এবং ইমেজ রেফারেন্সগুলো থাকে। env/*.env-এ প্রতিটি সার্ভিসের জন্য একটি করে সিক্রেট ফাইল থাকে। docker-compose.override.yml-এ তিনটি Ed25519 কি পেয়ার YAML ব্লক স্কেলার হিসেবে থাকে, কারণ মাল্টি-লাইন PEM ফরম্যাট env ফাইলে রাখা যায় না। এই সবকিছুই git-ignore করা থাকে এবং এর কোনোটিই পুনরায় একই ভ্যালুতে জেনারেট করা সম্ভব নয়।
এই ফাইলগুলো এখনই মেশিন থেকে কপি করে অন্য কোথাও রাখুন। প্রতিটি ফাইল হারিয়ে ফেললে নির্দিষ্ট কিছু সমস্যার সম্মুখীন হতে হবে:
- স্টোর পাসওয়ার্ড হারিয়ে ফেললে আপনি Postgres এবং ClickHouse-এ প্রবেশ করতে পারবেন না, যা শুধুমাত্র কন্টেইনারের ভেতর থেকে রিসেট করা সম্ভব।
OA_CREDENTIAL_KEYRINGহারিয়ে ফেললে সংরক্ষিত সকল থার্ড-পার্টি ক্রেডেনশিয়াল পুনরুদ্ধার করা অসম্ভব হয়ে পড়বে, ফলে যাদের Stripe অ্যাকাউন্ট কানেক্ট করা ছিল, তাদের পুনরায় তা কানেক্ট করতে হবে।ANONYMOUS_IDENTITY_SECRETহারিয়ে ফেললে ভিজিটর আইডেন্টিটি নতুন করে শুরু হবে: গতকালের সকল ভিজিটরকে নতুন হিসেবে গণ্য করা হবে এবং চার্টে এই পরিবর্তনটি দৃশ্যমান হবে।AUTH_SECRETহারিয়ে ফেললে প্রতিটি সেশন বাতিল হয়ে যাবে, ফলে সবাইকে পুনরায় সাইন-ইন করতে হবে।- সাইনিং প্রাইভেট কি হারিয়ে ফেললে আপনাকে কি পেয়ারটি রোটেট করতে হবে। এতে কোনো তথ্য হারাবে না।
দুটি সিক্রেটকে অবশ্যই দুটি ফাইলের মধ্যে বাইট-আইডেন্টিক্যাল (byte-identical) হতে হবে। 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 psmigrate কমান্ডটি Postgres এবং ClickHouse স্কিমা প্রয়োগ করে এবং তারপর বন্ধ হয়ে যায়, তাই একটি বন্ধ migrate কন্টেইনারই হলো সঠিক চূড়ান্ত অবস্থা। tracker-build কমান্ডটি oa.js কম্পাইল করে একটি ভলিউমে রাখে যা Caddy পরিবেশন করে এবং এটিও কাজ শেষে বন্ধ হয়ে যায়। বাকি সবকিছুর অবস্থা docker compose ps-এ healthy হিসেবে দেখা উচিত। কোনো সার্ভিস যদি লুপে রিস্টার্ট হতে থাকে, তবে বুঝতে হবে সেটি এনভায়রনমেন্ট ভ্যালিডেশনে ব্যর্থ হচ্ছে। এক্ষেত্রে লগ ফাইলটি প্রতিটি সমস্যাকে আলাদাভাবে না দেখিয়ে একটি তালিকার মাধ্যমে প্রদর্শন করে। এর দুটি সাধারণ কারণ হলো: কোনো ভেরিয়েবল খালি রাখা (যা unset হিসেবে গণ্য না হয়ে প্রত্যাখ্যাত হয়) এবং ভুল সার্ভিস ফাইলে কোনো সিক্রেট রাখা।
arm64 আর্কিটেকচারে বা কোনো নির্দিষ্ট ব্রাঞ্চ থেকে কাজ করার সময় কোনো পাবলিশড ইমেজ থাকে না, তাই আপনাকে docker compose up -d --build ব্যবহার করে লোকালি বিল্ড করতে হবে। 4 জিবি র্যামের হোস্ট মেশিনে বিল্ড করার সময় মেমরি শেষ হয়ে যেতে পারে। তাই বিল্ড করার আগে সোয়াপ (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 খুলুন। যে deployment-এ কেউ এখনও সাইন-ইন করেনি, সেখানে কোনো সাইন-ইন ফর্ম দেখায় না: এটি প্রথম অ্যাকাউন্ট তৈরি করার প্রস্তাব দেয়। সেই অ্যাকাউন্টটি স্থায়ীভাবে বিশেষ সুবিধাপ্রাপ্ত (privileged) অ্যাকাউন্ট হিসেবে থাকে এবং একমাত্র এই অ্যাকাউন্টটিই deployment সেটিংস স্ক্রিন দেখতে পায়। একবার এটি তৈরি হয়ে গেলে, রুটটি 409-এ উত্তর দেয়, তাই আপনার পরে অন্য কেউ আর সেখানে প্রবেশ করতে পারবে না। স্ট্যাকটি সচল হওয়ার সাথে সাথেই এটি করুন, পরের সপ্তাহের জন্য ফেলে রাখবেন না।
ট্র্যাকার ইনস্টল করুন
ড্যাশবোর্ডে একটি সাইট যোগ করুন এবং এটি আপনাকে ট্যাগটি প্রদান করবে। এর গঠন নির্দিষ্ট:
<script
async
src="https://c.example.com/oa.js"
data-key="YOUR_TRACKING_KEY"
data-collector="https://c.example.com"
></script>এটি পেজের head অংশে রাখুন। ট্র্যাকিং কি (key) ডিজাইন অনুযায়ী পাবলিক, তাই এটি আপনার HTML-এর এমন জায়গায় থাকবে যেখানে যে কেউ এটি পড়তে পারে। স্ক্রিপ্টটি window.oa ইনস্টল করে এবং oa("track", ...)-এর মতো কলগুলো একটি স্টাব (stub) দ্বারা কিউ (queue) করা হয়। ফাইলটি লোড হওয়ার সাথে সাথে সেগুলো ফ্লাশ হয়ে যায়, তাই আগেভাগে ফায়ার করা কোনো কাস্টম ইভেন্ট হারিয়ে যায় না। যদি পেজের অন্য কোনো কিছু ইতিমধ্যে window.oa-এর মালিক হয়, তবে ট্র্যাকারটি window.openanalytics হিসেবে ইনস্টল হয়। যদি একই সাইট an 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 এবং কয়েক কিলোবাইট প্রিন্ট করবে। আপনার সাইটের একটি পেজ লোড করুন, তারপর কয়েক সেকেন্ডের মধ্যে ওয়ার্কার লগে একটি ব্যাচ লাইন খুঁজুন। কালেক্টর ইভেন্ট গ্রহণ করার সাথে সাথে 202 উত্তর দেয় এবং 202 মানে হলো এটি কিউতে আছে, সংরক্ষিত হয়নি। ওয়ার্কারই ইভেন্টগুলোকে ClickHouse-এ স্থানান্তর করে। ইভেন্ট গৃহীত হওয়ার পরেও যদি ড্যাশবোর্ডে কিছু না দেখায়, তার মানে ওয়ার্কার ব্লক হয়ে আছে এবং Valkey কিউয়ের গভীরতা ক্রমাগত বাড়তে থাকা এটি নিশ্চিত করে। এর সাধারণ কারণ হলো worker.env-এ ভুল ClickHouse ক্রেডেনশিয়াল থাকা, অথবা মাইগ্রেশনের মাধ্যমে যোগ করা কোনো টেবিলে প্রয়োজনীয় পারমিশন (grant) না থাকা।
কালেক্টরকে পাবলিক রাখুন এবং ড্যাশবোর্ডকে অথেন্টিকেশনের পেছনে রাখুন
Caddy সরাসরি compose ফাইলের ভেতরে থাকে এবং চারটি নামের জন্যই নিজে থেকে সার্টিফিকেট সংগ্রহ করে, তাই ডিফল্ট পাথের জন্য আপনাকে কোনো প্রক্সি কনফিগারেশন করতে হবে না। যদি সার্ভারে আগে থেকেই একটি nginx reverse proxy চলতে থাকে, তবে এর পরিবর্তে সরবরাহকৃত 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. অবশ্যই আপনার পরিমাপ করা প্রতিটি সাইটের ভিজিটরের কাছে পৌঁছানোর যোগ্য হতে হবে, তাই এই দুটির সামনে কখনোই বেসিক অথেন্টিকেশন বা IP হোয়াইটলিস্ট রাখবেন না। app. এবং api. শুধুমাত্র সেই ব্যক্তিদের কাছে পৌঁছানোর যোগ্য হতে হবে যারা সাইন-ইন করেন। অ্যাপ্লিকেশনের নিজস্ব অথেন্টিকেশন ড্যাশবোর্ডকে সুরক্ষিত রাখে: env/api.env ফাইলে AUTH_PASSWORD_SIGNIN=enabled-এর মাধ্যমে পাসওয়ার্ড সাইন-ইন ডিফল্টভাবে চালু থাকে এবং Google বা GitHub বাটনগুলো তখনই প্রদর্শিত হয় যখন সেই প্রোভাইডারের জন্য ক্লায়েন্ট আইডি এবং ক্লায়েন্ট সিক্রেট উভয়ই বিদ্যমান থাকে। ম্যাজিক লিঙ্কের জন্য একটি মেইল ট্রান্সপোর্ট প্রয়োজন, এবং এটি ছাড়া API শুধুমাত্র আউটবক্সে সেন্ড রাইট করে, তাই কোনো কিছুই ডেলিভারি হয় না এবং কোনো এররও দেখায় না। যদি আপনার অন্যান্য self-hosted অ্যাপগুলো আগে থেকেই একটি একক Authentik লগইন-এর পেছনে থাকে, তবে শুরুতেই সিদ্ধান্ত নিন এই ড্যাশবোর্ড সেগুলোর সাথে যুক্ত হবে নাকি নিজস্ব অ্যাকাউন্ট বজায় রাখবে, কারণ এখানে তৈরি করা প্রথম অ্যাকাউন্টটি স্থায়ীভাবে প্রিভিলেজড অ্যাকাউন্ট হিসেবে গণ্য হয়।
একটি সেটিং নির্ধারণ করে ড্যাশবোর্ডটি আদৌ কাজ করবে কি না। env/api.env ফাইলে AUTH_TRUSTED_ORIGINS অবশ্যই ড্যাশবোর্ড অরিজিনের সাথে হুবহু মিলতে হবে। ভুল বা অনুপস্থিত থাকলে, API কোনো CORS (cross-origin resource sharing) হেডার পাঠায় না, ব্রাউজার প্রতিটি কল প্রত্যাখ্যান করে, এবং আপনি এমন একটি ড্যাশবোর্ড পাবেন যা লেআউট রেন্ডার করলেও কোনো ডেটা দেখায় না, যদিও docker compose ps সবকিছু ঠিকঠাক আছে বলে রিপোর্ট করে।
প্রক্সি কনফিগারেশনে থাকাকালীন স্বয়ংক্রিয় ট্র্যাফিক সামলান। ক্রলাররা অন্য যেকোনো কিছুর মতোই কালেক্টরকে হিট করে এবং তাদের পেজ ভিউ ClickHouse-এ এবং আপনার পরিসংখ্যানে জমা হয়। সার্ভার লেভেলে AI ক্রলার ব্লক করা ডেটাবেসকে অপ্রয়োজনীয় ট্র্যাফিক থেকে রক্ষা করে, যা আপনার ডেটার নির্ভুলতা বজায় রাখে এবং ডিস্কের জায়গা সাশ্রয় করে।
এখানে কুকিলেস (cookieless) বলতে কী বোঝায় এবং এর প্রভাব কী
এখানে কোনো কুকি নেই। ভিজিটরের পরিচয় একটি সল্টেড হ্যাশ (salted hash) হিসেবে থাকে, এই সল্ট প্রতিদিন পরিবর্তিত হয় এবং মূল IP অ্যাড্রেস কখনোই সংরক্ষণ করা হয় না। জিওলোকেশন বা ভৌগোলিক অবস্থান আপনার নিজের ডিস্কে থাকা DB-IP ফাইলের মাধ্যমে লোকালি সমাধান করা হয়, তাই ভিজিটর সম্পর্কিত কোনো তথ্য কখনোই হোস্ট সার্ভারের বাইরে যায় না। লোকাল লুকআপ ব্যবহারের ফলে কোনো থার্ড-পার্টি ভেন্ডর যুক্ত থাকে না, তবে ডেটার সীমাবদ্ধতা একই থাকে; যেমনটি ঘটে যখন আপনি নিজের SearXNG ইনস্ট্যান্স চালান এবং সার্চ ইঞ্জিনগুলো আপনার সার্ভারের IP দেখতে পায়।
এর সুবিধা হলো ভিজিটরের ডিভাইসে কোনো আইডেন্টিফায়ার স্থায়ীভাবে থাকে না, যা মূলত EU ePrivacy কনসেন্ট রুলসের আওতায় ট্র্যাকার হিসেবে গণ্য হয়। এই কারণে এই ধরনের অ্যাগ্রিগেট-অনলি (aggregate-only) সেটআপগুলো সাধারণত কোনো কনসেন্ট ব্যানার ছাড়াই চালানো যায়। তবে আপনি যা কিছু সংরক্ষণ করছেন এবং কতদিন রাখছেন, তা GDPR-এর আওতাভুক্ত। আপনার আইনি পরামর্শদাতা আপনার ক্ষেত্রে প্রযোজ্য নিয়ম নির্ধারণ করবেন, কোনো README ফাইল নয়।
এর অসুবিধা হলো দিনের ব্যবধানে ভিজিটরের পরিচয় শনাক্ত করা যায় না। সল্ট পরিবর্তনের কারণে যে ব্যক্তি সোমবার ভিজিট করেছেন এবং বুধবার আবার ফিরে এসেছেন, তাকে ডিজাইন অনুযায়ী দুইজন আলাদা ভিজিটর হিসেবে গণনা করা হয় এবং এর কোনো বিকল্প নেই। দৈনিক ইউনিক ভিজিটর গণনা সঠিক থাকে। সাপ্তাহিক বা মাসিক ইউনিক ভিজিটর সংখ্যা দৈনিক সংখ্যার ভিত্তিতে তৈরি হয়, তাই এটি প্রকৃত সংখ্যার চেয়ে বেশি দেখাতে পারে। ফলে দীর্ঘ সময়ের "রিটার্নিং ভিজিটর" পরিসংখ্যানটি তার নামের সাথে সামঞ্জস্যপূর্ণ নয়। সেশন এবং জার্নিগুলো একটি নির্দিষ্ট দিনের মধ্যে নির্ভরযোগ্য। ANONYMOUS_IDENTITY_SECRET পরিবর্তন করা হলে তা দিনের সীমানা পরিবর্তনের মতোই প্রভাব ফেলে, তাই এই পরিবর্তনকে রুটিন হাইজিন হিসেবে না দেখে ডেটা পরিবর্তনের অংশ হিসেবে বিবেচনা করুন।
কালেক্টরটি Do Not Track এবং Global Privacy Control-কে সম্মান জানায়, যা ব্রাউজারের একটি সিগন্যাল এবং সাইটকে ব্যক্তিগত ডেটা বিক্রি বা শেয়ার না করার নির্দেশ দেয়। স্ক্রিপ্ট ট্যাগটিতে একই উদ্দেশ্যে নিজস্ব সুইচ রয়েছে: data-respect-gpc, data-respect-dnt এবং data-require-consent, যা সম্মতি না পাওয়া পর্যন্ত সমস্ত কালেকশন আটকে রাখে এবং oa.consent কি (key)-এর অধীনে localStorage-এ উত্তরটি মনে রাখে। data-storage="none" সেট করলে ব্রাউজার স্টোরেজ পুরোপুরি বন্ধ হয়ে যায়।
কেন ছয় মাস পর ডিস্ক পূর্ণ হয়ে যায়
এটিই একটি self-hosted analytics সার্ভার অকেজো হওয়ার প্রধান কারণ, এবং সাধারণত ইভেন্টগুলো এর জন্য দায়ী নয়।
শুরুতেই ইমেজগুলোর কথা ধরা যাক। একটি release-এ দশটি ইমেজ থাকে এবং ডিস্কে এগুলোর আকার প্রায় 13 GB হয়। একটি upgrade পুরনো ইমেজ মুছে ফেলার আগেই নতুন জেনারেশন ডাউনলোড করে, তাই কিছু সময়ের জন্য আপনার কাছে দুটি জেনারেশন জমা থাকে। একটিও page view আসার আগেই 25 GB চাহিদার বেশিরভাগই এতে খরচ হয়ে যায়।
এরপর আসে স্ন্যাপশট। snapshot.sh স্ট্যাকটিকে থামিয়ে দেয়, সমস্ত secret-সহ দুটি ডেটা ভলিউমই আর্কাইভ করে এবং পুনরায় চালু করে। এক্ষেত্রে শুধুমাত্র cold copy-ই নিরাপদ, কারণ ClickHouse ব্যাকগ্রাউন্ডে পার্টগুলো মার্জ করে এবং মার্জ চলাকালীন নেওয়া কপি সামঞ্জস্যপূর্ণ (consistent) হয় না। upgrade.sh প্রতিটি upgrade-এর আগে স্বয়ংক্রিয়ভাবে একটি কপি নেয়, তাই আপনি সীমা নির্ধারণ না করা পর্যন্ত এই আর্কাইভগুলো একই ডিস্কে জমা হতে থাকে।
./snapshot.sh create --label before-something-risky
./snapshot.sh list
./snapshot.sh --keep 3ডিস্ক পূর্ণ হওয়ার কাছাকাছি কোনো হোস্টে, upgrade করার আগে আগের জেনারেশনটি মুছে ফেলুন। স্ট্যাক চলাকালীন এটি করা নিরাপদ, কারণ চলমান কন্টেইনারগুলোর ব্যাকএন্ডে থাকা ইমেজগুলো তখনও রেফারেন্স হিসেবে থাকে:
docker image prune -a -fএরপর আসে ইভেন্টগুলো। ClickHouse কলামার ডেটাকে খুব ভালোভাবে কম্প্রেস করে, তাই raw ইভেন্টের ভলিউম বেশিরভাগ মানুষের ধারণার চেয়ে ধীরগতিতে বাড়ে। ড্যাশবোর্ড যে rollup টেবিলগুলো পড়ে, সেগুলো raw টেবিলের তুলনায় অনেক ছোট। অনুমান না করে পরিমাপ করুন:
docker system df -v
docker compose exec clickhouse df -h /var/lib/clickhouseপ্রতিটি টেবিলের হিসাব পেতে, infra/selfhost/env/-এর অধীনে জেনারেটর যে ClickHouse ক্রেডেনশিয়াল তৈরি করেছে তা দিয়ে এটি চালান:
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;প্রথম সপ্তাহে এবং চতুর্থ সপ্তাহে এই রিডিং নিন। দুটি পয়েন্ট থেকে আপনি বৃদ্ধির হার (growth rate) পাবেন এবং এই হার আপনাকে জানাবে কখন ভলিউম রিসাইজ করা প্রয়োজন। আগস্ট 2026 পর্যন্ত, self-hosting গাইডে raw ইভেন্টের জন্য কোনো retention বা time-to-live অপশন নেই। তাই পুরনো সারিগুলো নিজে থেকেই মুছে যাবে—এমনটা ধরে না নিয়ে আপনার পরিমাপ করা হারের ভিত্তিতে ডিস্কের আকার নির্ধারণ করুন।
মুছে ফেলার একটি ফাঁদ সম্পর্কে জেনে রাখা ভালো, যাতে পরে সমস্যায় পড়তে না হয়। কোনো সাইট বা অ্যাকাউন্ট মুছে ফেললে তা worker-এর জন্য কাজ জমা করে, এবং সেই worker-এর জন্য CLICKHOUSE_MAINTENANCE_USER ও CLICKHOUSE_MAINTENANCE_PASSWORD সেট করা থাকতে হয়, সাথে ClickHouse-এ একটি সংশ্লিষ্ট oa_maintenance ইউজার থাকতে হয়। এগুলো ছাড়া ডিলিট করার কিউ চিরকাল ঝুলে থাকে। সাইটটি ড্যাশবোর্ড থেকে অদৃশ্য হয়ে যায় কিন্তু প্রতিটি সারি ডিস্কে থেকে যায়, ফলে মনে হয় সবকিছু পরিষ্কার হয়েছে অথচ কোনো জায়গাই খালি হয় না।
আপগ্রেড এবং তিনটি খরচ
git fetch --tags
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./upgrade.shupgrade.sh কোনো কাজ করার আগে তিনটি খরচের কথা জানায়। ডাউনটাইম একটি বাস্তব সমস্যা: কালেক্টর ডাউন থাকা অবস্থায় কোনো ইভেন্ট আসার চেষ্টা করলে তা হারিয়ে যায়, কারণ ট্র্যাকার সেগুলো পুনরায় পাঠানোর চেষ্টা করে না। রোলব্যাক করলে ডেটা হারিয়ে যায়, কারণ rollback.sh --to backups/<snapshot> উভয় স্টোরকেই পুরোপুরি প্রতিস্থাপন করে এবং সেই স্ন্যাপশট নেওয়ার পর লেখা প্রতিটি সারি মুছে ফেলে। ডিস্ক হলো তৃতীয় খরচ, যা উপরে বর্ণিত স্ন্যাপশট স্তূপের সাথে সম্পর্কিত।
রিস্টার্টের দুটি নিয়ম ভুল হওয়ার সম্ভাবনা থাকে। API-এর আগে কুয়েরি গেটওয়ে চালু করুন, কারণ নতুন API এমন কুয়েরি ফিল্ড পাঠাতে পারে যা পুরনো গেটওয়ে গ্রহণ করে না। এবং ClickHouse-এর ক্ষেত্রে রিস্টার্টের পরিবর্তে রিক্রিয়েট করা প্রয়োজন, কারণ docker compose restart কন্টেইনারের মূল এনভায়রনমেন্ট পুনরায় ব্যবহার করে এবং আপনার করা পরিবর্তনগুলো নীরবে উপেক্ষা করে:
docker compose up -d --force-recreate clickhouseড্যাশবোর্ডের ক্ষেত্রেও একই ধরনের ফাঁদ রয়েছে। env/web.env-এ থাকা তিনটি NEXT_PUBLIC_* অরিজিন ব্রাউজার বান্ডিলে কম্পাইল করা থাকে এবং কন্টেইনার চালু হওয়ার সময় সেগুলো প্রতিস্থাপিত হয়। তাই ভুল হোস্টনামে কল করা ড্যাশবোর্ড ঠিক করতে docker compose up -d --force-recreate web ব্যবহার করতে হবে, restart দিয়ে তা হবে না। ওয়েব কন্টেইনারের লগে এটি কোন অরিজিন নিয়ে চালু হয়েছে তা দেখা যায়, যা সমাধানটি কার্যকর হয়েছে কি না তা নিশ্চিত করার দ্রুততম উপায়।
কনফিগারেশন পরিবর্তনের পর ClickHouse চালু হতে অস্বীকার করলে, এর লগের প্রথম লাইনটি পড়ুন। oa-entrypoint: দিয়ে শুরু হওয়া লাইনটির অর্থ হলো এন্ট্রি পয়েন্ট আপনার সেট করা কোনো মান গ্রহণ করছে না। অন্য যেকোনো বার্তার অর্থ সাধারণত কনফিগারেশন ফাইলটি অবৈধ XML, এবং এর সবচেয়ে সাধারণ কারণ হলো XML কমেন্টের ভেতরে ডাবল হাইফেন ব্যবহার করা, যা সেখানে নিষিদ্ধ।
AGPL-3.0 এবং নাম
এই কোডটি AGPL-3.0 লাইসেন্সের অধীনে রয়েছে। আপনার নিজস্ব সাইটের জন্য কোনো পরিবর্তন না করে এটি চালালে কোনো কিছু প্রকাশ করার বাধ্যবাধকতা তৈরি হয় না। বাধ্যবাধকতাটি তখনই শুরু হয় যখন আপনি কোডটি পরিবর্তন করেন এবং সেই পরিবর্তিত সংস্করণটি একটি নেটওয়ার্ক সার্ভিস হিসেবে চালান: সেক্ষেত্রে লাইসেন্স অনুযায়ী আপনাকে সেই সার্ভিসের ব্যবহারকারীদের কাছে আপনার পরিবর্তিত সোর্স কোডটি অফার করতে হবে। এটি আপনার ইনস্ট্যান্সে ক্লায়েন্টদের ড্যাশবোর্ড দেওয়া এবং কোনো বিক্রয়যোগ্য পণ্যের সাথে এটি যুক্ত করার ক্ষেত্রে প্রযোজ্য। আপনার পরিবর্তনগুলো একটি পাবলিক ফোর্ক-এ রাখলে কোনো অতিরিক্ত প্রক্রিয়া ছাড়াই এই শর্ত পূরণ হয়।
ব্র্যান্ডটি কোড থেকে আলাদা। "OpenAnalytics" নাম এবং প্রজেক্টের হোস্ট করা ডোমেইনটি এর নির্মাতাদের পরিচালিত ইনস্ট্যান্সকে চিহ্নিত করে এবং এগুলো লাইসেন্স অনুদানের অংশ নয়। আপনার ডেপ্লয়মেন্টটি ব্র্যান্ড ছাড়াই সফটওয়্যারটি চালায়, তাই অর্থ প্রদানকারী গ্রাহকদের সামনে এটি উপস্থাপনের আগে সার্ভিসটিকে নিজস্ব একটি নাম দিন।
FAQ
আমি কি 1 GB RAM-এর VPS-এ OpenAnalytics চালাতে পারব?
না। এই প্রজেক্টের জন্য প্রায় 4 GB RAM এবং 25 GB খালি ডিস্ক স্পেস প্রয়োজন, কারণ একটি ডেপ্লয়মেন্টে Postgres, ClickHouse এবং দুটি Valkey ইনস্ট্যান্সের পাশাপাশি ছয়টি অ্যাপ্লিকেশন সার্ভিস চলে। ClickHouse নিজে কোনো ছোট প্রসেস নয়। 1 GB-এর সার্ভারে কন্টেইনারগুলো চালু হওয়ার পর কার্নেলের out-of-memory killer সাধারণত ClickHouse-কে বন্ধ করে দেয়। যদি 1 GB-এর প্ল্যানই আপনার একমাত্র সীমাবদ্ধতা হয়, তবে GoatCounter-এর মতো সিঙ্গেল-বাইনারি টুল ব্যবহার করুন, যা কোনো এক্সটার্নাল ডেটাবেস ছাড়াই SQLite-এ চলে।
OpenAnalytics-এর সাথে কি আমার কুকি ব্যানার প্রয়োজন?
এটি আপনার আইনজীবীর সাথে আলোচনার বিষয়, তবে প্রযুক্তিগত দিক থেকে বিষয়টি আপনার অনুকূলে। এখানে কোনো কুকি নেই, ভিজিটর আইডেন্টিটি হলো একটি সল্টেড হ্যাশ যা প্রতিদিন পরিবর্তিত হয়, এবং raw IP অ্যাড্রেস কখনোই সংরক্ষণ করা হয় না, তাই ভিজিটরকে শনাক্ত করার মতো স্থায়ী কিছু লেখা হয় না। তবে আপনি কী সংরক্ষণ করছেন এবং কতদিন রাখছেন, তা GDPR-এর আওতাভুক্ত। আপনি যদি স্পষ্টভাবে ডেটা সংগ্রহ নিয়ন্ত্রণ করতে চান, তবে স্ক্রিপ্ট ট্যাগে data-require-consent সেট করুন: এতে সম্মতি না পাওয়া পর্যন্ত ট্র্যাকার কিছুই সংগ্রহ করবে না এবং সম্মতি পাওয়ার পর তা oa.consent-এর অধীনে localStorage-এ সংরক্ষণ করবে।
ইভেন্টগুলো কেন 202 রেসপন্স দেয় কিন্তু ড্যাশবোর্ডে দেখা যায় না?
202 মানে হলো কালেক্টর ইভেন্টটি গ্রহণ করেছে এবং কিউতে (queue) রেখেছে, এটি ডেটাবেসে সংরক্ষিত হয়েছে এমন নয়। ওয়ার্কার সেই কিউ থেকে ডেটা ClickHouse-এ পাঠায়, তাই সফল রিকোয়েস্ট সত্ত্বেও ড্যাশবোর্ড খালি থাকলে বুঝতে হবে সমস্যা ওয়ার্কারে। docker compose logs --tail=50 worker পড়ুন এবং Valkey কিউয়ের গভীরতা পর্যবেক্ষণ করুন। কিউ যদি ক্রমাগত বাড়তে থাকে তবে বুঝতে হবে ওয়ার্কার ব্লক হয়ে আছে, এবং এর সাধারণ কারণ হলো worker.env-এ ভুল ClickHouse ক্রেডেনশিয়াল অথবা সাম্প্রতিক মাইগ্রেশনে তৈরি কোনো টেবিলে পারমিশনের অভাব।
সব কন্টেইনার হেলদি থাকা সত্ত্বেও ড্যাশবোর্ড কেন খালি?
প্রথমে env/api.env-এ AUTH_TRUSTED_ORIGINS চেক করুন। এটি অবশ্যই ড্যাশবোর্ড অরিজিনের সাথে হুবহু মিলতে হবে, অন্যথায় API কোনো CORS হেডার পাঠাবে না। ফলে ব্রাউজার প্রতিটি কল প্রত্যাখ্যান করবে এবং আপনি কোনো ডেটা ছাড়াই একটি কার্যকর লেআউট দেখতে পাবেন। দ্বিতীয়ত, env/web.env-এ তিনটি NEXT_PUBLIC_* ভ্যালু চেক করুন, যা ওয়েব কন্টেইনার চালু হওয়ার সময় সাবস্টিটিউট করা হয়। এগুলো সংশোধন করতে docker compose up -d --force-recreate web প্রয়োজন, কারণ সাধারণ রিস্টার্টে পুরনো ভ্যালুই থেকে যায়।
AGPL-3.0 কি আমাকে ক্লায়েন্টদের এই সার্ভিস অফার করা থেকে বাধা দেয়?
না, এটি কেবল একটি শর্ত জুড়ে দেয়। কোডটি কোনো পরিবর্তন ছাড়াই চালালে আপনাকে কাউকে কিছু দিতে হবে না। যদি আপনি কোডটি পরিবর্তন করেন এবং সেই পরিবর্তিত সংস্করণটি সার্ভিস হিসেবে অন্যদের ব্যবহার করতে দেন, তবে আপনাকে সেই ব্যবহারকারীদের আপনার পরিবর্তিত সোর্স কোড দিতে হবে, যা একটি পাবলিক ফর্ক (fork) তৈরির মাধ্যমে পূরণ করা সম্ভব। এছাড়া, "OpenAnalytics" নামটি কোডের সাথে লাইসেন্সভুক্ত নয়, তাই আপনি যা বিক্রি করবেন তার জন্য নিজস্ব নাম ব্যবহার করতে হবে।