A Telegram bot that automates the full lifecycle of a Hostinger VPS used as a personal VPN server: change physical location, reinstall the OS, harden the network, install 3x-ui, and provision VLESS+Reality inbounds — all from a chat window.
🇷🇺 Русская версия · 🐛 Debugging story
| Capability | Mechanism |
|---|---|
| Change VPS physical location | Browser automation via Chrome DevTools Protocol (Hostinger's Cloudflare-protected panel) |
| Reset / set root password | Hostinger HTTPS API |
| Power management (start / stop / reboot) | Hostinger HTTPS API |
| Install 3x-ui + UFW + cron + ICMP hardening + BBR | SSH + expect / pipe automation |
| Create VLESS+Reality inbounds with custom remark and uTLS fingerprint | 3x-ui REST API |
Generate ready-to-use vless:// links |
Direct from API + on-server xray x25519 |
| Manage UFW firewall (open / close ports with strict format validation) | SSH |
| Run arbitrary SSH commands | SSH |
The bot is single-user — it ignores any chat that isn't the configured TG_CHAT_ID.
/install_xui → panel port? → ⚙️ install + harden + BBR → ✅ panel link
/setup_inbound → Reality / TLS+CDN → panel port → inbound port →
dest (auto-completes :443 if omitted) → SNI list →
custom remark → fingerprint (Chrome/Safari/iOS/Android) →
✅ inbound created + vless:// link
/firewall → Open / Close / List / Reload →
strict format PORT/tcp or PORT/udp
End-to-end: from a freshly relocated VPS to a working VPN connection in ~10 minutes, without touching SSH or the panel manually.
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Telegram │◄───────►│ bot.py │◄───────►│ Hostinger │
│ (you) │ │ (asyncio, │ API │ API │
└─────────────┘ │ conversation │ └──────────────┘
│ handlers) │
│ │ ┌──────────────┐
│ │◄───────►│ Chrome │
│ │ CDP │ (CF bypass) │
│ │ └──────────────┘
│ │
│ │ ┌──────────────┐
│ │◄───────►│ Hostinger │
│ │ SSH │ VPS │
│ │ │ │
│ │ HTTP │ 3x-ui API │
│ │◄───────►│ │
└─────────┬────────┘ └──────────────┘
│
▼
┌──────────────┐
│ parsers.py │
│ (pure │
│ logic, 9 │
│ functions, │
│ 95 tests) │
└──────────────┘
parsers.py extracts pure-logic functions (output parsing, URL building, ICMP rule manipulation, port validation) so they can be unit-tested without SSH/HTTP dependencies. The same functions are also embedded into remote SSH commands via inspect.getsource() — single source of truth, zero drift between local and remote execution.
sudo apt install google-chrome-stable python3-venv python3-pipgit clone <your-repo-url>
cd hostinger-vps-bot
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
playwright install chromium
playwright install-deps chromiumcp .env.example api_tokens.env
chmod 600 api_tokens.env
nano api_tokens.env # fill in your tokensThe bot reads from api_tokens.env — see .env.example for all variables.
chmod +x start_chrome.shTerminal 1 — dedicated Chrome with Hostinger session:
./start_chrome.sh
# Log in to https://hpanel.hostinger.com manually, pass the Cloudflare check.
# Keep this window open.Terminal 2 — the bot:
source venv/bin/activate
python3 bot.py| Command | What it does |
|---|---|
/start /help |
List commands |
/status |
Current VPS info (IP, location, hostname, state) |
/check |
Test Chrome CDP connection |
| Location & Password | |
/change |
Change VPS location → auto-reset password |
/resetpwd |
Reset root password to env value |
/setpwd |
Set custom root password |
| Power | |
/stop /poweron /reboot |
Power management |
| VPN | |
/install_xui |
Install 3x-ui + UFW + cron + ICMP + BBR |
/setup_inbound |
Create VLESS inbound (Reality / TLS+CDN), with custom remark and uTLS choice |
| Firewall | |
/firewall |
Open / close ports (strict PORT/tcp or PORT/udp), list rules, reload |
| Advanced | |
/ssh |
Run any command on VPS |
/cancel |
Abort any in-progress flow |
pip install -r requirements-dev.txt
pytest -v
pytest --cov=parsers --cov-report=term-missing95 tests · 100 % coverage of parsers.py. Each test class corresponds to a real bug from the project's history — see DEBUGGING.md.
The 9 parser functions:
| Function | Responsibility |
|---|---|
parse_x25519_output |
Handle both legacy and new xray output formats |
normalize_web_base_path |
Canonicalize 3x-ui's webBasePath |
harden_icmp_rules |
Convert ICMP ACCEPT → DROP in before.rules (Ubuntu marker quirks) |
build_panel_url |
Build URLs that work as bases AND for direct display |
is_bbr_active |
Parse sysctl output for BBR status |
is_xui_install_marker |
Read READY/MISSING probe result |
parse_ufw_port_spec |
Strict PORT/tcp or PORT/udp validation |
ensure_port_suffix |
Auto-complete apple.com → apple.com:443 |
parse_sni_list |
Comma-separated SNI list with whitespace + empty handling |
| Limit | Value |
|---|---|
| Location changes | 1 per 30 days |
| Available locations | 9 (EU + Asia + US) |
| Setup time per change | 5–10 minutes |
State after /change |
All VPS data wiped |
hostinger-vps-bot/
├── bot.py # main bot, ~2000 lines
├── parsers.py # pure-logic functions (testable)
├── test_parsers.py # 95 pytest cases
├── data_centers.json # Hostinger location ID reference
├── start_chrome.sh # Chrome launcher with CDP
├── requirements.txt # runtime deps
├── requirements-dev.txt # + pytest
├── .env.example # secrets template
├── .gitignore # ignores api_tokens.env, venv, caches
├── README.md # this file
├── README.ru.md # Russian version
├── DEBUGGING.md # the bug-hunting story
├── DEBUGGING.ru.md # Russian version
└── LICENSE # MIT
api_tokens.envis.gitignored and should bechmod 600- The Chrome profile (
~/chrome-bot-profile/) is separate from your personal browser - The bot enforces
chat_idchecks on every handler — onlyTG_CHAT_IDcan issue commands /setpwddeletes the password message from chat after reading it
MIT — see LICENSE.