1. Marzban VPN Infrastructure (Spain + USA)
What it is: Two production Marzban panels (Spain + USA) serving VLESS Reality and Shadowsocks via Xray-core. Hardened with iptables, fronted by nginx for public subscriptions.
Why VLESS is on port 443
Tested with Atom mobile data: Shadowsocks :1080 connected, but VLESS :2053, VMess :2080 and Trojan :2081 all timed out. The carrier filters uncommon ports. Port 443 (HTTPS) is never filtered, so VLESS Reality moved there:
# On the USA panel host — backup first, always
cp /var/lib/marzban/xray_config.json /var/lib/marzban/xray_config.json.bak2053
# In the Marzban panel: inbounds -> VLESS TCP REALITY -> port 2053 -> 443
# Then restart the container:
cd /opt/marzban && docker compose restart marzban
# Verify xray is actually listening on 443
ss -tlnp | grep ':443' # expect: xray
# Existing customers must refresh their subscription to get the new port.
Firewall — panel locked to one IP
The Marzban dashboard (:8000) is only reachable from the Spain server. Everything else is dropped at the kernel level, before Marzban ever sees it:
# Run ON the USA Marzban host
iptables -A INPUT -i lo -j ACCEPT # localhost always
iptables -A INPUT -p tcp --dport 8000 -s -j ACCEPT # Spain only
iptables -A INPUT -p tcp --dport 8000 -j DROP # everyone else
iptables-save > /etc/iptables/rules.v4 # persist reboot
nginx — public subscriptions without VPN
Problem: customers buy VPN but needed VPN just to fetch their subscription link (both live on :8000). Fix: nginx on :8443 proxies only /sub/* to Marzban; /, /dashboard, /api all return 404. Note Marzban's :8000 is HTTPS with a self-signed cert, so the proxy disables upstream verification:
# /etc/nginx/sites-available/mm-sub-proxy
server {{
listen 8443 ssl;
server_name mm.kxant.eu.cc;
ssl_certificate /etc/letsencrypt/live/mm.kxant.eu.cc/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mm.kxant.eu.cc/privkey.pem;
location /sub/ {{
proxy_pass https://127.0.0.1:8000; # Marzban is HTTPS (self-signed)
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}}
location / {{ return 404; }} # dashboard stays Spain-only
}}
# Point new subscription URLs at the open port.
# /opt/marzban/.env:
XRAY_SUBSCRIPTION_URL_PREFIX="https://mm.kxant.eu.cc:8443"
# IMPORTANT: `docker compose restart` does NOT re-read .env.
# Recreate the container:
cd /opt/marzban && docker compose up -d
docker exec marzban-marzban-1 env | grep XRAY_SUBSCRIPTION_URL_PREFIX
Silence the bot-login Telegram spam
Marzban notifies the linked Telegram ID on every admin login — and the bot logs in constantly. Remove the link from the bot admin only; keep it on the human admin:
python3 -c "
import sqlite3
c = sqlite3.connect('/var/lib/marzban/db.sqlite3')
c.execute(\"UPDATE admins SET telegram_id = NULL WHERE username = 'vpnbot'\")
c.commit()
print(list(c.execute('SELECT username, telegram_id FROM admins')))
"
# -> [('kbko', 8500073574), ('vpnbot', None)]
# Bot logins: silent. Your manual logins: still alert.
Hard lessons: Always cp xray_config.json{,.bak} before inbound edits. docker compose restart ignores .env changes — use up -d. Test ports from the user's real network (Atom), not from the server — server-side checks lie about carrier filtering.
How Spain drives the USA panel — one bot, two panels
Spain is the brain, USA is the worker. The bot on Spain holds two Marzban API clients — one per panel — and every command picks the right one from the server key stored on the user row:
# /root/vpnbot/bot.py — two panels, one brain
mz = Marzban(config.MARZBAN_URL, config.MARZBAN_USERNAME, config.MARZBAN_PASSWORD)
mz_us = Marzban(config.US_MARZBAN_URL, config.US_MARZBAN_USERNAME,
config.US_MARZBAN_PASSWORD)
def mz_for(server):
"""The Marzban client for a Marzban-backed server key."""
if server == "us_marzban":
return mz_us
return mz
# ...inside any handler:
info = await mz_for(server).get_user(mzu) # `server` comes from the buy flow
The buy flow stores server on the user/order row, so renew, freeze, delete and referral credit all hit the correct panel automatically. The USA panel firewall only allows Spain's IP — the API travels over the internet, but nobody else can reach it.
Two traffics, fully separated — the link can't slow your VPN
A natural worry: does the Spain↔USA hop add lag to customer connections? No — there are two completely separate traffics:
MANAGEMENT (Spain <-> USA) DATA (customer <-> server)
───────────────────────── ─────────────────────────
bot -> USA panel API: customer -> USA :443 / :1080
create / renew / delete user DIRECT — Spain never in path
a few KB, only on action
hourly watcher: 1 API login subscription fetch:
customer -> mm.kxant.eu.cc:8443/sub/…
DIRECT to USA nginx
Honest trade-off: if Spain goes down, existing USA customers keep working (their traffic is direct) — but new buys and renewals pause until Spain is back, because the bot lives there. That's why Spain gets a 15-minute freeze auto-recovery plus an hourly health watcher.
Hard lesson: rename the admin → update every saved login
Renamed the Spain panel admin admin → kbko, verified the new login worked — then every bot API call started returning 401 Unauthorized. The panel was fine; the saved logins weren't: /root/vpnbot/.env and /root/resellerbot/.env still said MARZBAN_USERNAME=admin, and the admin user no longer existed:
# Back up both credential files first
cp /root/vpnbot/.env /root/vpnbot/.env.bak-20261018
cp /root/resellerbot/.env /root/resellerbot/.env.bak-20261018
# Update the saved login in both files
# (password redacted here — put the real new panel password)
sed -i "s/^MARZBAN_USERNAME=.*/MARZBAN_USERNAME=kbko/" \
/root/vpnbot/.env /root/resellerbot/.env
# + set MARZBAN_PASSWORD in both files, chmod 600
systemctl restart vpnbot resellerbot
# End-to-end proof: login → create → delete a test user
curl -s -X POST http://127.0.0.1:8000/api/admin/token \
-d "username=kbko&password=…" # → 200 + access_token
Why the watcher healed itself: the hourly health check never caches credentials — it reads them live from /root/vpnbot/.env over SSH on every run. So the first run after the fix went green and sent the recovery notice on its own. Lesson: one credential change means every saved copy — and a watcher that reads live instead of caching is worth it.
2. VidDrop — Telegram Video Downloader
What it is: Paste any video link → get quality buttons + MP3 option → the file arrives in chat. Powered by yt-dlp, capped at 2 concurrent downloads so the VPS never chokes.
Format listing → inline buttons
import yt_dlp, asyncio
def list_formats(url: str):
with yt_dlp.YoutubeDL({{"quiet": True}}) as ydl:
info = ydl.extract_info(url, download=False)
# pick one stream per height, skip absurd sizes
seen, out = set(), []
for f in info.get("formats", []):
h = f.get("height")
if h and h not in seen and h <= 1080:
seen.add(h)
out.append((h, f["format_id"]))
return info.get("title", "video"), sorted(out)
async def on_link(update, context):
title, fmts = await asyncio.to_thread(list_formats, update.message.text)
kb = [[InlineKeyboardButton(f"🎬 {{h}}p", callback_data=f"dl_{{fid}}_{{h}}")]
for h, fid in fmts]
kb.append([InlineKeyboardButton("🎵 MP3", callback_data="dl_mp3")])
context.user_data["dl_url"] = update.message.text
await update.message.reply_text(f"{{title}}\nQuality ရွေးပါ:",
reply_markup=InlineKeyboardMarkup(kb))
Download with a semaphore (max 2 at once)
SEMAPHORE = asyncio.Semaphore(2)
async def on_quality(update, context):
q = update.callback_query
await q.answer()
url = context.user_data["dl_url"]
async with SEMAPHORE: # never more than 2 downloads
if q.data == "dl_mp3":
opts = {{"format": "bestaudio/best",
"postprocessors": [{{"key": "FFmpegExtractAudio",
"preferredcodec": "mp3"}}]}}
else:
_, fid, h = q.data.split("_")
opts = {{"format": fid, "max_filesize": 50 * 1024 * 1024}}
opts.update({{"outtmpl": "/tmp/viddrop/%(id)s.%(ext)s", "quiet": True}})
fname = await asyncio.to_thread(download, url, opts)
await q.message.reply_document(open(fname, "rb"))
os.remove(fname) # don't fill the disk
def download(url, opts):
with yt_dlp.YoutubeDL(opts) as ydl:
info = ydl.extract_info(url, download=True)
return ydl.prepare_filename(info)
Hard lessons: TikTok blocks datacenter IPs — the fix is the user's own logged-in cookies.txt passed via --cookies; never print cookie contents. 50MB is Telegram's bot file cap — enforce max_filesize before downloading, not after.
3. DubLab — AI Video Dubbing Website
What it is: Upload a short video (≤5 min / 250MB) → pick EN↔MM + male/female voice → get a dubbed MP4. Pipeline: faster-whisper transcribes → Gemini translates → Edge-TTS speaks (Burmese voices work!) → FFmpeg muxes. Plus an SRT translator tab.
Step 1 — transcribe with faster-whisper
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8")
def transcribe(path: str):
segments, _ = model.transcribe(path, language="en")
return [{{"start": s.start, "end": s.end, "text": s.text.strip()}}
for s in segments if s.text.strip()]
# Dependency pins that actually work together (faster-whisper 1.2.1):
# av==15.1.0 + huggingface_hub<1
# av 19.x removed the `metadata_errors` kwarg faster-whisper passes -> TypeError.
# av 14.x has no cp312 wheel -> build fails. 15.1.0 is the sweet spot.
Step 2 — translate with Gemini key rotation
Free Gemini quota is tiny (~10–15 req/day/key ≈ 1–2 videos/day). Rotate through keys; when one hits quota, try the next:
import google.generativeai as genai, itertools
KEYS = open("/root/dublab/.env").read().split("GEMINI_API_KEYS=")[1].split(",")
key_cycle = itertools.cycle(k.strip() for k in KEYS)
def translate(text: str, target: str) -> str:
last_err = None
for _ in range(len(KEYS)): # try each key once
genai.configure(api_key=next(key_cycle))
try:
m = genai.GenerativeModel("gemini-2.0-flash")
r = m.generate_content(
f"Translate to {{target}}, return ONLY the translation: {{text}}")
return r.text.strip()
except Exception as e:
last_err = e # quota error -> next key
raise last_err
Step 3 — dub with Edge-TTS (Burmese voices!)
import edge_tts, asyncio
VOICES = {{
"mm-male": "my-MM-ThihaNeural",
"mm-female": "my-MM-NilarNeural",
"en-male": "en-US-ChristopherNeural",
"en-female": "en-US-AriaNeural",
}}
async def speak(text: str, voice: str, out: str):
await edge_tts.Communicate(text, voice).save(out)
# Per segment, then stretch/compress each clip to the segment's
# timestamp window with FFmpeg `atempo` so lips roughly match.
Step 4 — mux with FFmpeg
# dubbed.mp3 = concatenated per-segment clips, original kept quiet underneath
subprocess.run([
"ffmpeg", "-y",
"-i", "original.mp4",
"-i", "dubbed.mp3",
"-filter_complex",
"[0:a]volume=0.25[orig];[1:a]adelay=0[db];[orig][db]amix=inputs=2[a]",
"-map", "0:v", "-map", "[a]",
"-c:v", "copy", "-c:a", "aac",
"output.mp4"], check=True)
Serving — waitress behind nginx, iOS-safe uploads
# waitress on 127.0.0.1 ONLY (never expose the app server directly)
# /etc/systemd/system/dublab.service:
# ExecStart=/root/dublab/venv/bin/waitress-serve --listen=127.0.0.1:5000 app:app
/* iOS Safari bug: fetch() POST with FormData throws
"TypeError: The string did not match the expected pattern" (WebKit bug).
Fix: XMLHttpRequest for uploads, fetch only for JSON polling. */
function uploadFile(file) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const fd = new FormData();
fd.append("video", file);
xhr.open("POST", "/api/dub");
xhr.onload = () => resolve(JSON.parse(xhr.responseText));
xhr.onerror = reject;
xhr.send(fd);
});
}
Hard lessons: Jobs in process memory die on restart — an old job ID becomes invalid after any service restart. Rate-limit uploads (10/hour per IP) or one user eats the whole Gemini quota. The monitor must never auto-reopen the site against an explicit close order.
4. Movie Scraper Bot (Telethon)
What it is: An in-bot movie scraper — searches Telegram channels for movies and serves results inside the bot. Built on Telethon (user client, not bot API) so it can read channel history.
Client setup — secrets never in code
# /root/moviebot/.tg_env (chmod 600) — the ONLY place secrets live:
# API_ID=123456
# API_HASH=abcdef123456
# (first run does interactive login, then moviebot.session persists)
from telethon import TelegramClient
import os
def load_env(path):
d = {{}}
for line in open(path):
if "=" in line and not line.startswith("#"):
k, v = line.strip().split("=", 1)
d[k] = v
return d
env = load_env("/root/moviebot/.tg_env")
client = TelegramClient("/root/moviebot/moviebot", env["API_ID"], env["API_HASH"])
async def search_movies(query: str, limit=10):
results = []
async for msg in client.iter_messages("@movie_channel", search=query, limit=limit):
if msg.video or msg.document:
results.append({{"id": msg.id, "text": msg.text or "", "date": msg.date}})
return results
Dead-token crash loop — the fix
# If the BotFather token dies, the service crash-loops every few seconds.
# That once helped OOM-freeze the whole Spain server (with musicbot).
# Guard: stop + disable until a fresh token exists.
# systemctl stop moviebot && systemctl disable moviebot
# Then paste a NEW token from @BotFather into .tg_env and re-enable.
Hard lessons: A dead token doesn't fail quietly — Telethon retries forever, eating CPU/RAM in a tight loop. Combined with zero swap, two such bots froze the entire Spain VPS. Monitor for crash-looping services, not just stopped ones.
5. OSINT Bot
What it is: A Telegram bot for quick lookups: /ip /email /user /auto /inn /all /proxycheck. Built for speed — one message in, one answer out.
Proxy handling — file format + graceful fallback
Proxies die constantly. The bot reads proxy.txt (host:port:user:pass per line) and falls back to a direct connection instead of hard-failing:
import random, requests
def load_proxies(path="/root/info/proxy.txt"):
proxies = []
for line in open(path):
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split(":") # host:port:user:pass
if len(parts) == 4:
h, p, u, pw = parts
proxies.append(f"http://{{u}}:{{pw}}@{{h}}:{{p}}")
return proxies
PROXIES = load_proxies()
def fetch(url, timeout=15):
last_err = None
for px in random.sample(PROXIES, min(3, len(PROXIES))):
try:
return requests.get(url, proxies={{"http": px, "https": px}},
timeout=timeout).json()
except Exception as e:
last_err = e # proxy dead -> try next
# All proxies failed: direct fallback (better than nothing)
return requests.get(url, timeout=timeout).json()
Command skeleton
async def cmd_ip(update, context):
target = " ".join(context.args)
if not target:
return await update.message.reply_text("Usage: /ip 1.2.3.4")
try:
d = await asyncio.to_thread(fetch, f"http://ip-api.com/json/{{target}}")
await update.message.reply_text(
f"🌐 {{d.get('query')}}\n"
f"📍 {{d.get('city')}}, {{d.get('country')}}\n"
f"🏢 {{d.get('isp')}}\n"
f"🕒 {{d.get('timezone')}}")
except Exception as e:
await update.message.reply_text(f"❌ Lookup failed: {{e}}")
# Same shape for /email /user /inn — one fetcher, one formatter each.
Hard lessons: The original crash was a proxy-format mismatch (host:port:user:pass vs code expecting something else) plus missing imports — always validate the proxy file on startup and log which line failed.
6. Health Watchers & Automation
What it is: The monitoring layer keeping everything alive — hourly VPN health checks, 15-minute service watchers, a freeze auto-recovery, quota watchers, and a daily preventive reboot of all three servers.
The watcher pattern — 2 consecutive failures before alerting
This one rule kills 99% of false alarms. A single failed check means nothing; two in a row means something:
#!/bin/bash
# /root/watchers/vpn-server-health-watch.sh (simplified)
STATE=/root/watchers/state/health.json
URL="https://panel.kbko.qzz.io/"
was_down=$(jq -r .was_down "$STATE")
fails=$(jq -r .fails "$STATE")
if curl -sk --max-time 10 "$URL" | grep -q "Marzban"; then
fails=0
if [ "$was_down" = true ]; then
send_telegram "✅ Panel ပြန်ကောင်းသွားပါပြီ"
fi
was_down=false
else
fails=$((fails + 1))
if [ "$fails" -ge 2 ] && [ "$was_down" = false ]; then
# Second consecutive failure -> real alert, with fix buttons
send_telegram "🚨 Panel down (2 checks)\n\n✅ ပြင်ပါ / ⏭️ ထားလိုက်မယ်"
was_down=true
fi
fi
echo "{{\"was_down\": $was_down, \"fails\": $fails}}" > "$STATE"
# Silent when healthy. Re-alert only after 2 MORE consecutive failures.
# Fix-approval: next run fixes ONLY if the ✅ button was tapped.
Spain freeze auto-recovery
Standing order: "freezeဖြစ်တိုင်း မင်းဘာမင်းစစ်ပြီး လိုအပ်တာ တခါထဲ လုပ်လိုက်ပါ". Every 15 min: if both SSH and the web check fail twice in a row, reboot Spain via the IONOS panel (max once per incident), then verify and report in Burmese:
# Pseudo-logic of spain-freeze-auto-recovery (15-min cron):
ssh_ok=$(timeout 10 ssh spain "echo ok" 2>/dev/null)
web_ok=$(curl -sk --max-time 10 https://n8n.kbko.qzz.io/ -o /dev/null -w "%{{http_code}}")
if [ -z "$ssh_ok" ] && [ "$web_ok" != "200" ]; then
fails=$((fails + 1))
else
fails=0; rebooted_this_incident=false
fi
if [ "$fails" -ge 2 ] && [ "$rebooted_this_incident" = false ]; then
ionos_panel_restart "SPAIN_IP" # Restart action, that server ONLY
rebooted_this_incident=true
sleep 180; verify_services # dublab, vpnbot, nginx, marzban
send_telegram "🔄 Spain freeze → reboot လုပ်ပြီး, service တွေပြန်ကောင်း ✅"
fi
Root cause found (Oct 2025) — and fixed
# NOT a payment issue (contract clean). The real cause:
# - moviebot (dead BotFather token) + musicbot (login pending)
# crash-looped every few seconds
# - 3.8 GB RAM + ZERO swap -> memory-pressure stalls
# (panel showed RUNNING, but SSH + web were dead)
#
# Fix:
systemctl stop moviebot musicbot
systemctl disable moviebot musicbot
fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab # persist reboot
Daily preventive restart (07:00)
# 0 7 * * * /root/watchers/daily-restart.sh
# Spain: reboot via SSH, verify dublab/resellerbot/vpnbot/nginx/marzban
# USA: reboot via SSH, verify marzban container
# Windows: restart via IONOS panel API, verify IP + status On
# Report: one Burmese summary, silent if all green.
| Watcher | Schedule | What |
| vpn-server-health-watch | hourly | SSH + panels, throughput ≤2×/day, fix-approval buttons |
| resellerbot-watch | 15 min | Service + tracebacks + Telegram polling |
| spain-freeze-auto-recovery | 15 min | 2 consecutive full failures → 1 reboot max |
| dublab-gemini-quota-watch | daily 09:43 | Alert when a new Gemini key is needed |
| dublab-reviews-watch | 30 min | Alert on new 5-star reviews |
| hourly-db-replica-usa | hourly | Hot-backup 3 bot DBs Spain → USA, keep last 24 |
Hard lessons: Alerts go only to the admin Telegram ID — never to customers. No proactive watchers unless explicitly requested; the approved set is enough.
🛠️ Universal Patterns
Rules applied to every project, learned the hard way:
- Systemd everywhere:
Restart=always, RestartSec=5, enable --now. A service that can't survive a reboot isn't done.
- Secrets in
.env (chmod 600) — never in code, chat, logs, or screenshots. Bot tokens only in env.
- Backup before changes:
cp file{,.bak$(date +%Y%m%d)}. The 30 seconds it takes has saved hours.
- Create-and-verify before delete — especially user accounts and server migrations. New first, delete old only after the new one works.
- Inline buttons AND
/cmd routes — every bot command, both, always. No exceptions.
- Burmese-first messages — Unicode only (Zawgyi looks broken), short, Telegram-friendly.
- Free-only — every project runs on free tiers. Never suggest paid plans.
- Never break the live VPN bot — new work gets its own token, directory, service, DB, and capped resources.
- Ask before state-changing work — read-only checks are free to run; anything destructive needs approval (except the standing freeze-recovery order).
- Name the object in every closing line — a bare "ပိတ်လိုက်ပြီ" makes him ask what was closed.
1. Marzban VPN Infrastructure (Spain + USA)
ဘာလဲ: Marzban panel ၂ ခု (Spain + USA) — VLESS Reality နဲ့ Shadowsocks ကို Xray-core နဲ့ပေး။ iptables နဲ့ လုံခြုံအောင်လုပ်၊ subscription အတွက် nginx ခံထား။
VLESS ကို ဘာလို့ port 443 ပြောင်းတာလဲ
Atom mobile data နဲ့ စမ်းတာ: Shadowsocks :1080 ရတယ်၊ VLESS :2053, VMess :2080, Trojan :2081 အကုန် timeout။ Carrier က port ထူးထူးဆန်းဆန်းတွေ ပိတ်တယ်။ 443 (HTTPS) ကို ဘယ်တော့မှ မပိတ်ဘူး:
# USA panel host ပေါ်မှာ — အရင်ဆုံး backup ယူ (မဖြစ်မနေ)
cp /var/lib/marzban/xray_config.json /var/lib/marzban/xray_config.json.bak2053
# Marzban panel ထဲမှာ: inbounds -> VLESS TCP REALITY -> port 2053 -> 443
cd /opt/marzban && docker compose restart marzban
# xray တကယ် 443 နားထောင်နေလား စစ်
ss -tlnp | grep ':443' # xray လို့ ပေါ်ရမယ်
# Customer အဟောင်းတွေ subscription refresh လုပ်မှ port အသစ်ရမယ်။
Firewall — တစ်ကောင် IP တည်းကပဲ panel ဝင်လို့ရ
Marzban dashboard (:8000) ကို Spain server ကပဲ ဝင်လို့ရ။ ကျန်တာ kernel level မှာတင် ပစ်:
# USA Marzban host ပေါ်မှာ run
iptables -A INPUT -i lo -j ACCEPT # localhost အမြဲ
iptables -A INPUT -p tcp --dport 8000 -s -j ACCEPT # Spain ပဲ
iptables -A INPUT -p tcp --dport 8000 -j DROP # ကျန် အကုန်
iptables-save > /etc/iptables/rules.v4 # reboot လည်း မပျက်
nginx — VPN မလိုပဲ subscription ပေး
ပြဿနာ: customer က VPN ဝယ်ပြီး subscription link ယူဖို့ VPN အရင်ကျော်ရတယ် (:8000 ပေါ်မှာ ၂ ခုလုံးရှိလို့)။ အဖြေ: nginx :8443 က /sub/* ပဲ Marzban ဆီ ပို့; /, /dashboard, /api အကုန် 404။ သတိ: Marzban ရဲ့ :8000 က HTTPS (self-signed) မို့ proxy က verify ပိတ်ရမယ်:
# /etc/nginx/sites-available/mm-sub-proxy
server {{
listen 8443 ssl;
server_name mm.kxant.eu.cc;
ssl_certificate /etc/letsencrypt/live/mm.kxant.eu.cc/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mm.kxant.eu.cc/privkey.pem;
location /sub/ {{
proxy_pass https://127.0.0.1:8000; # Marzban က HTTPS (self-signed)
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}}
location / {{ return 404; }} # dashboard က Spain ပဲ
}}
# subscription URL အသစ်တွေ port အဖွင့်ကို ညွှန်း
# /opt/marzban/.env:
XRAY_SUBSCRIPTION_URL_PREFIX="https://mm.kxant.eu.cc:8443"
# သတိ: `docker compose restart` က .env အပြောင်းကို မဖတ်ဘူး။
# Container အသစ်ပြန်တည်:
cd /opt/marzban && docker compose up -d
docker exec marzban-marzban-1 env | grep XRAY_SUBSCRIPTION_URL_PREFIX
Bot login Telegram spam ပိတ်
Marzban က admin login တိုင်း ချိတ်ထားတဲ့ Telegram ID ကို notify ပို့တယ် — bot က ခဏခဏ login ဝင်တယ်။ bot admin ရဲ့ဟာပဲ ဖြုတ်၊ လူရဲ့ဟာ ချန်ထား:
python3 -c "
import sqlite3
c = sqlite3.connect('/var/lib/marzban/db.sqlite3')
c.execute(\"UPDATE admins SET telegram_id = NULL WHERE username = 'vpnbot'\")
c.commit()
print(list(c.execute('SELECT username, telegram_id FROM admins')))
"
# -> [('kbko', 8500073574), ('vpnbot', None)]
# Bot login: တိတ်ဆိတ်။ ကိုယ် login ဝင်ရင်: alert လာဦးမယ်။
သင်ခန်းစာများ: Inbound ပြင်ခင် cp xray_config.json{,.bak} အမြဲလုပ်။ docker compose restart က .env ကို မဖတ်ဘူး — up -d သုံး။ Port စမ်းရင် server ပေါ်ကမဟုတ်ပဲ user ရဲ့ တကယ့် network (Atom) ကနေ စမ်း — server ဘက်က စစ်တာ carrier filtering ကို မပြဘူး။
Spain က USA panel ကို ဘယ်လို ထိန်းသလဲ — bot တစ်ခု, panel နှစ်ခု
Spain က ဦးနှောက်, USA က အလုပ်သမား။ Spain ပေါ်က bot မှာ Marzban API client နှစ်ခု ရှိ — panel တစ်ခုကို တစ်ခု — command တိုင်းက user row မှာ သိမ်းထားတဲ့ server key နဲ့ မှန်တာကို ရွေး:
# /root/vpnbot/bot.py — panel နှစ်ခု, ဦးနှောက် တစ်ခု
mz = Marzban(config.MARZBAN_URL, config.MARZBAN_USERNAME, config.MARZBAN_PASSWORD)
mz_us = Marzban(config.US_MARZBAN_URL, config.US_MARZBAN_USERNAME,
config.US_MARZBAN_PASSWORD)
def mz_for(server):
"""Marzban-backed server key အတွက် Marzban client."""
if server == "us_marzban":
return mz_us
return mz
# ...handler တိုင်းထဲမှာ:
info = await mz_for(server).get_user(mzu) # `server` က buy flow က လာ
Buy flow က user/order row မှာ server သိမ်းထား — renew, freeze, delete, referral အားလုံး မှန် panel ကို အလိုအလျောက် သွား။ USA panel ရဲ့ firewall က Spain IP ပဲ ခွင့်ပြုထား — API က internet ပေါ် ဖြတ်သွားပေမယ့် တခြားသူ ဝင်မရဘူး။
Traffic နှစ်မျိုး လုံးဝ ခွဲ — link က VPN ကို မနှေးစေဘူး
သဘာဝကျတဲ့ စိုးရိမ်စိတ်: Spain↔USA hop က customer connection ကို နှေးစေမလား? မနှေးဘူး — traffic နှစ်မျိုး လုံးဝ သီးသန့်:
MANAGEMENT (Spain <-> USA) DATA (customer <-> server)
───────────────────────── ─────────────────────────
bot -> USA panel API: customer -> USA :443 / :1080
user ဖွင့် / renew / ဖျက် တိုက်ရိုက် — Spain မဖြတ်
KB အနည်းငယ်, လုပ်မှသာ
နာရီတိုင်း watcher: API login ၁ ခု subscription ဆွဲ:
customer -> mm.kxant.eu.cc:8443/sub/…
USA nginx တိုက်ရိုက်
ရိုးရိုးသားသား ပြောရရင်: Spain ကျသွားရင် USA customer အဟောင်းတွေ ဆက်သုံးလို့ရ (traffic တိုက်ရိုက်မို့) — ဒါပေမယ့် အသစ်ဝယ် / renew ခဏ ရပ်, bot က Spain မှာမို့။ ဒါကြောင့် Spain ကို 15 မိနစ်တစ်ခါ freeze auto-recovery + တစ်နာရီတစ်ခါ health watcher ထားတာ။
သင်ခန်းစာ: admin နာမည်ပြောင်းရင် သိမ်းထားတဲ့ login တိုင်း ပြင်ရ
Spain panel admin ကို admin → kbko ပြောင်း, login အသစ် စမ်းပြီး အဆင်ပြေ — ပြီးတော့ bot API call တိုင်း 401 Unauthorized ပြန်လာ။ Panel က ကောင်း; သိမ်းထားတဲ့ login တွေ မကောင်း: /root/vpnbot/.env နဲ့ /root/resellerbot/.env မှာ MARZBAN_USERNAME=admin ကျန်နေ, admin user က မရှိတော့:
# credential file နှစ်ခုလုံး အရင် backup ယူ
cp /root/vpnbot/.env /root/vpnbot/.env.bak-20261018
cp /root/resellerbot/.env /root/resellerbot/.env.bak-20261018
# နှစ်ခုလုံးမှာ saved login ပြင်
# (password ဒီမှာ ဖျက်ပြထား — panel password အသစ်အစစ် ထည့်)
sed -i "s/^MARZBAN_USERNAME=.*/MARZBAN_USERNAME=kbko/" \
/root/vpnbot/.env /root/resellerbot/.env
# + MARZBAN_PASSWORD ကို နှစ်ခုလုံးမှာ ထား, chmod 600
systemctl restart vpnbot resellerbot
# အစအဆုံး သက်သေ: login → user အစမ်းဖန်တီး → ဖျက်
curl -s -X POST http://127.0.0.1:8000/api/admin/token \
-d "username=kbko&password=…" # → 200 + access_token
Watcher က ဘာလို့ သူ့ဘာသာ ပြန်ကောင်းသွားလဲ: နာရီတိုင်း health check က credential သိမ်းမထား — run တိုင်း /root/vpnbot/.env ကို SSH နဲ့ အရှင်လတ်လတ် ဖတ်။ ဒါကြောင့် ပြင်ပြီးနောက် ပထမ run ကျရင် စိမ်းသွား, recovery notice အလိုအလျောက် ပို့။ သင်ခန်းစာ: credential တစ်ခုပြောင်းရင် သိမ်းထားတဲ့ copy တိုင်း လိုက် — cache မလုပ်ပဲ အရှင်ဖတ်တဲ့ watcher က တန်ဖိုးရှိ။
2. VidDrop — Video Downloader Bot
ဘာလဲ: Video link ပို့ → quality button + MP3 → file က chat ထဲရောက်။ yt-dlp သုံး၊ တစ်ပြိုင်နက် 2 ခုပဲ download လုပ် (VPS မပိ)။
Format list → inline button
import yt_dlp, asyncio
def list_formats(url: str):
with yt_dlp.YoutubeDL({{"quiet": True}}) as ydl:
info = ydl.extract_info(url, download=False)
seen, out = set(), []
for f in info.get("formats", []):
h = f.get("height")
if h and h not in seen and h <= 1080:
seen.add(h)
out.append((h, f["format_id"]))
return info.get("title", "video"), sorted(out)
async def on_link(update, context):
title, fmts = await asyncio.to_thread(list_formats, update.message.text)
kb = [[InlineKeyboardButton(f"🎬 {{h}}p", callback_data=f"dl_{{fid}}_{{h}}")]
for h, fid in fmts]
kb.append([InlineKeyboardButton("🎵 MP3", callback_data="dl_mp3")])
context.user_data["dl_url"] = update.message.text
await update.message.reply_text(f"{{title}}\nQuality ရွေးပါ:",
reply_markup=InlineKeyboardMarkup(kb))
Semaphore နဲ့ download (အများ 2 ခု)
SEMAPHORE = asyncio.Semaphore(2)
async def on_quality(update, context):
q = update.callback_query
await q.answer()
url = context.user_data["dl_url"]
async with SEMAPHORE: # 2 ခုထက် မပို
if q.data == "dl_mp3":
opts = {{"format": "bestaudio/best",
"postprocessors": [{{"key": "FFmpegExtractAudio",
"preferredcodec": "mp3"}}]}}
else:
_, fid, h = q.data.split("_")
opts = {{"format": fid, "max_filesize": 50 * 1024 * 1024}}
opts.update({{"outtmpl": "/tmp/viddrop/%(id)s.%(ext)s", "quiet": True}})
fname = await asyncio.to_thread(download, url, opts)
await q.message.reply_document(open(fname, "rb"))
os.remove(fname) # disk မပြည့်
def download(url, opts):
with yt_dlp.YoutubeDL(opts) as ydl:
info = ydl.extract_info(url, download=True)
return ydl.prepare_filename(info)
သင်ခန်းစာများ: TikTok က datacenter IP ပိတ်တယ် — အဖြေက user ရဲ့ ကိုယ်ပိုင် login cookies.txt ကို --cookies နဲ့ပေး; cookie အကြောင်း ဘယ်တော့မှ မထုတ်။ 50MB က Telegram bot limit — download မလုပ်ခင် max_filesize ကန့်သတ်။
3. DubLab — AI Video Dubbing Website
ဘာလဲ: Video တင် (၅ မိနစ်အထိ / 250MB) → EN↔MM + အသံရွေး → dub ထားတဲ့ MP4 ရ။ Pipeline: faster-whisper စာသားထုတ် → Gemini ဘာသာပြန် → Edge-TTS အသံထွက် (မြန်မာအသံရတယ်!) → FFmpeg ပေါင်း။ SRT translator tab ပါ။
အဆင့် 1 — faster-whisper နဲ့ စာသားထုတ်
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8")
def transcribe(path: str):
segments, _ = model.transcribe(path, language="en")
return [{{"start": s.start, "end": s.end, "text": s.text.strip()}}
for s in segments if s.text.strip()]
# အလုပ်ဖြစ်တဲ့ version အတွဲ (faster-whisper 1.2.1):
# av==15.1.0 + huggingface_hub<1
# av 19.x က faster-whisper သုံးတဲ့ `metadata_errors` ကို ဖြုတ်ပစ် -> TypeError။
# av 14.x မှာ cp312 wheel မရှိ -> build ပျက်။ 15.1.0 က အကောင်းဆုံး။
အဆင့် 2 — Gemini နဲ့ ဘာသာပြန် (key အလှည့်ကျ)
Gemini အခမဲ့ quota က သေးတယ် (~တစ်နေ့ 10–15 req/key ≈ 1–2 video)။ Key တွေ အလှည့်ကျသုံး၊ တစ်ခုပြည့်ရင် နောက်တစ်ခု:
import google.generativeai as genai, itertools
KEYS = open("/root/dublab/.env").read().split("GEMINI_API_KEYS=")[1].split(",")
key_cycle = itertools.cycle(k.strip() for k in KEYS)
def translate(text: str, target: str) -> str:
last_err = None
for _ in range(len(KEYS)): # key တစ်ခုချင်း တစ်ခါစမ်း�
genai.configure(api_key=next(key_cycle))
try:
m = genai.GenerativeModel("gemini-2.0-flash")
r = m.generate_content(
f"Translate to {{target}}, return ONLY the translation: {{text}}")
return r.text.strip()
except Exception as e:
last_err = e # quota ပြည့် -> နောက် key
raise last_err
အဆင့် 3 — Edge-TTS နဲ့ အသံထွက် (မြန်မာအသံ!)
import edge_tts, asyncio
VOICES = {{
"mm-male": "my-MM-ThihaNeural",
"mm-female": "my-MM-NilarNeural",
"en-male": "en-US-ChristopherNeural",
"en-female": "en-US-AriaNeural",
}}
async def speak(text: str, voice: str, out: str):
await edge_tts.Communicate(text, voice).save(out)
# အပိုင်းတစ်ခုချင်း အသံထွက်၊ ပြီးရင် timestamp window နဲ့ ကိုက်အောင်
# FFmpeg `atempo` နဲ့ ဆွဲ/ညှစ် — နှုတ်ခမ်းလှုပ်နဲ့ ခန့်မှန်းတူ။
အဆင့် 4 — FFmpeg နဲ့ ပေါင်း
# dubbed.mp3 = အပိုင်းလိုက် clip တွေဆက်, မူရင်းအသံ တိုးတိုးလေးချန်ထား
subprocess.run([
"ffmpeg", "-y",
"-i", "original.mp4",
"-i", "dubbed.mp3",
"-filter_complex",
"[0:a]volume=0.25[orig];[1:a]adelay=0[db];[orig][db]amix=inputs=2[a]",
"-map", "0:v", "-map", "[a]",
"-c:v", "copy", "-c:a", "aac",
"output.mp4"], check=True)
Serve လုပ်ပုံ — nginx နောက်က waitress, iOS အတွက်
# waitress ကို 127.0.0.1 ပဲဖွင့် (app server ကို တိုက်ရိုက်မပြ)
# /etc/systemd/system/dublab.service:
# ExecStart=/root/dublab/venv/bin/waitress-serve --listen=127.0.0.1:5000 app:app
/* iOS Safari bug: FormData နဲ့ fetch() POST တင်ရင်
"TypeError: The string did not match the expected pattern" (WebKit bug)။
အဖြေ: upload ကို XMLHttpRequest သုံး, fetch က JSON polling ပဲ။ */
function uploadFile(file) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const fd = new FormData();
fd.append("video", file);
xhr.open("POST", "/api/dub");
xhr.onload = () => resolve(JSON.parse(xhr.responseText));
xhr.onerror = reject;
xhr.send(fd);
});
}
သင်ခန်းစာများ: Job တွေ memory ထဲမှာပဲရှိတယ် — service restart လုပ်ရင် job ID အဟောင်း ပျက်။ Upload ကန့်သတ် (IP တစ်ခု ၁ နာရီ 10 ခု)၊ မဟုတ်ရင် user တစ်ယောက် Gemini quota အကုန်စား။ Monitor က ပိတ်ထားတဲ့ site ကို သူ့ဟာသူ ပြန်ဖွင့်လို့ မရ။
4. Movie Scraper Bot (Telethon)
ဘာလဲ: Bot ထဲကနေ ရုပ်ရှာတဲ့ scraper — Telegram channel တွေမှာ ရုပ်ရှာပြီး bot ထဲပဲ ပြန်။ Telethon (user client, bot API မဟုတ်) သုံးလို့ channel history ဖတ်လို့ရ။
Client တည် — secret ကို code ထဲ လုံးဝမထည့်
# /root/moviebot/.tg_env (chmod 600) — secret ရှိတဲ့ တစ်နေရာတည်း:
# API_ID=123456
# API_HASH=abcdef123456
# (ပထမအကြိမ် interactive login, ပြီးရင် moviebot.session သိမ်း)
from telethon import TelegramClient
def load_env(path):
d = {{}}
for line in open(path):
if "=" in line and not line.startswith("#"):
k, v = line.strip().split("=", 1)
d[k] = v
return d
env = load_env("/root/moviebot/.tg_env")
client = TelegramClient("/root/moviebot/moviebot", env["API_ID"], env["API_HASH"])
async def search_movies(query: str, limit=10):
results = []
async for msg in client.iter_messages("@movie_channel", search=query, limit=limit):
if msg.video or msg.document:
results.append({{"id": msg.id, "text": msg.text or "", "date": msg.date}})
return results
Token သေရင် crash loop — အဖြေ
# BotFather token သေရင် service က စက္ကန့်အနည်းငယ်ခြား crash-loop လည်။
# ဒါက Spain server တစ်ခုလုံး freeze ဖြစ်စေခဲ့တဲ့ အကြောင်းတစ်ခု (musicbot နဲ့အတူ)။
# ကာကွယ်: token အသစ်မရမချင်း stop + disable လုပ်။
# systemctl stop moviebot && systemctl disable moviebot
# @BotFather က token အသစ်ယူ, .tg_env ထဲထည့်, ပြီးမှ re-enable။
သင်ခန်းစာများ: Token သေတာ တိတ်တိတ်မပျက်ဘူး — Telethon က ထာဝရ retry လုပ်၊ CPU/RAM ကို ကျပ်ကျပ်စား။ Swap မရှိရင် bot ၂ ခု ဒီလိုလည်တာ VPS တစ်ခုလုံး freeze ဖြစ်။ ရပ်နေတဲ့ service ပဲမဟုတ်၊ crash-loop လည်နေတာပါ စောင့်ကြည့်။
5. OSINT Bot
ဘာလဲ: အမြန်ရှာဖွေတဲ့ bot: /ip /email /user /auto /inn /all /proxycheck။ မြန်ဖို့အဓိက — တစ်စောင်ပို့၊ တစ်ဖြေရ။
Proxy ကိုင်တွယ် — format + ပျက်ရင် fallback
Proxy တွေ ခဏခဏသေတယ်။ Bot က proxy.txt (host:port:user:pass တစ်ကြောင်း) ဖတ်၊ ပျက်ရင် တိုက်ရိုက်ဆက် (hard-fail မဖြစ်):
import random, requests
def load_proxies(path="/root/info/proxy.txt"):
proxies = []
for line in open(path):
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split(":") # host:port:user:pass
if len(parts) == 4:
h, p, u, pw = parts
proxies.append(f"http://{{u}}:{{pw}}@{{h}}:{{p}}")
return proxies
PROXIES = load_proxies()
def fetch(url, timeout=15):
last_err = None
for px in random.sample(PROXIES, min(3, len(PROXIES))):
try:
return requests.get(url, proxies={{"http": px, "https": px}},
timeout=timeout).json()
except Exception as e:
last_err = e # proxy သေ -> နောက်တစ်ခု
# proxy အကုန်ပျက်: တိုက်ရိုက်ဆက် (ဘာမှမရတာထက် ကောင်း)
return requests.get(url, timeout=timeout).json()
Command ပုံစံ
async def cmd_ip(update, context):
target = " ".join(context.args)
if not target:
return await update.message.reply_text("Usage: /ip 1.2.3.4")
try:
d = await asyncio.to_thread(fetch, f"http://ip-api.com/json/{{target}}")
await update.message.reply_text(
f"🌐 {{d.get('query')}}\n"
f"📍 {{d.get('city')}}, {{d.get('country')}}\n"
f"🏢 {{d.get('isp')}}\n"
f"🕒 {{d.get('timezone')}}")
except Exception as e:
await update.message.reply_text(f"❌ Lookup failed: {{e}}")
# /email /user /inn လည်း ဒီပုံပဲ — fetcher တစ်ခု, formatter တစ်ခုစီ။
သင်ခန်းစာများ: အရင် crash က proxy format မကိုက်တာ (host:port:user:pass ကို code က တခြားဟာထင်နေ) + import ပျောက် — startup မှာ proxy file ကို အမြဲ validate လုပ်၊ ဘယ် line ပျက်လဲ log ထား။
6. Health Watchers & Automation
ဘာလဲ: အရာအားလုံး အသက်ရှင်အောင် စောင့်တဲ့ အလွှာ — ၁ နာရီ VPN စစ်၊ 15 မိနစ် service watcher၊ freeze auto-recovery၊ quota watcher၊ server ၃ လုံး နေ့တိုင်း မနက် reboot။
Watcher ပုံစံ — 2 ခါဆက် fail မှ alert
ဒီစည်းမျဉ်းတစ်ခုက false alarm 99% သေ။ တစ်ခါပျက်တာ ဘာမှမဟုတ်; ၂ ခါဆက်ပျက်တာ တကယ်:
#!/bin/bash
# /root/watchers/vpn-server-health-watch.sh (အတိုချုပ်)
STATE=/root/watchers/state/health.json
URL="https://panel.kbko.qzz.io/"
was_down=$(jq -r .was_down "$STATE")
fails=$(jq -r .fails "$STATE")
if curl -sk --max-time 10 "$URL" | grep -q "Marzban"; then
fails=0
if [ "$was_down" = true ]; then
send_telegram "✅ Panel ပြန်ကောင်းသွားပါပြီ"
fi
was_down=false
else
fails=$((fails + 1))
if [ "$fails" -ge 2 ] && [ "$was_down" = false ]; then
# ဒုတိယ ဆက်တိုက်ပျက် -> တကယ် alert, ပြင်ခွင့်ခလုတ်နဲ့
send_telegram "🚨 Panel down (2 checks)\n\n✅ ပြင်ပါ / ⏭️ ထားလိုက်မယ်"
was_down=true
fi
fi
echo "{{\"was_down\": $was_down, \"fails\": $fails}}" > "$STATE"
# ကောင်းနေရင် တိတ်ဆိတ်။ 2 ခါထပ်ဆက်ပျက်မှ ထပ် alert။
# ✅ နှိပ်ထားမှသာ နောက် run က ပြင်။
Spain freeze auto-recovery
အမိန့်အတိုင်း: "freezeဖြစ်တိုင်း မင်းဘာမင်းစစ်ပြီး လိုအပ်တာ တခါထဲ လုပ်လိုက်ပါ"။ 15 မိနစ်တိုင်း: SSH ရော web စစ်တာရော ၂ ခါဆက် ပျက်ရင် IONOS panel ကနေ Spain ကို reboot (တစ်ဖြစ်စဉ်မှာ တစ်ခါပဲ), ပြီးရင် စစ်, မြန်မာလို report:
# spain-freeze-auto-recovery အကျဉ်းချုပ် (15-မိနစ် cron):
ssh_ok=$(timeout 10 ssh spain "echo ok" 2>/dev/null)
web_ok=$(curl -sk --max-time 10 https://n8n.kbko.qzz.io/ -o /dev/null -w "%{{http_code}}")
if [ -z "$ssh_ok" ] && [ "$web_ok" != "200" ]; then
fails=$((fails + 1))
else
fails=0; rebooted_this_incident=false
fi
if [ "$fails" -ge 2 ] && [ "$rebooted_this_incident" = false ]; then
ionos_panel_restart "SPAIN_IP" # ဒီ server ပဲ, Restart ပဲ
rebooted_this_incident=true
sleep 180; verify_services # dublab, vpnbot, nginx, marzban
send_telegram "🔄 Spain freeze → reboot လုပ်ပြီး, service တွေပြန်ကောင်း ✅"
fi
အကြောင်းရင်းအစစ် တွေ့ပြီ (Oct 2025) — ပြင်ပြီးပြီ
# ပိုက်ဆံပြဿနာ မဟုတ် (contract သန့်)။ အကြောင်းအစစ်:
# - moviebot (BotFather token သေ) + musicbot (login စောင့်)
# စက္ကန့်အနည်းငယ်ခြား crash-loop လည်
# - RAM 3.8 GB + swap လုံးဝမရှိ -> memory ဖိအားကြောင့် ရပ်
# (panel မှာ RUNNING လို့ပြ, ဒါပေမယ့် SSH + web သေ)
#
# အဖြေ:
systemctl stop moviebot musicbot
systemctl disable moviebot musicbot
fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab # reboot လည်း မပျက်
နေ့တိုင်း မနက် reboot (07:00)
# 0 7 * * * /root/watchers/daily-restart.sh
# Spain: SSH နဲ့ reboot, dublab/resellerbot/vpnbot/nginx/marzban စစ်
# USA: SSH နဲ့ reboot, marzban container စစ်
# Windows: IONOS panel API နဲ့ restart, IP + status On စစ်
# Report: မြန်မာလို အတိုချုပ် တစ်စောင်, အကုန်စိမ်းရင် တိတ်ဆိတ်။
| Watcher | အချိန် | ဘာလုပ်လဲ |
| vpn-server-health-watch | ၁ နာရီ | SSH + panel, throughput ≤တစ်နေ့ 2 ခါ, ပြင်ခွင့်ခလုတ် |
| resellerbot-watch | 15 မိနစ် | Service + traceback + Telegram polling |
| spain-freeze-auto-recovery | 15 မိနစ် | ၂ ခါဆက်လုံးဝပျက် → အများ 1 reboot |
| dublab-gemini-quota-watch | နေ့တိုင်း 09:43 | Gemini key အသစ် သတိပေး |
| dublab-reviews-watch | မိနစ် 30 | 5-star review အသစ် သတိပေး |
| hourly-db-replica-usa | ၁ နာရီ | Bot DB ၃ ခု Spain → USA hot-backup, 24 ခုသိမ်း |
သင်ခန်းစာများ: Alert က admin Telegram ID သီးသန့် — customer ဆီ ဘယ်တော့မှ မပို့။ တောင်းဆိုမှသာ watcher အသစ်ထည်; ခွင့်ပြုထားတဲ့ အစုက လုံလောက်ပြီ။
🛠️ Project တိုင်းမှာ သုံးတဲ့ ပုံစံများ
Project တိုင်းမှာ သုံးတဲ့ စည်းမျဉ်းများ — ခက်ခက်ခဲခဲ သင်ခဲ့ရတာ:
- Systemd နေရာတိုင်း:
Restart=always, RestartSec=5, enable --now။ Reboot လည်း မရှင်တဲ့ service က မပြီးသေးဘူး။
- Secret တွေ
.env (chmod 600) ထဲပဲ — code, chat, log, screenshot မှာ ဘယ်တော့မှ မထည့်။ Bot token က env မှာပဲ။
- မပြင်ခင် backup:
cp file{,.bak$(date +%Y%m%d)}။ စက္ကန့် 30 ပေးရတာ နာရီနဲ့ချီ ကယ်ခဲ့ပြီ။
- အသစ်တည်-စမ်း-ပြီးမှ အဟောင်းဖျက် — user account နဲ့ server ပြောင်းတာ အထူးသတိ။ အသစ်အလုပ်ဖြစ်မှ အဟောင်းဖျက်။
- Inline button ရော
/cmd ရော — bot command တိုင်း ၂ ခုလုံး၊ အမြဲ။ ခြွင်းချက်မရှိ။
- မြန်မာလို အရင် — Unicode သီးသန့် (Zawgyi က ပျက်), စာတို, Telegram နဲ့ လိုက်ဖက်။
- အခမဲ့သီးသန့် — project တိုင်း free tier ပေါ်။ Paid plan ဘယ်တော့မှ မညွှန်း။
- Live VPN bot ကို မထိနဲ့ — အလုပ်သစ်တိုင်း ကိုယ်ပိုင် token, dir, service, DB, resource ကန့်သတ်။
- ပြင်ဆင်မှုမလုပ်ခင် မေး — read-only စစ်ဆေးတာ လွတ်လွတ်လုပ်လို့ရ; ဖျက်ဆီးနိုင်တာ ခွင့်တောင်း (freeze-recovery အမိန့်ကလွဲရင်)။
- အပိတ်စာတိုင်း အရာဝတ္ထုနာမည်ပါ — "ပိတ်လိုက်ပြီ" ချည်းဆို ဘာ ပိတ်လဲ မေးခံရမယ်။