VPS-এ Actual Budget সেটআপ করার নিয়ম
Docker Compose ব্যবহার করে VPS-এ Actual Budget হোস্ট করার পূর্ণাঙ্গ নির্দেশিকা। HTTPS কেন বাধ্যতামূলক, ডেটা ভলিউম কনফিগারেশন, ব্যাংক ইমপোর্ট এবং ব্যাকআপ নেওয়ার সঠিক পদ্ধতি জানুন।
আপনি যা তৈরি করছেন
Actual Budget একটি self-hosted envelope budgeting app। নিজে host করার জন্য YNAB-এর বিকল্প খুঁজলে সাধারণত এটিই বিবেচনা করা হয়। সার্ভারটি একটি container, একটি data volume এবং একটি HTTPS name নিয়ে গঠিত। সার্ভার মূলত file সংরক্ষণ ও sync করে।
কোনো command চালানোর আগে architecture বোঝা দরকার। মূল budget হলো একটি SQLite database, যা আপনার browser এবং প্রতিটি mobile app-এর ভিতরে থাকে। আপনি যে server install করতে যাচ্ছেন, সেটি একটি sync endpoint। এটি account list, budget file এবং change log সংরক্ষণ করে। এই change log-এর মাধ্যমে phone ও laptop একই অবস্থায় থাকে। তাই server বন্ধ থাকলেও app কাজ করে। একই কারণে কোনো একটি client-এ copy থাকা অবস্থায় server হারিয়ে গেলেও budget হারায় না।
সার্ভারের HTTPS প্রয়োজন কেন
Actual HTTPS চায়, এবং এটি নিছক আনুষ্ঠানিকতা নয়। Actual চালানোর জন্য SharedArrayBuffer নামের একটি browser feature অবশ্যই প্রয়োজন, আর browser কেবল তখনই এটি সক্রিয় করে যখন specification-এ secure context বলা হয়েছে এমন অবস্থায় page-টি cross-origin isolated থাকে। Secure context হলো https:// অথবা http://localhost। অন্য একটি মেশিনের browser-এ http://203.0.113.10:5006 থেকে app লোড করলে browser কখনোই ওই feature-টি page-কে দেয় না। তাই budget লোড হয় না। ফোনের ক্ষেত্রে কোনো ব্যতিক্রম নেই। Actual-এর FAQ অনুযায়ী, একটি official iOS app beta testing-এ রয়েছে এবং সেটি আপনার নিজের server-এ সংযোগ করে। সেখানে আরও বলা আছে যে এখনো কোনো official Android app নেই। তাই অধিকাংশ ফোনে আপনাকে একই web app ব্যবহার করতে হবে। এটি home screen-এ Progressive Web App হিসেবে install করা যায়, এবং একই secure-context নিয়ম প্রযোজ্য থাকে।
তাই কার্যকর setup দুটি। Container-এর সামনে একটি আসল name-এ একটি আসল certificate বসান। এই guide-এ সেটিই করা হয়েছে। অথবা ACTUAL_HTTPS_KEY এবং ACTUAL_HTTPS_CERT ব্যবহার করে server-কে একটি self-signed certificate দিন। Project-এ এই পদ্ধতিটি নথিভুক্ত আছে, তবে প্রতিটি device-এ browser warning মেনে নিতে হবে। Let's Encrypt-এর একটি বিনামূল্যের certificate নিতে পাঁচ মিনিট লাগে। তাই প্রথম বিকল্পটি ব্যবহার করুন।
Docker Compose দিয়ে Actual Budget ইনস্টল করুন
সার্ভারটি নতুন হলে আগে Docker ইনস্টল করুন। Compose file-এর syntax আপনার কাছে নতুন হলে, VPS-এর জন্য Docker Compose-এর প্রাথমিক ধারণা গাইডে নিচে ব্যবহৃত field-গুলো ব্যাখ্যা করা হয়েছে।
sudo install -d -m 755 /opt/actual
sudo install -d -m 700 /opt/actual/data/opt/actual/docker-compose.yml লিখুন:
services:
actual:
image: actualbudget/actual-server:latest
container_name: actual
restart: unless-stopped
ports:
- '127.0.0.1:5006:5006'
volumes:
- ./data:/dataএই file-এর 3টি বিষয় গুরুত্বপূর্ণ।
image হলো actualbudget/actual-server:latest। এটি project Docker Hub-এ প্রকাশ করেছে এবং ghcr.io/actualbudget/actual-এ mirror করা হয়েছে। কম ক্ষমতার মেশিনের জন্য latest-alpine tag রয়েছে।
container-এর সব data /data-এর অধীনে লেখে। এর মধ্যে server-files রয়েছে, যেখানে আপনার login এবং session token-সহ account.sqlite থাকে। এছাড়া user-files রয়েছে, যেখানে budget file-গুলো থাকে। এই path mount করুন, যাতে container প্রতিস্থাপন করলেও আপনার budget অক্ষত থাকে। ACTUAL_DATA_DIR এটি সরিয়ে নিতে পারে, তবে default-ই যথেষ্ট।
port-টি শুধু 127.0.0.1-এ publish করা হয়েছে। সরাসরি 5006:5006 ব্যবহার করলে port-টি সব interface-এ publish হয়। Docker নিজের rule-গুলো ufw-এর আগে প্রয়োগ করে। তাই deny-all firewall থাকলেও app-টি Internet-এ উন্মুক্ত হয়ে যায়। এই অপ্রত্যাশিত আচরণ Docker-এর published port কেন ufw bypass করে গাইডে ব্যাখ্যা করা হয়েছে। loopback-এ bind করলে একই server-এর reverse proxy ছাড়া অন্য কেউ এতে পৌঁছাতে পারে না।
এটি চালু করুন:
cd /opt/actual
docker compose up --detach
docker compose logs -f actualserver port 5006-এ listening করছে বলে জানালে log স্থির হয়ে আসে। DNS পরিবর্তন করার আগে স্থানীয়ভাবে পরীক্ষা করুন:
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5006/200 মানে app অনুরোধ গ্রহণ করছে। curl: (7) Failed to connect মানে 127.0.0.1:5006-এ কোনো service connection গ্রহণ করেনি। docker compose ps --all চালান, কারণ সাধারণ docker compose ps শুধু চলমান container দেখায়। container-এর অবস্থা exited হলে সাধারণ কারণ হলো mounted volume-এ permission সমস্যা। log-এ এটি EACCES line হিসেবে দেখা যায়।
সামনে certificate এবং প্রকৃত নাম যুক্ত করুন
VPS-এর দিকে একটি A record নির্দেশ করুন, budget.example.com, এবং সেটি resolve হওয়া পর্যন্ত অপেক্ষা করুন। এরপর nginx ইনস্টল করে certificate issue করুন। Ubuntu 24.04-এ nginx সহ Certbot গাইডে issue করা এবং renewal timer সম্পর্কে সম্পূর্ণ নির্দেশনা রয়েছে।
Proxy block:
server {
listen 443 ssl;
http2 on;
server_name budget.example.com;
ssl_certificate /etc/letsencrypt/live/budget.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/budget.example.com/privkey.pem;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:5006;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}client_max_body_size হলো যে লাইনটি অনেকে ভুলে যান। দৈনন্দিন sync-এ শুধু আপনার পরিবর্তনগুলো পাঠানো হয়। কিন্তু আপনি প্রথমবার server-এর সঙ্গে কোনো budget register করলে এবং sync reset করলে পুরো budget file আপলোড হয়। nginx ডিফল্টভাবে 1 MB request body অনুমোদন করে। তাই file-এর আকার এর চেয়ে বড় হলে পুরো upload ব্যর্থ হয়। nginx access log-এ তখন 413 Request Entity Too Large দেখা যায়। অথচ app-এ শুধু একটি সাধারণ sync error দেখায়। Server-এর নিজস্ব আলাদা limit রয়েছে। ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB-এর ডিফল্ট মান 20 এবং ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB-এর ডিফল্ট মান 50। তাই আপনার ক্ষেত্রে যেটি প্রযোজ্য, nginx limit সেটির চেয়ে বেশি রাখুন।
Reload করে পরীক্ষা করুন:
sudo nginx -t && sudo systemctl reload nginx
curl -fsS -o /dev/null -w '%{http_code}\n' https://budget.example.com/প্রথমবার চালানো: পাসওয়ার্ড এবং আপনার প্রথম budget file
ব্রাউজারে https://budget.example.com খুলুন। প্রথম স্ক্রিনে server password সেট করতে বলা হবে। এই একটি পাসওয়ার্ড পুরো server সুরক্ষিত রাখে। তাই একটি দীর্ঘ, এলোমেলো পাসওয়ার্ড তৈরি করুন এবং এমন জায়গায় সংরক্ষণ করুন যেখানে পরে আবার খুঁজে পাবেন, যেমন একটি নিজে পরিচালিত Vaultwarden password manager। ডিফল্টভাবে কোনো user account তৈরি করার ব্যবস্থা নেই। server-এ একটি পাসওয়ার্ডই থাকে। তাই budget শেয়ার করার অর্থ হলো সেই পাসওয়ার্ড শেয়ার করা। আলাদা login কেবল তখনই ব্যবহার করা যায়, যখন আপনি একটি OpenID provider সংযুক্ত করেন। Actual multi-user setup-এর জন্য এটি সমর্থন করে।
এরপর একটি budget file তৈরি করুন। Settings খুলে Encryption section-এ Enable encryption-এ click করুন। এটি enable করলে server আপনার budget data-এর কেবল ciphertext সংরক্ষণ করবে। ভাড়া নেওয়া machine-এ financial data রাখার জন্য এটাই উপযুক্ত পদ্ধতি। তবে এর একটি বাস্তব ঝুঁকি আছে। encryption password কখনো server-এ পৌঁছায় না। তাই এটি হারিয়ে ফেললে এবং budget-এর কোনো local copy না থাকলে file-টি হারিয়ে যাবে। local copy থাকলে encryption key reset করে আবার sync করতে পারবেন। ওই screen পেরিয়ে যাওয়ার আগে password লিখে রাখুন।
বছরের পর বছর পুরোনো history import না করে আপনার bank-এর বর্তমান figure অনুযায়ী starting balance সেট করুন। Envelope budgeting আপনার হাতে এখন থাকা অর্থ থেকে পরবর্তী হিসাব শুরু করে। তাই পুরোনো history না থাকলেও আপনার কোনো ক্ষতি হবে না।
লেনদেন আমদানি করা
এখানে উৎসাহের চেয়ে সৎ তথ্য বেশি গুরুত্বপূর্ণ, কারণ self-hosted budgeting থেকে মানুষ সরে যাওয়ার প্রধান কারণ হলো আমদানির সীমাবদ্ধতা।
Manual entry হলো ভিত্তি, এবং এটি সব সময় কাজ করে। Envelope পদ্ধতিতে এটিই মূল উদ্দেশ্য বলা যায়, কারণ কোনো কেনাকাটার তথ্য টাইপ করলেই আপনি সেটি লক্ষ্য করেন।
File import বড় পরিমাণের লেনদেন সামলায়। Actual CSV, QIF, OFX, QFX এবং CAMT পড়তে পারে। Account screen থেকে প্রতিটি account-এর জন্য import করুন। ফাইলটি CSV হলে column mapping করুন।
Automatic bank sync আছে। তবে এর জন্য third-party service দরকার, কারণ server নিজে থেকে bank-এর সঙ্গে যোগাযোগ করতে পারে না। Actual North American bank-এর জন্য SimpleFIN Bridge, Europe-এর জন্য Enable Banking, New Zealand-এর জন্য Akahu এবং Brazil-এর জন্য Pluggy.ai সমর্থন করে। GoCardless এখনও সমর্থিত, কিন্তু নতুন account গ্রহণ করছে না। আপনাকে নিজে provider-এর কাছে sign up করতে হবে, credentials তৈরি করতে হবে এবং সেগুলো server-এ যোগ করতে হবে। October 2026 অনুযায়ী SimpleFIN Bridge সর্বোচ্চ 25টি institution-এর জন্য tax ছাড়া বছরে 15 US dollars charge করে। অন্যগুলোর pricing আলাদা।
এটির ওপর নির্ভর করার আগে দুটি সীমাবদ্ধতা মেনে নিন। API credentials server-এ থাকে এবং end-to-end encryption-এর আওতায় থাকে না, কারণ server-কে সেগুলো ব্যবহার করতে হয়। Actual বন্ধ থাকলে কোনো কিছু sync হয় না। Automatic syncing default অবস্থায় বন্ধ থাকে। এটি চালু করলেও কেবল app খোলা থাকা অবস্থায় চলে। তাই server নিজে থেকে আপনার bank-গুলো poll করে না।
Backups, কারণ এখানে শুধু ফাইল আছে
আপনার গুরুত্বপূর্ণ সবকিছু /opt/actual/data-এর অধীনে থাকে। কোনো export ধাপ নেই এবং script করার জন্য কোনো database dump-ও নেই।
এখানে একমাত্র ঝুঁকি হলো SQLite। সার্ভার এতে লেখার সময় account.sqlite কপি করলে অসম্পূর্ণ transaction কপি হতে পারে। Restore করার চেষ্টা না করা পর্যন্ত বিষয়টি ধরা নাও পড়তে পারে। কপি করতে যত কয়েক সেকেন্ড লাগে, সেই সময় container বন্ধ রাখুন:
cd /opt/actual
docker compose stop
restic -r sftp:backup@backup.example.com:/srv/restic backup /opt/actual/data
docker compose startVPS-এ restic backup-এ বর্ণিত পদ্ধতি ব্যবহার করে এটিকে schedule করুন। ওই পদ্ধতিতে repository setup, retention এবং restore drill অন্তর্ভুক্ত আছে। Restore drill চালান। যে backup কখনো restore করে দেখা হয়নি, সেটি কেবল একটি অনুমান।
Actual-এর নিজস্ব automatic backup আলাদা বিষয় এবং এটি জানা উপযোগী। তবে এই সুবিধা শুধু desktop app-এ আছে। এটি budget file-এর সর্বোচ্চ 10টি সাম্প্রতিক কপি রাখে। File > Load Backup থেকে এগুলো খোলা যায়। ফলে সার্ভারে কিছু না করেই “ভুল করে একটি category মুছে ফেলেছি” ধরনের সমস্যা সামলানো যায়। Browser-এ চলা web app এ ধরনের কোনো কপি রাখে না। তাই বাড়ির সবাই browser ব্যবহার করলে উপরের server backup schedule করুন। এছাড়া Settings > Export data থেকে manual budget backup সংরক্ষণ করতে পারেন।
সার্ভার আপডেট করা
cd /opt/actual
docker compose pull
docker compose up --detachCompose নতুন image থেকে container পুনরায় তৈরি করে এবং একই volume আবার সংযুক্ত করে। তাই data অক্ষত থাকে। Client-গুলোও আপডেট করুন। প্রাপ্ত data-এর তুলনায় পুরোনো কোনো client পরিবর্তন প্রয়োগ করা বন্ধ করে এবং Update Required বার্তা দেখায়। আপডেট করার আগে backup নিন, কারণ নতুন version-এ খোলা budget পুরোনো client-এর সঙ্গে অসামঞ্জস্যপূর্ণ হতে পারে। Actual floating latest tag ব্যবহারের পরামর্শ দেয়। তবে এটি এখনও SQLite database ব্যবহার করে। Chatwoot self-hosting-এ pinned tag এবং এই workflow অনুযায়ী প্রয়োজনীয় pre-upgrade dump ধাপে ধাপে দেখানো হয়েছে।
কী সমস্যা হতে পারে এবং আপনি কী দেখতে পাবেন
অ্যাপ লোড হয়, কিন্তু sync কখনো শেষ হয় না। nginx access log-এ 413 দেখুন। এর অর্থ client_max_body_size খুব কম সেট করা হয়েছে। 502 দেখা গেলে nginx upstream থেকে ব্যবহারযোগ্য response পায়নি। সাধারণত এর কারণ container বন্ধ আছে অথবা 5006 port-এ listening করছে না। তাই অন্য কিছু করার আগে docker compose ps --all এবং container log পরীক্ষা করুন।
অন্য device-এ অ্যাপ লোড হয় না, অথবা SharedArrayBuffer অনুপস্থিত দেখায়। পেজটি HTTPS ব্যবহার করছে এবং Actual থেকে cross-origin isolation header পাচ্ছে কি না পরীক্ষা করুন। address bar-এ IP address-সহ http:// অথবা localhost নয় এমন কোনো hostname দেখা গেলে workaround না করে certificate ঠিক করুন।
budget file এই version-এর সঙ্গে compatible নয়—এমন message দেখা যায়। আপনি বর্তমানে যে client ব্যবহার করছেন, তার চেয়ে নতুন version-এ file-টি খোলা হয়েছে। client-কে অন্তত সেই version-এ update করুন। অথবা নতুন version file-টি পরিবর্তন করার আগের backup restore করে আবার reload করুন।
container বারবার restart হতে থাকে। docker compose logs actual পড়ুন। /data-এ permission error দেখালে mounted directory-তে container-এর user লিখতে পারছে না। address-in-use error দেখালে loopback-এ 5006 port অন্য কোনো process ইতিমধ্যে ব্যবহার করছে।
প্রথমবার load হতে ধীর মনে হয়। কোনো device-এ প্রথমবার খুললে সম্পূর্ণ budget file browser-এ download হয়। এটি একটি বড় transfer-এর পর local read-এর কাজ। এটি server sizing-এর সমস্যা নয়, এবং RAM বাড়ালেও এর পরিবর্তন হবে না।
FAQ
Actual চালাতে কি HTTPS প্রয়োজন?
বাস্তবে, হ্যাঁ। Actual চালানোর জন্য ব্রাউজারের SharedArrayBuffer বৈশিষ্ট্য প্রয়োজন, আর ব্রাউজার এটি শুধু secure context-এ সক্রিয় করে, অর্থাৎ https:// বা http://localhost। অন্য কোনো মেশিন থেকে সাধারণ HTTP ব্যবহার করলে অ্যাপটি লোড হবে না। একটি অফিসিয়াল iOS অ্যাপ beta testing-এ আছে, কিন্তু এখনো কোনো অফিসিয়াল Android অ্যাপ নেই। তাই অধিকাংশ ফোনে একই web app ব্যবহার হয় এবং একই নিয়ম প্রযোজ্য। (FAQ) একটি প্রকৃত hostname-এ Let's Encrypt certificate ব্যবহার করুন। অথবা প্রতিটি ডিভাইসে certificate warning মেনে নিতে পারলে ACTUAL_HTTPS_KEY এবং ACTUAL_HTTPS_CERT সহ self-signed certificate ব্যবহার করুন।
Actual কি আমার bank transaction স্বয়ংক্রিয়ভাবে import করতে পারে?
শুধু এমন কোনো third-party service-এর মাধ্যমে, যেটিতে আপনাকে নিজে account খুলতে হবে: North America-তে SimpleFIN Bridge, Europe-তে Enable Banking, New Zealand-এ Akahu, অথবা Brazil-এ Pluggy.ai। GoCardless সমর্থিত, কিন্তু নতুন account গ্রহণ করছে না। ওই API credential-গুলো আপনার server-এ থাকে এবং end-to-end encryption-এর আওতায় পড়ে না। Automatic syncing defaultভাবে বন্ধ থাকে। চালু করার পর এটি শুধু Actual খোলা থাকলেই চলে। তাই server-এ background-এ কোনো polling হয় না। CSV, QIF, OFX, QFX এবং CAMT import-এর জন্য কোনো third party প্রয়োজন হয় না।
ঠিক কোন জিনিসের backup নিতে হবে?
Mounted data directory-এর backup নিতে হবে। এই guide-এ সেটি হলো /opt/actual/data। এতে login ও session-সহ server-files/account.sqlite এবং budget file-সহ user-files থাকে। Copy করার আগে container বন্ধ করুন। কারণ চলমান SQLite database copy করলে অসম্পূর্ণ write সংরক্ষিত হতে পারে। Server-এর অন্য কোনো জায়গায় state সংরক্ষিত থাকে না।
Encryption password হারালে কী হবে?
Budget-এর কোনো local copy এখনো থাকলে encryption key reset করে আবার sync করতে পারবেন। Password এবং সব local copy—দুটিই হারিয়ে গেলে encrypted file পুনরুদ্ধার করা যাবে না। File তৈরি করার সঙ্গে সঙ্গে এটি password manager-এ সংরক্ষণ করুন। একই server-এর ওপর নির্ভর করে না এমন অন্য কোনো স্থানে একটি copy রাখুন।
Actual-এর কতটা server resource প্রয়োজন?
খুব কম। Container static asset ও file পরিবেশন করে, আর budget calculation browser-এ হয়। Disk pressure Actual থেকে নয়, আপনার backup এবং অন্য container-গুলো থেকে আসে। এর পাশাপাশি যদি বেশি resource-প্রয়োজনীয় কোনো service চালাতে হয়, তাহলে সাধারণত photo server-ই ন্যূনতম resource নির্ধারণ করে। তাই plan বেছে নেওয়ার আগে PhotoPrism এবং Immich-এর আসলে কত RAM প্রয়োজন দেখে নিন। Media stack-এর ক্ষেত্রেও একই যুক্তি প্রযোজ্য। Plan নির্ধারণ করে transcoding। অন্যদিকে Jellyfin library-কে হাঁটাহাঁটি করা যায় এমন 90s-এর video store-এ রূপান্তরকারী Halcyon-এর মতো browser front end-এর resource প্রয়োজন Actual-এর মতোই কম।