feat(client): Support predefined tunnel configuration and management commands

Added predefined tunnel functionality, allowing users to define multiple tunnels in the configuration file and start them by name, including the following improvements:
- Added --all flag to start all configured tunnels
- Added parameterless start command to list available tunnels
- Support configuration of multiple tunnel types (http, https, tcp)
- Support advanced configurations such as subdomains, transport protocols, and IP allowlists

refactor(deployments): Refactor Docker deployment configuration

Removed old Dockerfile and Compose configurations, added new deployment files:
- Removed .env.example and old Docker build files
- Added Caddy reverse proxy configuration file
- Added two deployment modes: standard and Caddy reverse proxy
- Added detailed server configuration example files

docs: Update documentation to include tunnel configuration and deployment guide

Updated Chinese and English README documents:
- Added usage instructions and configuration examples for predefined tunnels
- Expanded server deployment section to include direct TLS and reverse proxy modes
- Added server configuration reference table with detailed configuration item descriptions
- Added specific configuration methods for Caddy and Nginx reverse proxies
This commit is contained in:
Gouryella
2026-01-15 17:18:27 +08:00
parent ba4c1761dc
commit 37d1bfc089
27 changed files with 981 additions and 1308 deletions

View File

@@ -10,11 +10,49 @@ import (
"gopkg.in/yaml.v3"
)
// TunnelConfig holds configuration for a predefined tunnel
type TunnelConfig struct {
Name string `yaml:"name"` // Tunnel name (required, unique identifier)
Type string `yaml:"type"` // Tunnel type: http, https, tcp (required)
Port int `yaml:"port"` // Local port to forward (required)
Address string `yaml:"address,omitempty"` // Local address (default: 127.0.0.1)
Subdomain string `yaml:"subdomain,omitempty"` // Custom subdomain
Transport string `yaml:"transport,omitempty"` // Transport: auto, tcp, wss
AllowIPs []string `yaml:"allow_ips,omitempty"` // Allowed IPs/CIDRs
DenyIPs []string `yaml:"deny_ips,omitempty"` // Denied IPs/CIDRs
Auth string `yaml:"auth,omitempty"` // Proxy authentication password (http/https only)
}
// Validate checks if the tunnel configuration is valid
func (t *TunnelConfig) Validate() error {
if t.Name == "" {
return fmt.Errorf("tunnel name is required")
}
if t.Type == "" {
return fmt.Errorf("tunnel type is required for '%s'", t.Name)
}
t.Type = strings.ToLower(t.Type)
if t.Type != "http" && t.Type != "https" && t.Type != "tcp" {
return fmt.Errorf("invalid tunnel type '%s' for '%s': must be http, https, or tcp", t.Type, t.Name)
}
if t.Port < 1 || t.Port > 65535 {
return fmt.Errorf("invalid port %d for '%s': must be between 1 and 65535", t.Port, t.Name)
}
if t.Transport != "" {
t.Transport = strings.ToLower(t.Transport)
if t.Transport != "auto" && t.Transport != "tcp" && t.Transport != "wss" {
return fmt.Errorf("invalid transport '%s' for '%s': must be auto, tcp, or wss", t.Transport, t.Name)
}
}
return nil
}
// ClientConfig represents the client configuration
type ClientConfig struct {
Server string `yaml:"server"` // Server address (e.g., tunnel.example.com:443)
Token string `yaml:"token"` // Authentication token
TLS bool `yaml:"tls"` // Use TLS (always true for production)
Server string `yaml:"server"` // Server address (e.g., tunnel.example.com:443)
Token string `yaml:"token"` // Authentication token
TLS bool `yaml:"tls"` // Use TLS (always true for production)
Tunnels []*TunnelConfig `yaml:"tunnels,omitempty"` // Predefined tunnels
}
// Validate checks if the client configuration is valid
@@ -39,9 +77,40 @@ func (c *ClientConfig) Validate() error {
return fmt.Errorf("server port is required")
}
// Validate tunnels and check for duplicate names
names := make(map[string]bool)
for _, t := range c.Tunnels {
if err := t.Validate(); err != nil {
return err
}
if names[t.Name] {
return fmt.Errorf("duplicate tunnel name: %s", t.Name)
}
names[t.Name] = true
}
return nil
}
// GetTunnel returns a tunnel by name
func (c *ClientConfig) GetTunnel(name string) *TunnelConfig {
for _, t := range c.Tunnels {
if t.Name == name {
return t
}
}
return nil
}
// GetTunnelNames returns all tunnel names
func (c *ClientConfig) GetTunnelNames() []string {
names := make([]string, len(c.Tunnels))
for i, t := range c.Tunnels {
names[i] = t.Name
}
return names
}
// DefaultClientConfig returns the default configuration path
func DefaultClientConfigPath() string {
home, err := os.UserHomeDir()

View File

@@ -4,36 +4,43 @@ import (
"crypto/tls"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// ServerConfig holds the server configuration
type ServerConfig struct {
Port int
PublicPort int // Port to display in URLs (for reverse proxy scenarios)
Domain string // Domain for client connections (e.g., connect.example.com)
TunnelDomain string // Domain for tunnel URLs (e.g., example.com for *.example.com)
Port int `yaml:"port"`
PublicPort int `yaml:"public_port"` // Port to display in URLs (for reverse proxy scenarios)
Domain string `yaml:"domain"` // Domain for client connections (e.g., connect.example.com)
TunnelDomain string `yaml:"tunnel_domain"` // Domain for tunnel URLs (e.g., example.com for *.example.com)
// TCP tunnel dynamic port allocation
TCPPortMin int
TCPPortMax int
TCPPortMin int `yaml:"tcp_port_min"`
TCPPortMax int `yaml:"tcp_port_max"`
// TLS settings
TLSEnabled bool
TLSCertFile string
TLSKeyFile string
TLSEnabled bool `yaml:"tls_enabled"`
TLSCertFile string `yaml:"tls_cert"`
TLSKeyFile string `yaml:"tls_key"`
// Security
AuthToken string
AuthToken string `yaml:"token"`
MetricsToken string `yaml:"metrics_token"`
// Logging
Debug bool
Debug bool `yaml:"debug"`
// Performance
PprofPort int `yaml:"pprof_port"`
// Allowed transports: "tcp", "wss", or "tcp,wss" (default: "tcp,wss")
AllowedTransports []string
AllowedTransports []string `yaml:"transports"`
// Allowed tunnel types: "http", "https", "tcp" (default: all)
AllowedTunnelTypes []string
AllowedTunnelTypes []string `yaml:"tunnel_types"`
}
// Validate checks if the server configuration is valid
@@ -156,3 +163,73 @@ func GetClientTLSConfigInsecure() *tls.Config {
},
}
}
// DefaultServerConfigPath returns the default server configuration path
func DefaultServerConfigPath() string {
// Check /etc/drip/config.yaml first (system-wide)
systemPath := "/etc/drip/config.yaml"
if _, err := os.Stat(systemPath); err == nil {
return systemPath
}
// Fall back to user home directory
home, err := os.UserHomeDir()
if err != nil {
return ".drip/server.yaml"
}
return filepath.Join(home, ".drip", "server.yaml")
}
// LoadServerConfig loads server configuration from file
func LoadServerConfig(path string) (*ServerConfig, error) {
if path == "" {
path = DefaultServerConfigPath()
}
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("config file not found at %s", path)
}
return nil, fmt.Errorf("failed to read config file: %w", err)
}
var config ServerConfig
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
return &config, nil
}
// SaveServerConfig saves server configuration to file
func SaveServerConfig(config *ServerConfig, path string) error {
if path == "" {
path = DefaultServerConfigPath()
}
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
data, err := yaml.Marshal(config)
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(path, data, 0600); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
// ServerConfigExists checks if server config file exists
func ServerConfigExists(path string) bool {
if path == "" {
path = DefaultServerConfigPath()
}
_, err := os.Stat(path)
return err == nil
}