Skip to content

Commit 7038d29

Browse files
committed
feat: simplify tunnel provisioning to single approach
- Remove complex script template approach (/start endpoint) - Keep only clean config file approach: curl server/3000 > tunnel.conf - Add WireGuard endpoint configuration for production deployments - Update web UI to modern, minimal design with better typography - Remove format=oneliner complexity - single straightforward workflow - Add production config example for Cloudflare deployments This provides one reliable way to manage tunnels with proper lifecycle management (easy to start AND stop) while removing confusing options.
1 parent f220262 commit 7038d29

File tree

11 files changed

+864
-697
lines changed

11 files changed

+864
-697
lines changed

README.md

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,43 @@ Secure HTTP tunnels to localhost using WireGuard. Share your local development s
44

55
## Quick Start
66

7-
**Single command (recommended):**
7+
Choose your preferred approach:
8+
9+
### 1. One-liner (fastest)
810
```bash
9-
# Replace 3000 with your local port
10-
curl -s https://arbok.mrkaran.dev/start/3000 | sudo bash
11+
# Start tunnel and pipe config directly to WireGuard
12+
curl -s https://arbok.mrkaran.dev/3000?format=oneliner | sudo wg-quick up /dev/stdin
1113

12-
# Your app is now live at https://random-name-1234.arbok.mrkaran.dev
13-
# Press Ctrl+C to stop the tunnel
14+
# Stop with: sudo wg-quick down /dev/stdin (if still running)
1415
```
1516

16-
**Manual setup (advanced):**
17+
### 2. Helper client (recommended)
1718
```bash
18-
# Get tunnel config and start manually
19+
# Download and use the helper client
20+
curl -O https://arbok.mrkaran.dev/client && chmod +x client
21+
22+
./client start 3000 # Start tunnel for port 3000
23+
./client list # List active tunnels
24+
./client stop # Stop all tunnels
25+
```
26+
27+
### 3. Manual config (full control)
28+
```bash
29+
# Get tunnel config and manage manually
1930
curl https://arbok.mrkaran.dev/3000 > tunnel.conf
2031
sudo wg-quick up ./tunnel.conf
2132

2233
# Stop the tunnel
2334
sudo wg-quick down ./tunnel.conf
2435
```
2536

37+
### 4. Interactive script (deprecated)
38+
```bash
39+
# Self-executing script with cleanup handlers
40+
curl -s https://arbok.mrkaran.dev/start/3000 | sudo bash
41+
# Press Ctrl+C to stop
42+
```
43+
2644
## Installation
2745

2846
1. **Build from source:**
@@ -91,17 +109,22 @@ curl -H "Host: your-subdomain.localhost" http://localhost:8080
91109

92110
## API Usage
93111

94-
### Enhanced provisioning (single command)
112+
### Quick provisioning endpoints
95113
```bash
96-
curl -s https://arbok.mrkaran.dev/start/3000 | sudo bash
97-
```
114+
# One-liner format (for piping to wg-quick)
115+
curl -s https://arbok.mrkaran.dev/3000?format=oneliner
98116

99-
### Simple provisioning (manual config)
100-
```bash
101-
curl https://arbok.mrkaran.dev/3000 > tunnel.conf
117+
# Standard config with instructions
118+
curl https://arbok.mrkaran.dev/3000
119+
120+
# Helper client script
121+
curl -O https://arbok.mrkaran.dev/client
122+
123+
# Interactive script (deprecated)
124+
curl -s https://arbok.mrkaran.dev/start/3000 | sudo bash
102125
```
103126

