One Binary to Manage an SSH Tunnel Server: Abdal 4iProto Panel

go dev.to

Why a single binary matters for tunnel administration

If you have ever stood up an SSH tunnel server on a fresh VPS, you know the real cost is not the server—it is everything around it. You need a way to add and revoke users, cap their bandwidth, watch sessions in real time, block abusive IPs, and survive a reboot without a 12-step runbook. The usual answer is a stack: a runtime, a web framework, a database, a reverse proxy, a process manager. Each layer is one more thing that breaks at 2 a.m.

Abdal 4iProto Panel takes the opposite bet. It is a web-based management interface for the Abdal 4iProto Server—a dedicated SSH tunnel server—written in Go and compiled to a single executable with embedded resources. CSS, JS, HTML templates, and translation files all live inside the binary. There is no runtime to install, no external dependency to pin, no asset directory to ship. You drop one file on Windows or Linux, optionally register it as a service, and you have a full admin console. This article walks through how it is built and how an expert operator would actually use it.

🔗 Panel repo: https://github.com/ebrasha/abdal-4iproto-panel
🔗 Server repo: https://github.com/ebrasha/abdal-4iproto-server

The architecture in one sentence

The panel is a Go process that embeds its own web assets, persists configuration as flat JSON files, talks to the 4iProto SSH tunnel server it manages, and can run as a Windows Service or a Linux systemd unit. That is the whole shape of it—and the simplicity is the point.

Configuration lives in plain JSON, not a database engine. On first run the panel writes abdal-4iproto-panel.json:

{"port":52202,"username":"ebrasha","password":"ebrasha1309","logging":true,"blocked_ips":[],"max_login_attempts":5,"login_attempt_window":300,"block_duration":3600}
Enter fullscreen mode Exit fullscreen mode

For a tunnel management surface, JSON state is a defensible choice: the data set (users, blocked IPs, server config) is small, human-auditable, trivially backed up with cp, and never introduces a separate failure domain. You can inspect or diff the live state with tools you already have. Note the SSH-native footprint of the install set, too—id_ed25519 and id_ed25519.pub ship alongside the binaries, since the server is doing real SSH tunneling.

⚠️ Change ebrasha1309 immediately after first login. Defaults are for first boot, not production.

The administrative workflow

Point a browser at http://localhost:52202 (or your configured port), authenticate, and you land on a dashboard summarizing users, sessions, and traffic. From there the work is organized around the lifecycle of a tunnel deployment rather than a flat settings dump.

User provisioning is full CRUD, but the interesting part is per-user policy. Each account carries far more than a username and password: a role (admin/user), blocked domains and IPs scoped to that user, concurrent session limits and TTL, a speed cap in KB/s, a traffic quota in MB, and individual logging preferences. This is enough to run differentiated tiers—a throttled trial account and an uncapped admin account—without touching the server binary.

Server configuration is edited from the same UI: listening ports (multiple are supported), the default shell for Windows/Linux targets, the maximum authentication attempts, and even the server's advertised version signature. The panel writes these to the server's config and the change takes effect on the managed service.

Security is handled at two layers. There is straightforward IP blocking, and there is an automatic brute-force defense with a configurable attempt ceiling, a sliding time window, and a block duration. Cross the threshold inside the window and the offending IP is blocked automatically for the configured period—no manual intervention.

Monitoring is where an operator spends real time. User access logs update over Ajax without a full page reload. Traffic monitoring tracks consumption per user—session-based and cumulative—and surfaces warnings as accounts approach their quota. The sessions view reflects live state from the server: session ID, username, source IP, client version, creation time, and last-seen timestamp, with the ability to revoke a session.

The whole console is bilingual (English and Persian), responsive down to a mobile hamburger menu, and—because configuration changes can require a clean reload—the panel auto-restarts its own service after you save, so applied settings are never half-live.

The Telegram bot: a study in isolation

The feature most worth dissecting is the optional Telegram bot, which mirrors every administrative action from the web UI. Enabling it is a config flow: create a bot via @BotFather, grab your numeric ID from @userinfobot, then in Panel Configuration → 🤖 Telegram Bot tick enable, paste the token, and list admin IDs. Settings persist under a telegram_bot key:

"telegram_bot":{"enabled":true,"token":"123456789:AA...your-bot-token","admins":[111111111,222222222]}
Enter fullscreen mode Exit fullscreen mode

The security model is deliberately blunt: the bot drops every update that does not originate from an admin Telegram ID and replies with a short "not authorized" message, so an attacker cannot silently probe behavior.

What makes the bot worth studying is its concurrency design. It is not a side thread bolted onto the request handler—it is a fully isolated subsystem inside the panel process, with its own HTTP connection pool, dispatcher worker pool, asynchronous log channel, and a debounced restart queue. The explicit goal: a busy panel UI or slow disk I/O can never slow the bot down. The update pipeline:

