mirror of
https://github.com/Gouryella/drip.git
synced 2026-03-02 16:20:57 +00:00
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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user