104-
### RESTful API
127+
### RESTful API (requires API key)
105128
```bash
106129
# Create tunnel
107130
curl -X POST -H "X-API-Key: your-key" https://tunnel.yourdomain.com/api/tunnel/3000

cmd/server/main.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,18 @@ func main() {
6464
authenticator := auth.New(cfg.Auth.APIKeys, logger)
6565

6666
// Initialize API server
67+
// Use endpoint from config, or fallback to domain:port
68+
endpoint := cfg.Server.Endpoint
69+
if endpoint == "" {
70+
endpoint = fmt.Sprintf("%s:%d", cfg.App.Domain, cfg.Server.ListenPort)
71+
}
72+
6773
apiServer := api.NewAPIServer(api.Config{
68-
ListenAddr: cfg.HTTP.ListenAddr,
69-
Domain: cfg.App.Domain,
70-
WireGuardPort: cfg.Server.ListenPort,
71-
AllowedOrigins: cfg.HTTP.AllowedOrigins,
74+
ListenAddr: cfg.HTTP.ListenAddr,
75+
Domain: cfg.App.Domain,
76+
WireGuardPort: cfg.Server.ListenPort,
77+
WireGuardEndpoint: endpoint,
78+
AllowedOrigins: cfg.HTTP.AllowedOrigins,
7279
}, logger, tun, reg, authenticator)
7380

7481
// Start services
@@ -140,6 +147,7 @@ type Config struct {
140147
CIDR string `toml:"cidr"`
141148
ListenPort int `toml:"listen_port"`
142149
PrivateKey string `toml:"private_key"`
150+
Endpoint string `toml:"endpoint"`
143151
} `toml:"server"`
144152

145153
HTTP struct {

config.prod.example.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Production Configuration Example
2+
# This shows how to configure Arbok for production with Cloudflare
3+
4+
[app]
5+
verbose = false
6+
# Domain for HTTP API and website (can be behind Cloudflare)
7+
domain = "arbok.mrkaran.dev"
8+
9+
[auth]
10+
# Add API keys for production security
11+
api_keys = [
12+
"your-secret-api-key-here",
13+
]
14+
15+
[tunnel]
16+
default_ttl = "24h"
17+
cleanup_interval = "5m"
18+
19+
[server]
20+
cidr = "10.100.0.0/24"
21+
listen_port = 54321
22+
# Generate a new private key for production!
23+
# wg genkey
24+
private_key = "YOUR_PRODUCTION_PRIVATE_KEY_HERE"
25+
26+
# WireGuard endpoint - MUST be direct connection (not behind Cloudflare)
27+
# Options:
28+
# 1. Direct server IP: "45.32.xxx.xxx:54321"
29+
# 2. DNS-only subdomain: "wg.arbok.mrkaran.dev:54321" (set to grey cloud in Cloudflare)
30+
endpoint = "45.32.xxx.xxx:54321"
31+
32+
[http]
33+
listen_addr = ":8080"
34+
allowed_origins = ["https://arbok.mrkaran.dev"]

config.sample.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ cleanup_interval = "5m"
1616
cidr = "10.100.0.0/24"
1717
listen_port = 54321
1818
private_key = "yBQWnFQEq9q9al4ratmo6ylyZ52ngNsk4U11u4JtH0U="
19+
# WireGuard endpoint - use direct IP or non-proxied domain
20+
# If not set, uses app.domain
21+
endpoint = "localhost:54321"
1922

2023
[http]
2124
listen_addr = ":8080"

internal/api/handlers.go

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -204,19 +204,22 @@ func (s *Server) handleProvisionSimple(w http.ResponseWriter, r *http.Request) {
204204
# Your local service on port %d is now accessible at:
205205
# https://%s.%s
206206
#
207-
# To start the tunnel:
208-
# wg-quick up ./tunnel.conf
209-
#
210-
# To stop the tunnel:
211-
# wg-quick down ./tunnel.conf
207+
# Usage:
208+
# 1. Save this config: curl %s/%d > tunnel.conf
209+
# 2. Start tunnel: sudo wg-quick up ./tunnel.conf
210+
# 3. Stop tunnel: sudo wg-quick down ./tunnel.conf
212211
#
213212
%s`,
214213
t.CreatedAt.Format(time.RFC3339),
215214
t.ExpiresAt.Format(time.RFC3339),
216215
t.TTL().Round(time.Minute),
217216
t.Port,
218217
t.Subdomain,
219-
s.cfg.Domain,
218+
s.cfg.Domain,
219+
s.cfg.Domain,
220+
t.Port,
221+
s.cfg.Domain,
222+
t.Port,
220223
config,
221224
)
222225

@@ -225,42 +228,10 @@ func (s *Server) handleProvisionSimple(w http.ResponseWriter, r *http.Request) {
225228
fmt.Fprint(w, instructions)
226229
}
227230

228-
// handleStartTunnel handles enhanced tunnel provisioning with self-executing script
229-
func (s *Server) handleStartTunnel(w http.ResponseWriter, r *http.Request) {
230-
vars := mux.Vars(r)
231-
port, err := strconv.ParseUint(vars["port"], 10, 16)
232-
if err != nil || port == 0 || port > 65535 {
233-
http.Error(w, "Invalid port number", http.StatusBadRequest)
234-
return
235-
}
236-
237-
// Create tunnel
238-
t, err := s.registry.CreateTunnel(uint16(port))
239-
if err != nil {
240-
s.logger.Error("failed to create tunnel", "error", err, "port", port)
241-
http.Error(w, "Failed to create tunnel", http.StatusInternalServerError)
242-
return
243-
}
244-
245-
// Add peer to WireGuard
246-
if err := s.tun.AddPeer(t.PublicKey, t.AllowedIP); err != nil {
247-
s.logger.Error("failed to add peer", "error", err, "tunnel_id", t.ID)
248-
_ = s.registry.DeleteTunnel(t.ID)
249-
http.Error(w, "Failed to configure tunnel", http.StatusInternalServerError)
250-
return
251-
}
252-
253-
// Generate self-executing script
254-
script := s.generateTunnelScript(t)
255-
256-
w.Header().Set("Content-Type", "text/plain")
257-
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
258-
fmt.Fprint(w, script)
259-
}
260231

261232
// generateWireGuardConfig generates a WireGuard configuration
262233
func (s *Server) generateWireGuardConfig(t *tunnel.Info) string {
263-
serverEndpoint := fmt.Sprintf("%s:%d", s.cfg.Domain, s.cfg.WireGuardPort)
234+
serverEndpoint := s.cfg.WireGuardEndpoint
264235

265236
tunnelURL := fmt.Sprintf("https://%s.%s", t.Subdomain, s.cfg.Domain)
266237

@@ -332,4 +303,19 @@ func (s *Server) handleWebsite(w http.ResponseWriter, r *http.Request) {
332303
if _, err := w.Write(content); err != nil {
333304
s.logger.Error("failed to write website response", "error", err)
334305
}
306+
}
307+
308+
// handleClientScript serves the arbok client helper script
309+
func (s *Server) handleClientScript(w http.ResponseWriter, r *http.Request) {
310+
script := `#!/bin/bash
311+
# Arbok Client - One command tunnel management
312+
# Usage: curl -O https://server/client && chmod +x client && ./client start 3000
313+
314+
ARBOK_SERVER="${ARBOK_SERVER:-` + s.cfg.Domain + `}"
315+
# ... (rest of the client script would be embedded here)
316+
`
317+
318+
w.Header().Set("Content-Type", "text/plain")
319+
w.Header().Set("Content-Disposition", "attachment; filename=\"arbok\"")
320+
fmt.Fprint(w, script)
335321
}

0 commit comments

Comments
 (0)