┌─────────────────────────┐      ┌──────────────────────┐
│ Telegram getUpdates     │ ───► │ updates channel      │
│ (1 goroutine)           │      │ (cap 1024)           │
└─────────────────────────┘      └──────────┬───────────┘
                                            │
                          ┌─────────────────┼─────────────────┐
                          ▼                 ▼                 ▼
                   ┌──────────┐      ┌──────────┐      ┌──────────┐
                   │ worker 1 │ ...  │ worker N │      │ worker N │ (NumCPU*2, ≥8)
                   └────┬─────┘      └────┬─────┘      └────┬─────┘
                        │                 │                 │
                        ▼ (go r(ctx,b,u)) ▼                 ▼
                   ┌────────────────────────────────────────────┐
                   │ Per-handler goroutine                       │
                   │  → trackerMiddleware (WaitGroup +1)         │
                   │  → adminOnly                                │
                   │  → recover                                  │
                   │  → handler (sendText via HTTP/2 conn pool)  │
                   └────────────────────────────────────────────┘
                        │
                        ▼ (svc.Info/Warning/Error)
                   ┌────────────────┐
                   │ async log chan │ ──► dedicated writer ──► panelLogger
                   │ (cap 1024)     │
                   └────────────────┘
Enter fullscreen mode Exit fullscreen mode

A single goroutine long-polls getUpdates and pushes into a buffered channel (capacity 1024). A worker pool sized at NumCPU*2 (floor of 8) drains it. Each update spawns a per-handler goroutine wrapped in a middleware chain: a trackerMiddleware that increments a WaitGroup for clean shutdown, an adminOnly gate, a recover so one panicking handler never takes down the pool, and finally the handler itself sending replies through the shared HTTP/2 connection pool. Logging is fire-and-forget into another 1024-cap channel drained by a dedicated writer—so logging never blocks a handler. This is textbook Go: bounded channels for backpressure, a fixed worker pool to cap concurrency, recover per goroutine for fault isolation, and a WaitGroup for graceful drain.

The command surface is registered automatically via SetMyCommands, so everything shows up in Telegram's / menu. Highlights:

Command Description
/start Start the bot; first-time users pick a language (🇬🇧 / 🇮🇷).
/menu, /help, /cancel Navigation and flow control.
/users, /adduser, /adduser_interactive Paginated user list; quick or step-by-step user creation.
/server View/edit server config field by field.
/blockedips, /blockedaccess Manage blocked IPs and browse blocked-access logs.
/logs Browse access logs (last 20 per user).
/traffic, /sessions Per-user traffic; list and revoke sessions.
/restart_server, /restart_panel Restart either service, with confirmation.

Build and deploy

From source you need Go 1.21+:

git clone https://github.com/ebrasha/abdal-4iproto-panel.git
cd abdal-4iproto-panel
go build -o abdal-4iproto-panel main.go      # Linux
go build -o abdal-4iproto-panel.exe main.go  # Windows
Enter fullscreen mode Exit fullscreen mode

As a service, the binary self-registers. On Windows:

abdal-4iproto-panel.exe install
abdal-4iproto-panel.exe start
Enter fullscreen mode Exit fullscreen mode

On Linux with systemd:

sudo ./install-abdal-4iproto-panel.sh
sudo systemctl enable abdal-4iproto-panel
sudo systemctl status abdal-4iproto-panel
Enter fullscreen mode Exit fullscreen mode

For a hands-off setup, the Abdal 4iProto Cli detects your OS/architecture, verifies SHA-256 checksums, configures ports, generates SSH keys, and registers persistent services.

FAQ

Does the panel need a database? No. State is stored in flat JSON files, which keeps deployment to a single binary and makes backups a file copy.

Can I cap individual users? Yes—per-user speed limits (KB/s), traffic quotas (MB), session limits, and TTL are all configurable.

Is the Telegram bot safe to expose? It only accepts updates from explicitly listed admin IDs and rejects everything else. Treat the token like a secret and restrict admins tightly.

Windows and Linux both? Yes—single binary per platform, installable as a Windows Service or a Linux systemd unit.

Closing thought

The design philosophy behind Abdal 4iProto Panel is that operational tooling should reduce moving parts, not add them. One binary, embedded assets, JSON state, and a concurrency model that isolates the bot from the UI—each decision trades novelty for fewer 2 a.m. surprises.

Handcrafted with passion by Ebrahim Shafiei (EbraSha) — 📧 Prof.Shafiei@Gmail.com · ✈️ @ProfShafiei

Source: dev.to

arrow_back Back to Tutorials