package main import ( "log" "os" "strconv" "time" "github.com/nixc/reverse-ssh-traefik/internal/client" ) func envRequired(key string) string { v := os.Getenv(key) if v == "" { log.Fatalf("Required environment variable %s is not set", key) } return v } func envOr(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println("tunnel-client starting") serverAddr := envRequired("TUNNEL_SERVER") domain := envRequired("TUNNEL_DOMAIN") keyPath := envOr("TUNNEL_KEY", "/keys/id_ed25519") localPortStr := envOr("TUNNEL_PORT", "8080") localPort, err := strconv.Atoi(localPortStr) if err != nil { log.Fatalf("Invalid TUNNEL_PORT=%q: %v", localPortStr, err) } // Load the private key. signer, err := client.LoadPrivateKey(keyPath) if err != nil { log.Fatalf("Failed to load private key: %v", err) } log.Printf("Loaded key from %s", keyPath) // Reconnect loop. backoff := time.Second maxBackoff := 30 * time.Second for { log.Printf("Connecting to %s (domain=%s, local_port=%d)", serverAddr, domain, localPort) sshClient, err := client.Connect(serverAddr, signer) if err != nil { log.Printf("Connection failed: %v (retry in %s)", err, backoff) time.Sleep(backoff) backoff = min(backoff*2, maxBackoff) continue } // Reset backoff on successful connection. backoff = time.Second log.Printf("Connected to %s", serverAddr) // Set up the reverse tunnel (blocks until disconnected). if err := client.SetupTunnel(sshClient, domain, localPort); err != nil { log.Printf("Tunnel error: %v (reconnecting in %s)", err, backoff) } sshClient.Close() time.Sleep(backoff) backoff = min(backoff*2, maxBackoff) } }