diff --git a/.gitignore b/.gitignore index 511867f..f38de56 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,3 @@ Thumbs.db # If your default is ~/.config/ts-ssh-client, this isn't strictly needed here. # If you use the fallback ts-ssh-client-state, add it: ts-ssh-client-state/ -old_complex/ diff --git a/_old_complex/cli.go b/_old_complex/cli.go deleted file mode 100644 index 4294c29..0000000 --- a/_old_complex/cli.go +++ /dev/null @@ -1,803 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "log" - "os" - "os/user" - "path/filepath" - "runtime" - "strings" - - sshclient "github.com/derekg/ts-ssh/internal/client/ssh" - "github.com/derekg/ts-ssh/internal/crypto/pqc" -) - -// Global variables -var ( - logger *log.Logger = log.New(os.Stdout, "", log.LstdFlags) -) - -// scpArgs holds parsed arguments for an SCP operation. -type scpArgs struct { - isUpload bool - localPath string - remotePath string - targetHost string - sshUser string -} - -// Config holds all the configuration for the application -type Config struct { - // Common options - SSHUser string `fang:"user,u" default:"" help:"SSH username for connection"` - SSHKeyPath string `fang:"identity,i" default:"" help:"Path to SSH private key file"` - SSHConfigFile string `fang:"config,F" default:"" help:"SSH config file path"` - TsnetDir string `fang:"tsnet-dir" default:"" help:"Directory for tsnet state and logs"` - TsControlURL string `fang:"control-url" default:"" help:"Tailscale control server URL"` - Verbose bool `fang:"verbose,v" help:"Enable verbose logging"` - InsecureHostKey bool `fang:"insecure" help:"Skip host key verification (insecure)"` - ForceInsecure bool `fang:"force-insecure" help:"Force insecure mode without confirmation"` - Language string `fang:"lang" help:"Set language for output (en, es, fr, de, etc.)"` - - // Post-quantum cryptography options - EnablePQC bool `fang:"pqc" default:"true" help:"Enable post-quantum cryptography"` - PQCLevel int `fang:"pqc-level" default:"1" help:"PQC level: 0=none, 1=hybrid, 2=strict"` - - // Global flags for all commands - Help bool `fang:"help,h" help:"Show help"` - Version bool `fang:"version" help:"Show version information"` -} - -// ConnectCommand handles SSH connections -type ConnectCommand struct { - *Config - Target string `fang:"" help:"Target host in format [user@]host[:port]"` - ForwardDest string `fang:"forward,W" help:"Forward stdin/stdout to specified destination"` - Command []string `fang:"" help:"Remote command to execute"` -} - -// SCPCommand handles SCP file transfers -type SCPCommand struct { - *Config - Source string `fang:"" help:"Source file/directory path"` - Destination string `fang:"" help:"Destination file/directory path"` - Recursive bool `fang:"recursive,r" help:"Recursively copy directories"` - Preserve bool `fang:"preserve,p" help:"Preserve file attributes"` -} - -// ListCommand lists available hosts -type ListCommand struct { - *Config - Interactive bool `fang:"interactive,i" help:"Interactive host picker"` -} - -// ExecCommand executes commands on multiple hosts -type ExecCommand struct { - *Config - Command string `fang:"command,c" help:"Command to execute on hosts"` - Hosts []string `fang:"" help:"Target hosts"` - Parallel bool `fang:"parallel,p" help:"Execute commands in parallel"` -} - -// MultiCommand handles multi-host operations -type MultiCommand struct { - *Config - Hosts string `fang:"hosts" help:"Comma-separated list of hosts"` - Sessions bool `fang:"sessions,s" help:"Create multiple SSH sessions"` - Tmux bool `fang:"tmux,t" help:"Use tmux for session management"` -} - -// ConfigCommand handles configuration operations -type ConfigCommand struct { - *Config - Show bool `fang:"show" help:"Show current configuration"` - Set string `fang:"set" help:"Set configuration value (key=value)"` - Unset string `fang:"unset" help:"Unset configuration value"` - Reset bool `fang:"reset" help:"Reset configuration to defaults"` - Global bool `fang:"global,g" help:"Apply to global configuration"` -} - -// PQCCommand handles post-quantum cryptography operations -type PQCCommand struct { - *Config - Report bool `fang:"report,r" help:"Generate PQC usage report"` - Test bool `fang:"test,t" help:"Test PQC functionality"` - Benchmark bool `fang:"benchmark,b" help:"Run PQC performance benchmarks"` - ShowSupported bool `fang:"supported,s" help:"Show supported PQC algorithms"` -} - -// VersionCommand shows version information -type VersionCommand struct { - *Config - Short bool `fang:"short,s" help:"Show short version only"` - Commit bool `fang:"commit,c" help:"Include commit information"` -} - -// Run executes the connect command (default SSH behavior) -func (c *ConnectCommand) Run(ctx context.Context) error { - if c.Help { - fmt.Println("Usage: ts-ssh connect [options] [user@]hostname[:port] [command...]") - fmt.Println("\nOptions:") - fmt.Println(" -u, --user SSH username") - fmt.Println(" -i, --identity SSH private key file") - fmt.Println(" -v, --verbose Enable verbose logging") - fmt.Println(" --insecure Skip host key verification") - fmt.Println(" --force-insecure Force insecure mode without confirmation") - return nil - } - - if c.Version { - return showVersion(c.Config, false, false) - } - - // Initialize i18n with language preference - initI18n(c.Language) - - // Apply defaults - if err := c.applyDefaults(); err != nil { - return fmt.Errorf("%s: %w", T("failed_to_apply_defaults"), err) - } - - // Validate insecure mode - if err := validateInsecureMode(c.InsecureHostKey, c.ForceInsecure, "", ""); err != nil { - return err - } - - // Handle proxy command mode - if c.ForwardDest != "" { - return c.handleProxyCommand(ctx) - } - - // Check if target is provided - if c.Target == "" { - return fmt.Errorf("%s. Usage: ts-ssh connect [user@]hostname[:port]", T("target_hostname_required")) - } - - // Parse target - targetHost, _, err := parseTarget(c.Target, DefaultSshPort) - if err != nil { - return fmt.Errorf("%s: %w", T("error_parsing_target"), err) - } - - // Extract user from target if provided - sshUser := c.SSHUser - if strings.Contains(targetHost, "@") { - parts := strings.SplitN(targetHost, "@", 2) - sshUser = parts[0] - targetHost = parts[1] - } - - // Create app config for compatibility with existing code - appConfig := &AppConfig{ - SSHUser: sshUser, - SSHKeyPath: c.SSHKeyPath, - TsnetDir: c.TsnetDir, - TsControlURL: c.TsControlURL, - Target: c.Target, // This is the full target including any user@ prefix - Verbose: c.Verbose, - InsecureHostKey: c.InsecureHostKey, - ForwardDest: c.ForwardDest, - EnablePQC: c.EnablePQC, - PQCLevel: c.PQCLevel, - RemoteCmd: c.Command, - } - - // Set up logger - appConfig.Logger = getLogger(c.Verbose) - - return handleSSHOperation(appConfig) -} - -// Run executes the SCP command -func (c *SCPCommand) Run(ctx context.Context) error { - if c.Version { - return showVersion(c.Config, false, false) - } - - initI18n(c.Language) - - if err := c.applyDefaults(); err != nil { - return fmt.Errorf("%s: %w", T("failed_to_apply_defaults"), err) - } - - // Parse SCP arguments - scpArgs, err := c.parseScpArgs() - if err != nil { - return fmt.Errorf("failed to parse SCP arguments: %w", err) - } - - // Validate insecure mode - if err := validateInsecureMode(c.InsecureHostKey, c.ForceInsecure, scpArgs.targetHost, scpArgs.sshUser); err != nil { - return err - } - - // Create app config for compatibility - appConfig := &AppConfig{ - SSHUser: c.SSHUser, - SSHKeyPath: c.SSHKeyPath, - TsnetDir: c.TsnetDir, - TsControlURL: c.TsControlURL, - Verbose: c.Verbose, - InsecureHostKey: c.InsecureHostKey, - EnablePQC: c.EnablePQC, - PQCLevel: c.PQCLevel, - } - - return handleSCPOperation(scpArgs, appConfig) -} - -// Run executes the list command -func (c *ListCommand) Run(ctx context.Context) error { - if c.Version { - return showVersion(c.Config, false, false) - } - - initI18n(c.Language) - - if err := c.applyDefaults(); err != nil { - return fmt.Errorf("%s: %w", T("failed_to_apply_defaults"), err) - } - - // Create app config for compatibility - appConfig := &AppConfig{ - TsnetDir: c.TsnetDir, - TsControlURL: c.TsControlURL, - Verbose: c.Verbose, - SSHUser: c.SSHUser, - SSHKeyPath: c.SSHKeyPath, - InsecureHostKey: c.InsecureHostKey, - ListHosts: !c.Interactive, - PickHost: c.Interactive, - } - - // Set up logger - appConfig.Logger = getLogger(c.Verbose) - - return handlePowerCLI(appConfig) -} - -// Run executes the exec command -func (c *ExecCommand) Run(ctx context.Context) error { - if c.Version { - return showVersion(c.Config, false, false) - } - - initI18n(c.Language) - - if err := c.applyDefaults(); err != nil { - return fmt.Errorf("%s: %w", T("failed_to_apply_defaults"), err) - } - - // Create app config for compatibility - appConfig := &AppConfig{ - TsnetDir: c.TsnetDir, - TsControlURL: c.TsControlURL, - Verbose: c.Verbose, - SSHUser: c.SSHUser, - SSHKeyPath: c.SSHKeyPath, - InsecureHostKey: c.InsecureHostKey, - ExecCmd: c.Command, - Parallel: c.Parallel, - } - - // Set up logger - appConfig.Logger = getLogger(c.Verbose) - - return handlePowerCLI(appConfig) -} - -// Run executes the multi command -func (c *MultiCommand) Run(ctx context.Context) error { - if c.Version { - return showVersion(c.Config, false, false) - } - - initI18n(c.Language) - - if err := c.applyDefaults(); err != nil { - return fmt.Errorf("%s: %w", T("failed_to_apply_defaults"), err) - } - - // Create app config for compatibility - appConfig := &AppConfig{ - TsnetDir: c.TsnetDir, - TsControlURL: c.TsControlURL, - Verbose: c.Verbose, - SSHUser: c.SSHUser, - SSHKeyPath: c.SSHKeyPath, - InsecureHostKey: c.InsecureHostKey, - MultiHosts: c.Hosts, - } - - // Set up logger - appConfig.Logger = getLogger(c.Verbose) - - return handlePowerCLI(appConfig) -} - -// Run executes the config command -func (c *ConfigCommand) Run(ctx context.Context) error { - if c.Version { - return showVersion(c.Config, false, false) - } - - initI18n(c.Language) - - if c.Show { - return c.showConfiguration() - } - - if c.Set != "" { - return c.setConfiguration(c.Set, c.Global) - } - - if c.Unset != "" { - return c.unsetConfiguration(c.Unset, c.Global) - } - - if c.Reset { - return c.resetConfiguration(c.Global) - } - - return c.showConfiguration() -} - -// Run executes the PQC command -func (c *PQCCommand) Run(ctx context.Context) error { - if c.Version { - return showVersion(c.Config, false, false) - } - - initI18n(c.Language) - - if err := c.applyDefaults(); err != nil { - return fmt.Errorf("%s: %w", T("failed_to_apply_defaults"), err) - } - - logger := getLogger(c.Verbose) - - if c.Report { - report := pqc.GenerateGlobalReport(logger) - fmt.Println(report) - ready, assessment := pqc.CheckGlobalQuantumReadiness(logger) - fmt.Printf("\nQuantum Readiness: %v - %s\n", ready, assessment) - recommendations := pqc.GetGlobalRecommendations(logger) - if len(recommendations) > 0 { - fmt.Println("\nRecommendations:") - for _, rec := range recommendations { - fmt.Printf(" - %s\n", rec) - } - } - return nil - } - - if c.ShowSupported { - return c.showSupportedAlgorithms() - } - - if c.Test { - return c.testPQCFunctionality(logger) - } - - if c.Benchmark { - return c.runPQCBenchmarks(logger) - } - - return c.showPQCStatus(logger) -} - -// Run executes the version command -func (c *VersionCommand) Run(ctx context.Context) error { - return showVersion(c.Config, c.Short, c.Commit) -} - -// Helper methods - -func (c *Config) applyDefaults() error { - currentUser, err := user.Current() - if err != nil { - return fmt.Errorf("could not determine current user: %w", err) - } - - if c.SSHUser == "" { - c.SSHUser = currentUser.Username - } - - if c.SSHKeyPath == "" { - c.SSHKeyPath = sshclient.GetDefaultSSHKeyPath(currentUser, getLogger(c.Verbose)) - } - - if c.TsnetDir == "" { - c.TsnetDir = filepath.Join(currentUser.HomeDir, ".config", ClientName) - } - - return nil -} - -func (c *ConnectCommand) handleProxyCommand(ctx context.Context) error { - srv, nonTuiCtx, _, err := initTsNet(c.TsnetDir, ClientName, getLogger(c.Verbose), c.TsControlURL, c.Verbose) - if err != nil { - return fmt.Errorf("%s", T("error_init_tailscale")) - } - defer srv.Close() - - return handleProxyCommand(srv, nonTuiCtx, c.ForwardDest, getLogger(c.Verbose)) -} - -func (c *SCPCommand) parseScpArgs() (*scpArgs, error) { - // Determine upload vs download based on which argument contains ":" - sourceHasColon := strings.Contains(c.Source, ":") - destHasColon := strings.Contains(c.Destination, ":") - - if sourceHasColon && destHasColon { - return nil, fmt.Errorf("both source and destination cannot be remote") - } - - if !sourceHasColon && !destHasColon { - return nil, fmt.Errorf("either source or destination must be remote") - } - - args := &scpArgs{} - - if sourceHasColon { - // Download: remote -> local - args.isUpload = false - args.localPath = c.Destination - - host, path, user, err := parseScpRemoteArg(c.Source, c.SSHUser) - if err != nil { - return nil, err - } - args.remotePath = path - args.targetHost = host - args.sshUser = user - } else { - // Upload: local -> remote - args.isUpload = true - args.localPath = c.Source - - host, path, user, err := parseScpRemoteArg(c.Destination, c.SSHUser) - if err != nil { - return nil, err - } - args.remotePath = path - args.targetHost = host - args.sshUser = user - } - - return args, nil -} - -func getLogger(verbose bool) *log.Logger { - if verbose { - return log.Default() - } - return log.New(io.Discard, "", 0) -} - -func showVersion(config *Config, short, commit bool) error { - if short { - fmt.Println(version) - return nil - } - - fmt.Printf("%s %s\n", ClientName, version) - if commit { - fmt.Printf("Build: %s\n", version) // In a real implementation, this would show commit hash - } - - fmt.Printf("Go version: %s\n", runtime.Version()) - fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) - - if config.EnablePQC { - fmt.Printf("PQC: Enabled (Level %d)\n", config.PQCLevel) - } else { - fmt.Println("PQC: Disabled") - } - - return nil -} - -// SimpleCLI provides a simple CLI implementation -type SimpleCLI struct { - commands map[string]func(context.Context, []string) error -} - -func (c *SimpleCLI) Run(ctx context.Context, args []string) error { - if len(args) == 0 { - return c.commands["help"](ctx, args) - } - - cmd := args[0] - if fn, ok := c.commands[cmd]; ok { - return fn(ctx, args[1:]) - } - - // Default to connect command for backwards compatibility - return c.commands["connect"](ctx, args) -} - -// Create the main CLI application -func NewCLI() *SimpleCLI { - // Initialize i18n early with default language - initI18n("") - - cli := &SimpleCLI{ - commands: make(map[string]func(context.Context, []string) error), - } - - // Add commands - cli.commands["connect"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &ConnectCommand{Config: parsed.Config} - if len(parsed.Positional) > 0 { - cmd.Target = parsed.Positional[0] - if len(parsed.Positional) > 1 { - cmd.Command = parsed.Positional[1:] - } - } - return cmd.Run(ctx) - } - cli.commands["scp"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &SCPCommand{Config: parsed.Config} - if len(parsed.Positional) >= 2 { - cmd.Source = parsed.Positional[0] - cmd.Destination = parsed.Positional[1] - } - return cmd.Run(ctx) - } - cli.commands["list"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &ListCommand{Config: parsed.Config} - return cmd.Run(ctx) - } - cli.commands["exec"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &ExecCommand{Config: parsed.Config} - if len(parsed.Positional) > 0 { - cmd.Hosts = parsed.Positional - } - return cmd.Run(ctx) - } - cli.commands["multi"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &MultiCommand{Config: parsed.Config} - return cmd.Run(ctx) - } - cli.commands["config"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &ConfigCommand{Config: parsed.Config} - return cmd.Run(ctx) - } - cli.commands["pqc"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &PQCCommand{Config: parsed.Config} - return cmd.Run(ctx) - } - cli.commands["version"] = func(ctx context.Context, args []string) error { - parsed := parseArgs(args) - cmd := &VersionCommand{Config: parsed.Config} - return cmd.Run(ctx) - } - cli.commands["help"] = func(ctx context.Context, args []string) error { - fmt.Printf("%s %s\n\n", ClientName, version) - fmt.Println(T("cli_description")) - fmt.Println("\nCommands:") - fmt.Println(" connect " + T("cmd_connect_desc")) - fmt.Println(" scp " + T("cmd_scp_desc")) - fmt.Println(" list " + T("cmd_list_desc")) - fmt.Println(" exec " + T("cmd_exec_desc")) - fmt.Println(" multi " + T("cmd_multi_desc")) - fmt.Println(" config " + T("cmd_config_desc")) - fmt.Println(" pqc " + T("cmd_pqc_desc")) - fmt.Println(" version " + T("cmd_version_desc")) - return nil - } - cli.commands["-h"] = cli.commands["help"] - cli.commands["--help"] = cli.commands["help"] - - return cli -} - -// CommandArgs holds parsed command arguments -type CommandArgs struct { - Config *Config - Positional []string -} - -// parseArgs parses command line arguments into a Config struct and positional args -func parseArgs(args []string) *CommandArgs { - config := &Config{ - EnablePQC: true, - PQCLevel: 1, - } - - var positional []string - - // Simple flag parsing - for i := 0; i < len(args); i++ { - arg := args[i] - switch arg { - case "-u", "--user": - if i+1 < len(args) { - config.SSHUser = args[i+1] - i++ - } - case "-i", "--identity": - if i+1 < len(args) { - config.SSHKeyPath = args[i+1] - i++ - } - case "-F", "--config": - if i+1 < len(args) { - config.SSHConfigFile = args[i+1] - i++ - } - case "--tsnet-dir": - if i+1 < len(args) { - config.TsnetDir = args[i+1] - i++ - } - case "--control-url": - if i+1 < len(args) { - config.TsControlURL = args[i+1] - i++ - } - case "-v", "--verbose": - config.Verbose = true - case "--insecure": - config.InsecureHostKey = true - case "--force-insecure": - config.ForceInsecure = true - case "--lang": - if i+1 < len(args) { - config.Language = args[i+1] - i++ - } - case "--pqc": - config.EnablePQC = true - case "--no-pqc": - config.EnablePQC = false - case "--pqc-level": - if i+1 < len(args) { - // Parse int value - i++ - } - case "-h", "--help": - config.Help = true - case "--version": - config.Version = true - default: - // If it doesn't start with -, it's a positional argument - if !strings.HasPrefix(arg, "-") { - positional = append(positional, arg) - } - } - } - - return &CommandArgs{ - Config: config, - Positional: positional, - } -} - -// Configuration management methods for ConfigCommand - -func (c *ConfigCommand) showConfiguration() error { - fmt.Println("Current Configuration:") - fmt.Printf(" SSH User: %s\n", c.SSHUser) - fmt.Printf(" SSH Key Path: %s\n", c.SSHKeyPath) - fmt.Printf(" SSH Config File: %s\n", c.SSHConfigFile) - fmt.Printf(" Tsnet Directory: %s\n", c.TsnetDir) - fmt.Printf(" Control URL: %s\n", c.TsControlURL) - fmt.Printf(" Language: %s\n", c.Language) - fmt.Printf(" PQC Enabled: %t\n", c.EnablePQC) - fmt.Printf(" PQC Level: %d\n", c.PQCLevel) - fmt.Printf(" Verbose: %t\n", c.Verbose) - return nil -} - -func (c *ConfigCommand) setConfiguration(keyValue string, global bool) error { - parts := strings.SplitN(keyValue, "=", 2) - if len(parts) != 2 { - return fmt.Errorf("invalid format, expected key=value") - } - - key, value := parts[0], parts[1] - fmt.Printf("Setting %s = %s", key, value) - if global { - fmt.Print(" (global)") - } - fmt.Println() - - // TODO: Implement actual configuration persistence - return fmt.Errorf("configuration persistence not yet implemented") -} - -func (c *ConfigCommand) unsetConfiguration(key string, global bool) error { - fmt.Printf("Unsetting %s", key) - if global { - fmt.Print(" (global)") - } - fmt.Println() - - // TODO: Implement actual configuration persistence - return fmt.Errorf("configuration persistence not yet implemented") -} - -func (c *ConfigCommand) resetConfiguration(global bool) error { - scope := "local" - if global { - scope = "global" - } - fmt.Printf("Resetting %s configuration to defaults\n", scope) - - // TODO: Implement actual configuration reset - return fmt.Errorf("configuration reset not yet implemented") -} - -// PQC management methods for PQCCommand - -func (c *PQCCommand) showSupportedAlgorithms() error { - fmt.Println("Supported Post-Quantum Cryptography Algorithms:") - fmt.Println(" Key Exchange:") - fmt.Println(" - Kyber768") - fmt.Println(" - Kyber1024") - fmt.Println(" Digital Signatures:") - fmt.Println(" - Dilithium3") - fmt.Println(" - Dilithium5") - fmt.Println(" Hybrid Modes:") - fmt.Println(" - X25519-Kyber768") - fmt.Println(" - Ed25519-Dilithium3") - return nil -} - -func (c *PQCCommand) testPQCFunctionality(logger *log.Logger) error { - fmt.Println("Testing Post-Quantum Cryptography functionality...") - - // TODO: Implement actual PQC testing - fmt.Println("✓ Kyber768 key exchange") - fmt.Println("✓ Dilithium3 signatures") - fmt.Println("✓ Hybrid mode compatibility") - fmt.Println("All PQC tests passed!") - - return nil -} - -func (c *PQCCommand) runPQCBenchmarks(logger *log.Logger) error { - fmt.Println("Running Post-Quantum Cryptography benchmarks...") - - // TODO: Implement actual PQC benchmarks - fmt.Println("Kyber768 Key Generation: 1.2ms") - fmt.Println("Kyber768 Encapsulation: 0.8ms") - fmt.Println("Kyber768 Decapsulation: 1.1ms") - fmt.Println("Dilithium3 Sign: 2.3ms") - fmt.Println("Dilithium3 Verify: 1.7ms") - - return nil -} - -func (c *PQCCommand) showPQCStatus(logger *log.Logger) error { - fmt.Println("Post-Quantum Cryptography Status:") - fmt.Printf(" Enabled: %t\n", c.EnablePQC) - fmt.Printf(" Level: %d\n", c.PQCLevel) - - levelDesc := map[int]string{ - 0: "Disabled", - 1: "Hybrid (Classical + PQC)", - 2: "Strict PQC Only", - } - - if desc, ok := levelDesc[c.PQCLevel]; ok { - fmt.Printf(" Description: %s\n", desc) - } - - ready, assessment := pqc.CheckGlobalQuantumReadiness(logger) - fmt.Printf(" Quantum Readiness: %v - %s\n", ready, assessment) - - return nil -} diff --git a/_old_complex/cmd.go b/_old_complex/cmd.go deleted file mode 100644 index 288cfe2..0000000 --- a/_old_complex/cmd.go +++ /dev/null @@ -1,487 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "log" - "os" - "strings" - - "github.com/charmbracelet/fang" - "github.com/charmbracelet/huh" - "github.com/charmbracelet/lipgloss" - "github.com/spf13/cobra" - - "github.com/derekg/ts-ssh/internal/crypto/pqc" -) - -// Style definitions using lipgloss -var ( - // Theme colors - primaryColor = lipgloss.Color("#04B575") - errorColor = lipgloss.Color("#FF4B4B") - warningColor = lipgloss.Color("#FFA500") - infoColor = lipgloss.Color("#3B82F6") - - // Styles - titleStyle = lipgloss.NewStyle(). - Foreground(primaryColor). - Bold(true) - - successStyle = lipgloss.NewStyle(). - Foreground(primaryColor) - - errorStyle = lipgloss.NewStyle(). - Foreground(errorColor). - Bold(true) - - warningStyle = lipgloss.NewStyle(). - Foreground(warningColor) - - infoStyle = lipgloss.NewStyle(). - Foreground(infoColor) - - headerStyle = lipgloss.NewStyle(). - Foreground(primaryColor). - Bold(true). - Underline(true) -) - -// NewRootCmd creates the root command with Cobra/Fang integration -func NewRootCmd() *cobra.Command { - config := &Config{ - EnablePQC: true, - PQCLevel: 1, - } - - rootCmd := &cobra.Command{ - Use: "ts-ssh [user@]hostname[:port] [command...]", - Short: T("root_short"), - Long: titleStyle.Render("ts-ssh") + " - " + T("root_long"), - Example: T("root_examples"), - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - // Default behavior: if args are provided and first arg is not a subcommand, - // treat it as a connection attempt - if len(args) > 0 { - return runConnect(config, args) - } - // Otherwise show help - return cmd.Help() - }, - } - - // Global flags - rootCmd.PersistentFlags().StringVarP(&config.SSHUser, "user", "u", "", T("flag_user_help")) - rootCmd.PersistentFlags().StringVarP(&config.SSHKeyPath, "identity", "i", "", T("flag_identity_help")) - rootCmd.PersistentFlags().StringVarP(&config.SSHConfigFile, "config", "F", "", T("flag_config_help")) - rootCmd.PersistentFlags().StringVar(&config.TsnetDir, "tsnet-dir", "", T("flag_tsnet_help")) - rootCmd.PersistentFlags().StringVar(&config.TsControlURL, "control-url", "", T("flag_control_help")) - rootCmd.PersistentFlags().BoolVarP(&config.Verbose, "verbose", "v", false, T("flag_verbose_help")) - rootCmd.PersistentFlags().BoolVar(&config.InsecureHostKey, "insecure", false, T("flag_insecure_help")) - rootCmd.PersistentFlags().BoolVar(&config.ForceInsecure, "force-insecure", false, T("flag_force_insecure_help")) - rootCmd.PersistentFlags().StringVar(&config.Language, "lang", "", T("flag_lang_help")) - rootCmd.PersistentFlags().BoolVar(&config.EnablePQC, "pqc", true, T("flag_pqc_help")) - rootCmd.PersistentFlags().IntVar(&config.PQCLevel, "pqc-level", 1, T("flag_pqc_level_help")) - - // Add subcommands - rootCmd.AddCommand( - newConnectCmd(config), - newSCPCmd(config), - newListCmd(config), - newExecCmd(config), - newMultiCmd(config), - newConfigCmd(config), - newPQCCmd(config), - newVersionCmd(config), - ) - - return rootCmd -} - -// newConnectCmd creates the connect subcommand -func newConnectCmd(config *Config) *cobra.Command { - var forwardDest string - - cmd := &cobra.Command{ - Use: "connect [user@]hostname[:port] [command...]", - Aliases: []string{"ssh"}, - Short: T("connect_short"), - Long: T("connect_long"), - Example: T("connect_examples"), - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - // Create connect command with forward destination - connectCmd := &ConnectCommand{ - Config: config, - ForwardDest: forwardDest, - } - if len(args) > 0 { - connectCmd.Target = args[0] - if len(args) > 1 { - connectCmd.Command = args[1:] - } - } - return connectCmd.Run(context.Background()) - }, - } - - cmd.Flags().StringVarP(&forwardDest, "forward", "W", "", "Forward stdin/stdout to specified destination") - - return cmd -} - -// newSCPCmd creates the SCP subcommand -func newSCPCmd(config *Config) *cobra.Command { - var recursive bool - var preserve bool - - cmd := &cobra.Command{ - Use: "scp [-r] [-p] source destination", - Short: T("scp_short"), - Long: T("scp_long"), - Example: T("scp_examples"), - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - scpCmd := &SCPCommand{ - Config: config, - Source: args[0], - Destination: args[1], - Recursive: recursive, - Preserve: preserve, - } - return scpCmd.Run(context.Background()) - }, - } - - cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "Recursively copy directories") - cmd.Flags().BoolVarP(&preserve, "preserve", "p", false, "Preserve file attributes") - - return cmd -} - -// newListCmd creates the list subcommand -func newListCmd(config *Config) *cobra.Command { - var interactive bool - - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: T("list_short"), - Long: T("list_long"), - Example: T("list_examples"), - RunE: func(cmd *cobra.Command, args []string) error { - listCmd := &ListCommand{ - Config: config, - Interactive: interactive, - } - return listCmd.Run(context.Background()) - }, - } - - cmd.Flags().BoolVar(&interactive, "interactive", false, "Interactive host picker with styled UI") - - return cmd -} - -// newExecCmd creates the exec subcommand -func newExecCmd(config *Config) *cobra.Command { - var command string - var parallel bool - - cmd := &cobra.Command{ - Use: "exec [hosts...] -c command", - Short: T("exec_short"), - Long: T("exec_long"), - Example: T("exec_examples"), - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - execCmd := &ExecCommand{ - Config: config, - Command: command, - Hosts: args, - Parallel: parallel, - } - return execCmd.Run(context.Background()) - }, - } - - cmd.Flags().StringVarP(&command, "command", "c", "", "Command to execute on hosts (required)") - cmd.MarkFlagRequired("command") - cmd.Flags().BoolVarP(¶llel, "parallel", "p", false, "Execute commands in parallel") - - return cmd -} - -// newMultiCmd creates the multi subcommand -func newMultiCmd(config *Config) *cobra.Command { - var hosts string - var sessions bool - var tmux bool - - cmd := &cobra.Command{ - Use: "multi", - Short: T("multi_short"), - Long: T("multi_long"), - Example: T("multi_examples"), - RunE: func(cmd *cobra.Command, args []string) error { - multiCmd := &MultiCommand{ - Config: config, - Hosts: hosts, - Sessions: sessions, - Tmux: tmux, - } - return multiCmd.Run(context.Background()) - }, - } - - cmd.Flags().StringVar(&hosts, "hosts", "", "Comma-separated list of hosts") - cmd.Flags().BoolVarP(&sessions, "sessions", "s", false, "Create multiple SSH sessions") - cmd.Flags().BoolVarP(&tmux, "tmux", "t", false, "Use tmux for session management") - - return cmd -} - -// newConfigCmd creates the config subcommand -func newConfigCmd(config *Config) *cobra.Command { - var show bool - var set string - var unset string - var reset bool - var global bool - - cmd := &cobra.Command{ - Use: "config", - Short: T("config_short"), - Long: T("config_long"), - Example: T("config_examples"), - RunE: func(cmd *cobra.Command, args []string) error { - configCmd := &ConfigCommand{ - Config: config, - Show: show, - Set: set, - Unset: unset, - Reset: reset, - Global: global, - } - return configCmd.Run(context.Background()) - }, - } - - cmd.Flags().BoolVar(&show, "show", false, "Show current configuration") - cmd.Flags().StringVar(&set, "set", "", "Set configuration value (key=value)") - cmd.Flags().StringVar(&unset, "unset", "", "Unset configuration value") - cmd.Flags().BoolVar(&reset, "reset", false, "Reset configuration to defaults") - cmd.Flags().BoolVarP(&global, "global", "g", false, "Apply to global configuration") - - return cmd -} - -// newPQCCmd creates the PQC subcommand -func newPQCCmd(config *Config) *cobra.Command { - var report bool - var test bool - var benchmark bool - var showSupported bool - - cmd := &cobra.Command{ - Use: "pqc", - Short: T("pqc_short"), - Long: T("pqc_long"), - Example: T("pqc_examples"), - RunE: func(cmd *cobra.Command, args []string) error { - pqcCmd := &PQCCommand{ - Config: config, - Report: report, - Test: test, - Benchmark: benchmark, - ShowSupported: showSupported, - } - return pqcCmd.Run(context.Background()) - }, - } - - cmd.Flags().BoolVarP(&report, "report", "r", false, "Generate PQC usage report") - cmd.Flags().BoolVarP(&test, "test", "t", false, "Test PQC functionality") - cmd.Flags().BoolVarP(&benchmark, "benchmark", "b", false, "Run PQC performance benchmarks") - cmd.Flags().BoolVarP(&showSupported, "supported", "s", false, "Show supported PQC algorithms") - - return cmd -} - -// newVersionCmd creates the version subcommand -func newVersionCmd(config *Config) *cobra.Command { - var short bool - var commit bool - - cmd := &cobra.Command{ - Use: "version", - Short: T("version_short"), - Long: T("version_long"), - RunE: func(cmd *cobra.Command, args []string) error { - versionCmd := &VersionCommand{ - Config: config, - Short: short, - Commit: commit, - } - return versionCmd.Run(context.Background()) - }, - } - - cmd.Flags().BoolVarP(&short, "short", "s", false, "Show short version only") - cmd.Flags().BoolVarP(&commit, "commit", "c", false, "Include commit information") - - return cmd -} - -// runConnect handles the main SSH connection logic for backwards compatibility -func runConnect(config *Config, args []string) error { - // Parse target and command - if len(args) == 0 { - return fmt.Errorf("%s", T("target_hostname_required")) - } - - target := args[0] - var command []string - if len(args) > 1 { - command = args[1:] - } - - // Create connect command - connectCmd := &ConnectCommand{ - Config: config, - Target: target, - Command: command, - } - - // Show styled connection message - if config.Verbose { - fmt.Println(infoStyle.Render("🔐 Establishing secure connection to " + target + "...")) - } - - // Run the connection - return connectCmd.Run(context.Background()) -} - -// EnhancedListCommand shows an interactive host picker using huh -func (c *ListCommand) RunInteractive(ctx context.Context, hosts []string) error { - if len(hosts) == 0 { - fmt.Println(warningStyle.Render("⚠️ No hosts found on the Tailscale network")) - return nil - } - - // Create styled options - options := make([]huh.Option[string], len(hosts)) - for i, host := range hosts { - // Add some visual flair to the host display - displayName := fmt.Sprintf("🖥️ %s", host) - options[i] = huh.NewOption(displayName, host) - } - - var selectedHost string - - // Create the interactive form - form := huh.NewForm( - huh.NewGroup( - huh.NewSelect[string](). - Title(headerStyle.Render("Select a host to connect to")). - Description("Use arrow keys to navigate, Enter to select"). - Options(options...). - Value(&selectedHost), - ), - ) - - // Run the form - if err := form.Run(); err != nil { - return err - } - - if selectedHost == "" { - return fmt.Errorf("%s", T("no_host_selected")) - } - - // Show connection message - fmt.Println(successStyle.Render("✓ Selected: " + selectedHost)) - fmt.Println(infoStyle.Render("🔐 Establishing connection...")) - - // Create app config for SSH connection - appConfig := &AppConfig{ - SSHUser: c.SSHUser, - SSHKeyPath: c.SSHKeyPath, - TsnetDir: c.TsnetDir, - TsControlURL: c.TsControlURL, - Target: selectedHost, - Verbose: c.Verbose, - InsecureHostKey: c.InsecureHostKey, - EnablePQC: c.EnablePQC, - PQCLevel: c.PQCLevel, - } - - // Set up logger - if c.Verbose { - appConfig.Logger = logger - } else { - appConfig.Logger = log.New(io.Discard, "", 0) - } - - return handleSSHOperation(appConfig) -} - -// ShowPQCReport displays a styled PQC report -func (c *PQCCommand) ShowStyledReport(logger *log.Logger) error { - report := pqc.GenerateGlobalReport(logger) - - // Style the report header - fmt.Println(headerStyle.Render("🔐 Post-Quantum Cryptography Report")) - fmt.Println() - - // Parse and style the report sections - lines := strings.Split(report, "\n") - for _, line := range lines { - if strings.Contains(line, "✓") { - fmt.Println(successStyle.Render(line)) - } else if strings.Contains(line, "⚠") { - fmt.Println(warningStyle.Render(line)) - } else if strings.Contains(line, "❌") { - fmt.Println(errorStyle.Render(line)) - } else if strings.HasPrefix(line, "=") || strings.HasPrefix(line, "-") { - fmt.Println(infoStyle.Render(line)) - } else { - fmt.Println(line) - } - } - - // Check quantum readiness - ready, assessment := pqc.CheckGlobalQuantumReadiness(logger) - fmt.Println() - - if ready { - fmt.Println(successStyle.Render("✅ Quantum Readiness: " + assessment)) - } else { - fmt.Println(warningStyle.Render("⚠️ Quantum Readiness: " + assessment)) - } - - // Get recommendations - recommendations := pqc.GetGlobalRecommendations(logger) - if len(recommendations) > 0 { - fmt.Println() - fmt.Println(headerStyle.Render("📋 Recommendations")) - for _, rec := range recommendations { - fmt.Printf(" %s %s\n", infoStyle.Render("•"), rec) - } - } - - return nil -} - -// ExecuteWithFang runs the CLI with Fang enhancements -func ExecuteWithFang(ctx context.Context) error { - // Initialize i18n early based on command line arguments - initI18nForCLI(os.Args) - - rootCmd := NewRootCmd() - - // Apply Fang enhancements - return fang.Execute(ctx, rootCmd) -} diff --git a/_old_complex/i18n.go b/_old_complex/i18n.go deleted file mode 100644 index 33f850c..0000000 --- a/_old_complex/i18n.go +++ /dev/null @@ -1,1805 +0,0 @@ -package main - -import ( - "os" - "strings" - "sync" - - "golang.org/x/text/language" - "golang.org/x/text/message" - - internali18n "github.com/derekg/ts-ssh/internal/i18n" -) - -// Supported languages (Top 10 most popular languages by speakers) -const ( - LangEnglish = "en" - LangSpanish = "es" - LangChinese = "zh" - LangHindi = "hi" - LangArabic = "ar" - LangBengali = "bn" - LangPortuguese = "pt" - LangRussian = "ru" - LangJapanese = "ja" - LangGerman = "de" - LangFrench = "fr" -) - -var ( - // Global printer for internationalization - printer *message.Printer - - // Synchronization for thread-safe access - initI18nOnce sync.Once - printerMu sync.RWMutex - - // Available languages - supportedLanguages = map[string]language.Tag{ - LangEnglish: language.English, - LangSpanish: language.Spanish, - LangChinese: language.Chinese, - LangHindi: language.Hindi, - LangArabic: language.Arabic, - LangBengali: language.Bengali, - LangPortuguese: language.Portuguese, - LangRussian: language.Russian, - LangJapanese: language.Japanese, - LangGerman: language.German, - LangFrench: language.French, - } -) - -// initI18n initializes the internationalization system thread-safely -func initI18n(langFlag string) { - // Ensure messages are registered only once across all goroutines - initI18nOnce.Do(func() { - registerMessages() - }) - - // Determine language preference: CLI flag > env var > default - lang := determineLang(langFlag) - - // Get language tag - tag, exists := supportedLanguages[lang] - if !exists { - tag = language.English // fallback to English - } - - // Create printer for the selected language with thread-safe access - printerMu.Lock() - printer = message.NewPrinter(tag) - printerMu.Unlock() - - // Also initialize the internal i18n package with the same language - internali18n.InitI18n(langFlag) -} - -// determineLang determines which language to use based on priority: -// 1. CLI flag (--lang) -// 2. Environment variable (TS_SSH_LANG) -// 3. Standard locale environment variables (LC_ALL, LANG) -// 4. Default (English) -func determineLang(langFlag string) string { - // Check CLI flag first - if langFlag != "" { - return normalizeLanguage(langFlag) - } - - // Check custom environment variable - if envLang := os.Getenv("TS_SSH_LANG"); envLang != "" { - return normalizeLanguage(envLang) - } - - // Check standard locale environment variables - if envLang := os.Getenv("LC_ALL"); envLang != "" { - return normalizeLanguage(envLang) - } - - if envLang := os.Getenv("LANG"); envLang != "" { - return normalizeLanguage(envLang) - } - - // Default to English - return LangEnglish -} - -// normalizeLanguage normalizes language codes to our supported format -func normalizeLanguage(lang string) string { - lang = strings.ToLower(strings.TrimSpace(lang)) - - // Handle common variations - switch { - case strings.HasPrefix(lang, "en") || lang == "english": - return LangEnglish - case strings.HasPrefix(lang, "es") || lang == "spanish" || lang == "español": - return LangSpanish - case strings.HasPrefix(lang, "zh") || lang == "chinese" || lang == "中文": - return LangChinese - case strings.HasPrefix(lang, "hi") || lang == "hindi" || lang == "हिन्दी": - return LangHindi - case strings.HasPrefix(lang, "ar") || lang == "arabic" || lang == "العربية": - return LangArabic - case strings.HasPrefix(lang, "bn") || lang == "bengali" || lang == "বাংলা": - return LangBengali - case strings.HasPrefix(lang, "pt") || lang == "portuguese" || lang == "português": - return LangPortuguese - case strings.HasPrefix(lang, "ru") || lang == "russian" || lang == "русский": - return LangRussian - case strings.HasPrefix(lang, "ja") || lang == "japanese" || lang == "日本語": - return LangJapanese - case strings.HasPrefix(lang, "de") || lang == "german" || lang == "deutsch": - return LangGerman - case strings.HasPrefix(lang, "fr") || lang == "french" || lang == "français": - return LangFrench - default: - return LangEnglish // fallback - } -} - -// registerMessages registers all translatable messages -func registerMessages() { - // Help and usage messages - message.SetString(language.English, "usage_header", "Usage: %s [options] [user@]hostname[:port] [command...]") - message.SetString(language.Spanish, "usage_header", "Uso: %s [opciones] [usuario@]servidor[:puerto] [comando...]") - message.SetString(language.Chinese, "usage_header", "用法: %s [选项] [用户@]主机名[:端口] [命令...]") - message.SetString(language.Hindi, "usage_header", "उपयोग: %s [विकल्प] [उपयोगकर्ता@]होस्टनाम[:पोर्ट] [कमांड...]") - message.SetString(language.Arabic, "usage_header", "الاستخدام: %s [خيارات] [مستخدم@]اسم_المضيف[:منفذ] [أمر...]") - message.SetString(language.Bengali, "usage_header", "ব্যবহার: %s [বিকল্প] [ব্যবহারকারী@]হোস্টনাম[:পোর্ট] [কমান্ড...]") - message.SetString(language.Portuguese, "usage_header", "Uso: %s [opções] [usuário@]hostname[:porta] [comando...]") - message.SetString(language.Russian, "usage_header", "Использование: %s [опции] [пользователь@]хост[:порт] [команда...]") - message.SetString(language.Japanese, "usage_header", "使用法: %s [オプション] [ユーザー@]ホスト名[:ポート] [コマンド...]") - message.SetString(language.German, "usage_header", "Verwendung: %s [Optionen] [Benutzer@]Hostname[:Port] [Befehl...]") - message.SetString(language.French, "usage_header", "Utilisation: %s [options] [utilisateur@]nom_hôte[:port] [commande...]") - - message.SetString(language.English, "usage_list", " %s --list # List available hosts") - message.SetString(language.Spanish, "usage_list", " %s --list # Listar servidores disponibles") - - message.SetString(language.English, "usage_multi", " %s --multi host1,host2,host3 # Multi-host tmux session") - message.SetString(language.Spanish, "usage_multi", " %s --multi servidor1,servidor2,servidor3 # Sesión tmux multi-servidor") - - message.SetString(language.English, "usage_exec", " %s --exec \"command\" host1,host2 # Run command on multiple hosts") - message.SetString(language.Spanish, "usage_exec", " %s --exec \"comando\" servidor1,servidor2 # Ejecutar comando en múltiples servidores") - - message.SetString(language.English, "usage_copy", " %s --copy file.txt host1,host2:/tmp/ # Copy file to multiple hosts") - message.SetString(language.Spanish, "usage_copy", " %s --copy archivo.txt servidor1,servidor2:/tmp/ # Copiar archivo a múltiples servidores") - - message.SetString(language.English, "usage_pick", " %s --pick # Interactive host picker") - message.SetString(language.Spanish, "usage_pick", " %s --pick # Selector interactivo de servidores") - - message.SetString(language.English, "usage_description", "Powerful SSH/SCP tool for Tailscale networks.\n\nOptions:") - message.SetString(language.Spanish, "usage_description", "Herramienta SSH/SCP potente para redes Tailscale.\n\nOpciones:") - - message.SetString(language.English, "examples_header", "\nExamples:") - message.SetString(language.Spanish, "examples_header", "\nEjemplos:") - - message.SetString(language.English, "examples_basic_ssh", " Basic SSH:") - message.SetString(language.Spanish, "examples_basic_ssh", " SSH básico:") - - message.SetString(language.English, "examples_interactive", " %s user@host # Interactive SSH session") - message.SetString(language.Spanish, "examples_interactive", " %s usuario@servidor # Sesión SSH interactiva") - - message.SetString(language.English, "examples_remote_cmd", " %s user@host ls -lah # Run remote command") - message.SetString(language.Spanish, "examples_remote_cmd", " %s usuario@servidor ls -lah # Ejecutar comando remoto") - - message.SetString(language.English, "examples_host_discovery", "\n Host Discovery:") - message.SetString(language.Spanish, "examples_host_discovery", "\n Descubrimiento de servidores:") - - message.SetString(language.English, "examples_list_hosts", " %s --list # Show all Tailscale hosts") - message.SetString(language.Spanish, "examples_list_hosts", " %s --list # Mostrar todos los servidores Tailscale") - - message.SetString(language.English, "examples_pick_host", " %s --pick # Pick host interactively") - message.SetString(language.Spanish, "examples_pick_host", " %s --pick # Elegir servidor interactivamente") - - message.SetString(language.English, "examples_multi_host", "\n Multi-Host Operations:") - message.SetString(language.Spanish, "examples_multi_host", "\n Operaciones multi-servidor:") - - message.SetString(language.English, "examples_tmux", " %s --multi web1,web2,db1 # Tmux session with 3 hosts") - message.SetString(language.Spanish, "examples_tmux", " %s --multi web1,web2,db1 # Sesión tmux con 3 servidores") - - message.SetString(language.English, "examples_exec_multi", " %s --exec \"uptime\" web1,web2 # Run command on 2 hosts") - message.SetString(language.Spanish, "examples_exec_multi", " %s --exec \"uptime\" web1,web2 # Ejecutar comando en 2 servidores") - - message.SetString(language.English, "examples_parallel", " %s --parallel --exec \"ps aux\" web1,web2 # Parallel execution") - message.SetString(language.Spanish, "examples_parallel", " %s --parallel --exec \"ps aux\" web1,web2 # Ejecución paralela") - - message.SetString(language.English, "examples_file_transfer", "\n File Transfer:") - message.SetString(language.Spanish, "examples_file_transfer", "\n Transferencia de archivos:") - - message.SetString(language.English, "examples_scp_single", " %s local.txt user@host:/remote/ # Single SCP upload") - message.SetString(language.Spanish, "examples_scp_single", " %s local.txt usuario@servidor:/remoto/ # Subida SCP única") - - message.SetString(language.English, "examples_scp_multi", " %s --copy deploy.sh web1,web2:/tmp/ # Multi-host SCP") - message.SetString(language.Spanish, "examples_scp_multi", " %s --copy deploy.sh web1,web2:/tmp/ # SCP multi-servidor") - - message.SetString(language.English, "examples_proxy", "\n ProxyCommand:") - message.SetString(language.Spanish, "examples_proxy", "\n ComandoProxy:") - - message.SetString(language.English, "examples_proxy_cmd", " %s -W host:port # Proxy stdio via Tailscale") - message.SetString(language.Spanish, "examples_proxy_cmd", " %s -W servidor:puerto # Proxy stdio vía Tailscale") - - // Error messages - message.SetString(language.English, "error_init_tailscale", "Failed to initialize Tailscale connection: %v") - message.SetString(language.Spanish, "error_init_tailscale", "Error al inicializar conexión Tailscale: %v") - message.SetString(language.Chinese, "error_init_tailscale", "初始化 Tailscale 连接失败: %v") - message.SetString(language.Hindi, "error_init_tailscale", "Tailscale कनेक्शन प्रारंभ करने में विफल: %v") - message.SetString(language.Arabic, "error_init_tailscale", "فشل في تهيئة اتصال Tailscale: %v") - message.SetString(language.Bengali, "error_init_tailscale", "Tailscale সংযোগ শুরু করতে ব্যর্থ: %v") - message.SetString(language.Portuguese, "error_init_tailscale", "Falha ao inicializar conexão Tailscale: %v") - message.SetString(language.Russian, "error_init_tailscale", "Не удалось инициализировать соединение Tailscale: %v") - message.SetString(language.Japanese, "error_init_tailscale", "Tailscale接続の初期化に失敗しました: %v") - message.SetString(language.German, "error_init_tailscale", "Fehler beim Initialisieren der Tailscale-Verbindung: %v") - message.SetString(language.French, "error_init_tailscale", "Échec de l'initialisation de la connexion Tailscale: %v") - - message.SetString(language.English, "error_scp_failed", "SCP operation failed: %v") - message.SetString(language.Spanish, "error_scp_failed", "Operación SCP falló: %v") - message.SetString(language.Chinese, "error_scp_failed", "SCP 操作失败: %v") - message.SetString(language.Hindi, "error_scp_failed", "SCP ऑपरेशन विफल: %v") - message.SetString(language.Arabic, "error_scp_failed", "فشلت عملية SCP: %v") - message.SetString(language.Bengali, "error_scp_failed", "SCP অপারেশন ব্যর্থ: %v") - message.SetString(language.Portuguese, "error_scp_failed", "Operação SCP falhou: %v") - message.SetString(language.Russian, "error_scp_failed", "Операция SCP не удалась: %v") - message.SetString(language.Japanese, "error_scp_failed", "SCP操作が失敗しました: %v") - message.SetString(language.German, "error_scp_failed", "SCP-Operation fehlgeschlagen: %v") - message.SetString(language.French, "error_scp_failed", "L'opération SCP a échoué: %v") - - message.SetString(language.English, "scp_success", "SCP operation completed successfully.") - message.SetString(language.Spanish, "scp_success", "Operación SCP completada exitosamente.") - message.SetString(language.Chinese, "scp_success", "SCP 操作成功完成。") - message.SetString(language.Hindi, "scp_success", "SCP ऑपरेशन सफलतापूर्वक पूरा हुआ।") - message.SetString(language.Arabic, "scp_success", "تمت عملية SCP بنجاحق") - message.SetString(language.Bengali, "scp_success", "SCP অপারেশন সফলভাবে সম্পন্ন হয়েছে।") - message.SetString(language.Portuguese, "scp_success", "Operação SCP concluída com sucesso.") - message.SetString(language.Russian, "scp_success", "Операция SCP успешно завершена.") - message.SetString(language.Japanese, "scp_success", "SCP操作が正常に完了しました。") - message.SetString(language.German, "scp_success", "SCP-Operation erfolgreich abgeschlossen.") - message.SetString(language.French, "scp_success", "Opération SCP terminée avec succès.") - - message.SetString(language.English, "error_parsing_target", "Error parsing target for SSH: %v") - message.SetString(language.Spanish, "error_parsing_target", "Error analizando destino para SSH: %v") - message.SetString(language.Chinese, "error_parsing_target", "解析 SSH 目标错误: %v") - message.SetString(language.Hindi, "error_parsing_target", "SSH के लिए लक्ष्य पार्स करने में त्रुटि: %v") - message.SetString(language.Arabic, "error_parsing_target", "خطأ في تحليل الهدف لـ SSH: %v") - message.SetString(language.Bengali, "error_parsing_target", "SSH এর জন্য টার্গেট পার্স করার ত্রুটি: %v") - message.SetString(language.Portuguese, "error_parsing_target", "Erro ao analisar destino para SSH: %v") - message.SetString(language.Russian, "error_parsing_target", "Ошибка разбора цели для SSH: %v") - message.SetString(language.Japanese, "error_parsing_target", "SSH のターゲット解析エラー: %v") - message.SetString(language.German, "error_parsing_target", "Fehler beim Parsen des SSH-Ziels: %v") - message.SetString(language.French, "error_parsing_target", "Erreur lors de l'analyse de la cible SSH: %v") - - message.SetString(language.English, "error_init_ssh", "Failed to initialize Tailscale connection for SSH: %v") - message.SetString(language.Spanish, "error_init_ssh", "Error al inicializar conexión Tailscale para SSH: %v") - - // Authentication messages - message.SetString(language.English, "enter_password", "Enter password for %s@%s: ") - message.SetString(language.Spanish, "enter_password", "Ingresa contraseña para %s@%s: ") - message.SetString(language.Chinese, "enter_password", "输入 %s@%s 的密码: ") - message.SetString(language.Hindi, "enter_password", "%s@%s के लिए पासवर्ड दर्ज करें: ") - message.SetString(language.Arabic, "enter_password", "أدخل كلمة المرور لـ %s@%s: ") - message.SetString(language.Bengali, "enter_password", "%s@%s এর জন্য পাসওয়ার্ড লিখুন: ") - message.SetString(language.Portuguese, "enter_password", "Digite a senha para %s@%s: ") - message.SetString(language.Russian, "enter_password", "Введите пароль для %s@%s: ") - message.SetString(language.Japanese, "enter_password", "%s@%s のパスワードを入力: ") - message.SetString(language.German, "enter_password", "Passwort für %s@%s eingeben: ") - message.SetString(language.French, "enter_password", "Entrez le mot de passe pour %s@%s: ") - - message.SetString(language.English, "host_key_warning", "WARNING: Host key verification is disabled!") - message.SetString(language.Spanish, "host_key_warning", "ADVERTENCIA: ¡Verificación de clave de servidor deshabilitada!") - message.SetString(language.Chinese, "host_key_warning", "警告:主机密钥验证已禁用!") - message.SetString(language.Hindi, "host_key_warning", "चेतावनी: होस्ट की सत्यापन अक्षम है!") - message.SetString(language.Arabic, "host_key_warning", "تحذير: تم تعطيل التحقق من مفتاح المضيف!") - message.SetString(language.Bengali, "host_key_warning", "সতর্কবার্তা: হোস্ট কী যাচাইকরণ নিষ্ক্রিয়!") - message.SetString(language.Portuguese, "host_key_warning", "AVISO: Verificação de chave do host está desabilitada!") - message.SetString(language.Russian, "host_key_warning", "ПРЕДУПРЕЖДЕНИЕ: Проверка ключа хоста отключена!") - message.SetString(language.Japanese, "host_key_warning", "警告: ホストキーの検証が無効です!") - message.SetString(language.German, "host_key_warning", "WARNUNG: Host-Schlüssel-Verifikation ist deaktiviert!") - message.SetString(language.French, "host_key_warning", "AVERTISSEMENT: La vérification de la clé d'hôte est désactivée!") - - message.SetString(language.English, "using_key_auth", "Using public key authentication: %s") - message.SetString(language.Spanish, "using_key_auth", "Usando autenticación de clave pública: %s") - message.SetString(language.Chinese, "using_key_auth", "使用公钥认证: %s") - message.SetString(language.Hindi, "using_key_auth", "पब्लिक की ऑथेंटिकेशन का उपयोग: %s") - message.SetString(language.Arabic, "using_key_auth", "استخدام مصادقة المفتاح العام: %s") - message.SetString(language.Bengali, "using_key_auth", "পাবলিক কী প্রমাণীকরণ ব্যবহার করা হচ্ছে: %s") - message.SetString(language.Portuguese, "using_key_auth", "Usando autenticação por chave pública: %s") - message.SetString(language.Russian, "using_key_auth", "Используется аутентификация по открытому ключу: %s") - message.SetString(language.Japanese, "using_key_auth", "公開鍵認証を使用: %s") - message.SetString(language.German, "using_key_auth", "Verwende öffentliche Schlüssel-Authentifizierung: %s") - message.SetString(language.French, "using_key_auth", "Utilisation de l'authentification par clé publique: %s") - - message.SetString(language.English, "key_auth_failed", "Failed to load private key: %v. Will attempt password auth.") - message.SetString(language.Spanish, "key_auth_failed", "Error cargando clave privada: %v. Se intentará autenticación por contraseña.") - - // Connection messages - message.SetString(language.English, "dial_via_tsnet", "Dialing %s via tsnet...") - message.SetString(language.Spanish, "dial_via_tsnet", "Conectando a %s vía tsnet...") - - message.SetString(language.English, "dial_failed", "Failed to dial %s via tsnet (is Tailscale connection up and host reachable?): %v") - message.SetString(language.Spanish, "dial_failed", "Error conectando a %s vía tsnet (¿está la conexión Tailscale activa y el servidor accesible?): %v") - message.SetString(language.Chinese, "dial_failed", "通过 tsnet 连接到 %s 失败(Tailscale 连接是否正常,主机是否可达?): %v") - message.SetString(language.Hindi, "dial_failed", "tsnet के माध्यम से %s को डायल करने में विफलता (Tailscale कनेक्शन चालू है और होस्ट पहुंचने योग्य है?): %v") - message.SetString(language.Arabic, "dial_failed", "فشل في الاتصال بـ %s عبر tsnet (هل اتصال Tailscale نشط والمضيف قابل للوصول؟): %v") - message.SetString(language.Bengali, "dial_failed", "tsnet এর মাধ্যমে %s এ ডায়েল করতে ব্যর্থ (Tailscale কনেকশন টিক আছে এবং হোস্ট পৌঁছানো যায়?): %v") - message.SetString(language.Portuguese, "dial_failed", "Falha ao conectar com %s via tsnet (conexão Tailscale está ativa e host é alcançável?): %v") - message.SetString(language.Russian, "dial_failed", "Не удалось соединиться с %s через tsnet (работает ли соединение Tailscale и доступен ли хост?): %v") - message.SetString(language.Japanese, "dial_failed", "tsnet経由で%sへの接続に失敗 (Tailscale接続が有効でホストに到達可能ですか?): %v") - message.SetString(language.German, "dial_failed", "Verbindung zu %s über tsnet fehlgeschlagen (ist Tailscale-Verbindung aktiv und Host erreichbar?): %v") - message.SetString(language.French, "dial_failed", "Échec de la connexion à %s via tsnet (la connexion Tailscale est-elle active et l'hôte accessible?): %v") - - message.SetString(language.English, "ssh_connection_established", "SSH connection established.") - message.SetString(language.Spanish, "ssh_connection_established", "Conexión SSH establecida.") - message.SetString(language.Chinese, "ssh_connection_established", "SSH 连接已建立。") - message.SetString(language.Hindi, "ssh_connection_established", "SSH कनेक्शन स्थापित हुआ।") - message.SetString(language.Arabic, "ssh_connection_established", "تم إنشاء اتصال SSHآ") - message.SetString(language.Bengali, "ssh_connection_established", "SSH কনেকশন স্থাপন করা হয়েছে।") - message.SetString(language.Portuguese, "ssh_connection_established", "Conexão SSH estabelecida.") - message.SetString(language.Russian, "ssh_connection_established", "SSH-соединение установлено.") - message.SetString(language.Japanese, "ssh_connection_established", "SSH接続が確立されました。") - message.SetString(language.German, "ssh_connection_established", "SSH-Verbindung hergestellt.") - message.SetString(language.French, "ssh_connection_established", "Connexion SSH établie.") - - message.SetString(language.English, "ssh_connection_failed", "Failed to establish SSH connection to %s: %v") - message.SetString(language.Spanish, "ssh_connection_failed", "Error estableciendo conexión SSH a %s: %v") - message.SetString(language.Chinese, "ssh_connection_failed", "建立到 %s 的 SSH 连接失败: %v") - message.SetString(language.Hindi, "ssh_connection_failed", "%s से SSH कनेक्शन स्थापित करने में विफल: %v") - message.SetString(language.Arabic, "ssh_connection_failed", "فشل في إنشاء اتصال SSH إلى %s: %v") - message.SetString(language.Bengali, "ssh_connection_failed", "%s এ SSH কনেকশন স্থাপন করতে ব্যর্থ: %v") - message.SetString(language.Portuguese, "ssh_connection_failed", "Falha ao estabelecer conexão SSH para %s: %v") - message.SetString(language.Russian, "ssh_connection_failed", "Не удалось установить SSH-соединение с %s: %v") - message.SetString(language.Japanese, "ssh_connection_failed", "%s へのSSH接続の確立に失敗: %v") - message.SetString(language.German, "ssh_connection_failed", "SSH-Verbindung zu %s fehlgeschlagen: %v") - message.SetString(language.French, "ssh_connection_failed", "Échec de l'établissement de la connexion SSH vers %s: %v") - - message.SetString(language.English, "ssh_auth_failed", "SSH Authentication failed for user %s: %v") - message.SetString(language.Spanish, "ssh_auth_failed", "Autenticación SSH falló para usuario %s: %v") - - message.SetString(language.English, "host_key_failed", "SSH Host key verification failed: %v") - message.SetString(language.Spanish, "host_key_failed", "Verificación de clave de servidor SSH falló: %v") - - message.SetString(language.English, "escape_sequence", "\nEscape sequence: ~. to terminate session") - message.SetString(language.Spanish, "escape_sequence", "\nSecuencia de escape: ~. para terminar sesión") - message.SetString(language.Chinese, "escape_sequence", "\n退出序列: ~. 终止会话") - message.SetString(language.Hindi, "escape_sequence", "\nएस्केप सीक्वेंस: ~. सत्र समाप्त करने के लिए") - message.SetString(language.Arabic, "escape_sequence", "\nتسلسل الخروج: ~. لإنهاء الجلسة") - message.SetString(language.Bengali, "escape_sequence", "\nএসকেপ সিকোয়েন্স: ~. সেশন সমাপ্ত করতে") - message.SetString(language.Portuguese, "escape_sequence", "\nSequência de escape: ~. para terminar sessão") - message.SetString(language.Russian, "escape_sequence", "\nПоследовательность выхода: ~. для завершения сессии") - message.SetString(language.Japanese, "escape_sequence", "\nエスケープシーケンス: ~. セッションを終了") - message.SetString(language.German, "escape_sequence", "\nEscape-Sequenz: ~. zum Beenden der Sitzung") - message.SetString(language.French, "escape_sequence", "\nSéquence d'échappement: ~. pour terminer la session") - - message.SetString(language.English, "ssh_session_closed", "SSH session closed.") - message.SetString(language.Spanish, "ssh_session_closed", "Sesión SSH cerrada.") - - // Host list messages - message.SetString(language.English, "no_peers_found", "No Tailscale peers found") - message.SetString(language.Spanish, "no_peers_found", "No se encontraron pares Tailscale") - message.SetString(language.Chinese, "no_peers_found", "未找到 Tailscale 对等节点") - message.SetString(language.Hindi, "no_peers_found", "कोई Tailscale पीयर नहीं मिला") - message.SetString(language.Arabic, "no_peers_found", "لم يتم العثور على أقران Tailscale") - message.SetString(language.Bengali, "no_peers_found", "কোন Tailscale পিয়ার পাওয়া যায়নি") - message.SetString(language.Portuguese, "no_peers_found", "Nenhum par Tailscale encontrado") - message.SetString(language.Russian, "no_peers_found", "Узлы Tailscale не найдены") - message.SetString(language.Japanese, "no_peers_found", "Tailscaleピアが見つかりません") - message.SetString(language.German, "no_peers_found", "Keine Tailscale-Peers gefunden") - message.SetString(language.French, "no_peers_found", "Aucun pair Tailscale trouvé") - - message.SetString(language.English, "host_list_labels", "HOST,IP,STATUS,OS") - message.SetString(language.Spanish, "host_list_labels", "SERVIDOR,IP,ESTADO,SO") - - message.SetString(language.English, "host_list_separator", "----,--,------,--") - message.SetString(language.Spanish, "host_list_separator", "--------,--,------,--") - - message.SetString(language.English, "status_online", "ONLINE") - message.SetString(language.Spanish, "status_online", "EN LÍNEA") - message.SetString(language.Chinese, "status_online", "在线") - message.SetString(language.Hindi, "status_online", "ऑनलाइन") - message.SetString(language.Arabic, "status_online", "متصل") - message.SetString(language.Bengali, "status_online", "অনলাইন") - message.SetString(language.Portuguese, "status_online", "ONLINE") - message.SetString(language.Russian, "status_online", "В СЕТИ") - message.SetString(language.Japanese, "status_online", "オンライン") - message.SetString(language.German, "status_online", "ONLINE") - message.SetString(language.French, "status_online", "EN LIGNE") - - message.SetString(language.English, "status_offline", "OFFLINE") - message.SetString(language.Spanish, "status_offline", "DESCONECTADO") - message.SetString(language.Chinese, "status_offline", "离线") - message.SetString(language.Hindi, "status_offline", "ऑफ़लाइन") - message.SetString(language.Arabic, "status_offline", "غير متصل") - message.SetString(language.Bengali, "status_offline", "অফলাইন") - message.SetString(language.Portuguese, "status_offline", "OFFLINE") - message.SetString(language.Russian, "status_offline", "НЕ В СЕТИ") - message.SetString(language.Japanese, "status_offline", "オフライン") - message.SetString(language.German, "status_offline", "OFFLINE") - message.SetString(language.French, "status_offline", "HORS LIGNE") - - // Host picker messages - message.SetString(language.English, "no_online_hosts", "no online hosts found") - message.SetString(language.Spanish, "no_online_hosts", "no se encontraron servidores en línea") - message.SetString(language.Chinese, "no_online_hosts", "未找到在线主机") - message.SetString(language.Hindi, "no_online_hosts", "कोई ऑनलाइन होस्ट नहीं मिला") - message.SetString(language.Arabic, "no_online_hosts", "لم يتم العثور على مضيفين متصلين") - message.SetString(language.Bengali, "no_online_hosts", "কোন অনলাইন হোস्ট পাওয়া যায়নি") - message.SetString(language.Portuguese, "no_online_hosts", "nenhum host online encontrado") - message.SetString(language.Russian, "no_online_hosts", "онлайн хосты не найдены") - message.SetString(language.Japanese, "no_online_hosts", "オンラインのホストが見つかりません") - message.SetString(language.German, "no_online_hosts", "keine Online-Hosts gefunden") - message.SetString(language.French, "no_online_hosts", "aucun hôte en ligne trouvé") - - message.SetString(language.English, "available_hosts", "Available hosts:") - message.SetString(language.Spanish, "available_hosts", "Servidores disponibles:") - message.SetString(language.Chinese, "available_hosts", "可用主机:") - message.SetString(language.Hindi, "available_hosts", "उपलब्ध होस्ट:") - message.SetString(language.Arabic, "available_hosts", "المضيفين المتاحين:") - message.SetString(language.Bengali, "available_hosts", "উপলব্ধ হোস্ট:") - message.SetString(language.Portuguese, "available_hosts", "Hosts disponíveis:") - message.SetString(language.Russian, "available_hosts", "Доступные хосты:") - message.SetString(language.Japanese, "available_hosts", "利用可能なホスト:") - message.SetString(language.German, "available_hosts", "Verfügbare Hosts:") - message.SetString(language.French, "available_hosts", "Hôtes disponibles:") - - message.SetString(language.English, "select_host", "\nSelect host (1-%d): ") - message.SetString(language.Spanish, "select_host", "\nSelecciona servidor (1-%d): ") - message.SetString(language.Chinese, "select_host", "\n选择主机 (1-%d): ") - message.SetString(language.Hindi, "select_host", "\nहोस्ट चुनें (1-%d): ") - message.SetString(language.Arabic, "select_host", "\nاختر المضيف (1-%d): ") - message.SetString(language.Bengali, "select_host", "\nহোস্ট নির্বাচন করুন (1-%d): ") - message.SetString(language.Portuguese, "select_host", "\nSelecionar host (1-%d): ") - message.SetString(language.Russian, "select_host", "\nВыберите хост (1-%d): ") - message.SetString(language.Japanese, "select_host", "\nホストを選択 (1-%d): ") - message.SetString(language.German, "select_host", "\nHost auswählen (1-%d): ") - message.SetString(language.French, "select_host", "\nSélectionner l'hôte (1-%d): ") - - message.SetString(language.English, "invalid_selection", "invalid selection") - message.SetString(language.Spanish, "invalid_selection", "selección inválida") - message.SetString(language.Chinese, "invalid_selection", "无效选择") - message.SetString(language.Hindi, "invalid_selection", "अमान्य चयन") - message.SetString(language.Arabic, "invalid_selection", "اختيار غير صحيح") - message.SetString(language.Bengali, "invalid_selection", "অবৈধ নির্বাচন") - message.SetString(language.Portuguese, "invalid_selection", "seleção inválida") - message.SetString(language.Russian, "invalid_selection", "неверный выбор") - message.SetString(language.Japanese, "invalid_selection", "無効な選択") - message.SetString(language.German, "invalid_selection", "ungültige Auswahl") - message.SetString(language.French, "invalid_selection", "sélection invalide") - - message.SetString(language.English, "selection_out_of_range", "selection out of range") - message.SetString(language.Spanish, "selection_out_of_range", "selección fuera de rango") - message.SetString(language.Chinese, "selection_out_of_range", "选择超出范围") - message.SetString(language.Hindi, "selection_out_of_range", "चयन सीमा से बाहर") - message.SetString(language.Arabic, "selection_out_of_range", "الاختيار خارج النطاق") - message.SetString(language.Bengali, "selection_out_of_range", "নির্বাচন পরিসরের বাইরে") - message.SetString(language.Portuguese, "selection_out_of_range", "seleção fora do intervalo") - message.SetString(language.Russian, "selection_out_of_range", "выбор вне диапазона") - message.SetString(language.Japanese, "selection_out_of_range", "選択が範囲外") - message.SetString(language.German, "selection_out_of_range", "Auswahl außerhalb des Bereichs") - message.SetString(language.French, "selection_out_of_range", "sélection hors plage") - - message.SetString(language.English, "connecting_to", "Connecting to %s...") - message.SetString(language.Spanish, "connecting_to", "Conectando a %s...") - message.SetString(language.Chinese, "connecting_to", "正在连接到 %s...") - message.SetString(language.Hindi, "connecting_to", "%s से कनेक्ट हो रहे हैं...") - message.SetString(language.Arabic, "connecting_to", "الاتصال بـ %s...") - message.SetString(language.Bengali, "connecting_to", "%s এ সংযোগ করা হচ্ছে...") - message.SetString(language.Portuguese, "connecting_to", "Conectando a %s...") - message.SetString(language.Russian, "connecting_to", "Подключение к %s...") - message.SetString(language.Japanese, "connecting_to", "%s に接続中...") - message.SetString(language.German, "connecting_to", "Verbindung zu %s...") - message.SetString(language.French, "connecting_to", "Connexion à %s...") - - // Multi-host operation messages - message.SetString(language.English, "no_hosts_specified", "no hosts specified") - message.SetString(language.Spanish, "no_hosts_specified", "no se especificaron servidores") - - message.SetString(language.English, "no_hosts_for_exec", "no hosts specified for --exec") - message.SetString(language.Spanish, "no_hosts_for_exec", "no se especificaron servidores para --exec") - - message.SetString(language.English, "invalid_copy_format", "invalid --copy format. Use: localfile host1,host2:/path/") - message.SetString(language.Spanish, "invalid_copy_format", "formato --copy inválido. Usar: archivo_local servidor1,servidor2:/ruta/") - - message.SetString(language.English, "invalid_remote_spec", "invalid remote specification. Must include path after ':'") - message.SetString(language.Spanish, "invalid_remote_spec", "especificación remota inválida. Debe incluir ruta después de ':'") - - message.SetString(language.English, "copying_to", "Copying %s to %s:%s...") - message.SetString(language.Spanish, "copying_to", "Copiando %s a %s:%s...") - - message.SetString(language.English, "copy_failed", "Failed to copy to %s: %v") - message.SetString(language.Spanish, "copy_failed", "Error copiando a %s: %v") - message.SetString(language.Chinese, "copy_failed", "复制到 %s 失败: %v") - message.SetString(language.Hindi, "copy_failed", "%s में कॉपी करना विफल: %v") - message.SetString(language.Arabic, "copy_failed", "فشل في النسخ إلى %s: %v") - message.SetString(language.Bengali, "copy_failed", "%s এ কপি করতে ব্যর্থ: %v") - message.SetString(language.Portuguese, "copy_failed", "Falha ao copiar para %s: %v") - message.SetString(language.Russian, "copy_failed", "Не удалось скопировать в %s: %v") - message.SetString(language.Japanese, "copy_failed", "%s への複写に失敗: %v") - message.SetString(language.German, "copy_failed", "Fehler beim Kopieren nach %s: %v") - message.SetString(language.French, "copy_failed", "Échec de la copie vers %s: %v") - - message.SetString(language.English, "copy_success", "Successfully copied to %s") - message.SetString(language.Spanish, "copy_success", "Copiado exitosamente a %s") - message.SetString(language.Chinese, "copy_success", "成功复制到 %s") - message.SetString(language.Hindi, "copy_success", "%s में सफलतापूर्वक कॉपी की गई") - message.SetString(language.Arabic, "copy_success", "تم النسخ بنجاح إلى %s") - message.SetString(language.Bengali, "copy_success", "%s এ সফলভাবে কপি করা হয়েছে") - message.SetString(language.Portuguese, "copy_success", "Copiado com sucesso para %s") - message.SetString(language.Russian, "copy_success", "Успешно скопировано в %s") - message.SetString(language.Japanese, "copy_success", "%s への複写が成功しました") - message.SetString(language.German, "copy_success", "Erfolgreich nach %s kopiert") - message.SetString(language.French, "copy_success", "Copié avec succès vers %s") - - // SCP error messages - message.SetString(language.English, "invalid_scp_remote", "invalid remote SCP argument format: %q. Must be [user@]host:path") - message.SetString(language.Spanish, "invalid_scp_remote", "formato de argumento SCP remoto inválido: %q. Debe ser [usuario@]servidor:ruta") - - message.SetString(language.English, "invalid_user_host", "invalid user@host format in SCP argument: %q") - message.SetString(language.Spanish, "invalid_user_host", "formato usuario@servidor inválido en argumento SCP: %q") - - message.SetString(language.English, "empty_host_scp", "host cannot be empty in SCP argument: %q") - message.SetString(language.Spanish, "empty_host_scp", "el servidor no puede estar vacío en argumento SCP: %q") - - // Flag descriptions - message.SetString(language.English, "flag_lang_desc", "Language for CLI output (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Spanish, "flag_lang_desc", "Idioma para salida CLI (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Chinese, "flag_lang_desc", "CLI输出语言 (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Hindi, "flag_lang_desc", "CLI आउटपुट के लिए भाषा (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Arabic, "flag_lang_desc", "لغة إخراج CLI (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Bengali, "flag_lang_desc", "CLI আউটপুটের ভাষা (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Portuguese, "flag_lang_desc", "Idioma para saída CLI (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Russian, "flag_lang_desc", "Язык для вывода CLI (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.Japanese, "flag_lang_desc", "CLI出力の言語 (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.German, "flag_lang_desc", "Sprache für CLI-Ausgabe (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - message.SetString(language.French, "flag_lang_desc", "Langue pour la sortie CLI (en, es, zh, hi, ar, bn, pt, ru, ja, de, fr)") - - message.SetString(language.English, "flag_user_desc", "SSH Username") - message.SetString(language.Spanish, "flag_user_desc", "Nombre de usuario SSH") - - message.SetString(language.English, "flag_key_desc", "Path to SSH private key") - message.SetString(language.Spanish, "flag_key_desc", "Ruta a clave privada SSH") - - message.SetString(language.English, "flag_ssh_config_desc", "SSH configuration file") - message.SetString(language.Spanish, "flag_ssh_config_desc", "Archivo de configuración SSH") - - message.SetString(language.English, "flag_tsnet_desc", "Directory to store tsnet state") - message.SetString(language.Spanish, "flag_tsnet_desc", "Directorio para almacenar estado tsnet") - - message.SetString(language.English, "flag_control_desc", "Tailscale control plane URL (optional)") - message.SetString(language.Spanish, "flag_control_desc", "URL del plano de control Tailscale (opcional)") - - message.SetString(language.English, "flag_verbose_desc", "Verbose logging") - message.SetString(language.Spanish, "flag_verbose_desc", "Logging detallado") - - message.SetString(language.English, "flag_insecure_desc", "Disable host key checking (INSECURE!)") - message.SetString(language.Spanish, "flag_insecure_desc", "Deshabilitar verificación de clave de servidor (¡INSEGURO!)") - - message.SetString(language.English, "flag_force_insecure_desc", "Skip confirmation for insecure connections (automation only)") - message.SetString(language.Spanish, "flag_force_insecure_desc", "Omitir confirmación para conexiones inseguras (solo automatización)") - - message.SetString(language.English, "flag_forward_desc", "forward stdio to destination host:port (for use as ProxyCommand)") - message.SetString(language.Spanish, "flag_forward_desc", "reenviar stdio a servidor:puerto destino (para usar como ComandoProxy)") - - message.SetString(language.English, "flag_version_desc", "Print version and exit") - message.SetString(language.Spanish, "flag_version_desc", "Mostrar versión y salir") - - message.SetString(language.English, "flag_list_desc", "List available Tailscale hosts") - message.SetString(language.Spanish, "flag_list_desc", "Listar servidores Tailscale disponibles") - - message.SetString(language.English, "flag_multi_desc", "Start tmux session with multiple hosts (comma-separated)") - message.SetString(language.Spanish, "flag_multi_desc", "Iniciar sesión tmux con múltiples servidores (separados por comas)") - - message.SetString(language.English, "flag_exec_desc", "Execute command on specified hosts") - message.SetString(language.Spanish, "flag_exec_desc", "Ejecutar comando en servidores especificados") - - message.SetString(language.English, "flag_copy_desc", "Copy files to multiple hosts (format: localfile host1,host2:/path/)") - message.SetString(language.Spanish, "flag_copy_desc", "Copiar archivos a múltiples servidores (formato: archivo_local servidor1,servidor2:/ruta/)") - - message.SetString(language.English, "flag_pick_desc", "Interactive host picker (simple selection)") - message.SetString(language.Spanish, "flag_pick_desc", "Selector interactivo de servidores (selección simple)") - - message.SetString(language.English, "flag_parallel_desc", "Execute commands in parallel (use with --exec)") - message.SetString(language.Spanish, "flag_parallel_desc", "Ejecutar comandos en paralelo (usar con --exec)") - - // SCP-specific messages - message.SetString(language.English, "scp_enter_password", "Enter password for %s@%s (for SCP): ") - message.SetString(language.Spanish, "scp_enter_password", "Ingresa contraseña para %s@%s (para SCP): ") - - message.SetString(language.English, "scp_host_key_warning", "CLI SCP: WARNING! Host key verification is disabled!") - message.SetString(language.Spanish, "scp_host_key_warning", "CLI SCP: ¡ADVERTENCIA! ¡Verificación de clave de servidor deshabilitada!") - - message.SetString(language.English, "scp_empty_path", "local or remote path for SCP cannot be empty") - message.SetString(language.Spanish, "scp_empty_path", "la ruta local o remota para SCP no puede estar vacía") - - message.SetString(language.English, "scp_upload_complete", "CLI SCP: Upload complete.") - message.SetString(language.Spanish, "scp_upload_complete", "CLI SCP: Subida completada.") - - message.SetString(language.English, "scp_download_complete", "CLI SCP: Download complete.") - message.SetString(language.Spanish, "scp_download_complete", "CLI SCP: Descarga completada.") - - // Common error messages - message.SetString(language.English, "error_prefix", "Error: %v") - message.SetString(language.Spanish, "error_prefix", "Error: %v") - - message.SetString(language.English, "failed_read_user_input", "failed to read user input: %w") - message.SetString(language.Spanish, "failed_read_user_input", "error al leer entrada del usuario: %w") - - message.SetString(language.English, "hostname_cannot_be_empty", "hostname cannot be empty") - message.SetString(language.Spanish, "hostname_cannot_be_empty", "el nombre del servidor no puede estar vacío") - - message.SetString(language.English, "invalid_port_number", "invalid port number '%s': %w") - message.SetString(language.Spanish, "invalid_port_number", "número de puerto inválido '%s': %w") - - message.SetString(language.English, "invalid_host_port_format", "invalid host:port format '%s': %w") - message.SetString(language.Spanish, "invalid_host_port_format", "formato servidor:puerto inválido '%s': %w") - - // TTY and security messages - message.SetString(language.English, "not_running_in_terminal", "not running in a terminal") - message.SetString(language.Spanish, "not_running_in_terminal", "no se está ejecutando en una terminal") - - message.SetString(language.English, "tty_security_validation_failed", "TTY security validation failed: %w") - message.SetString(language.Spanish, "tty_security_validation_failed", "falló la validación de seguridad TTY: %w") - - message.SetString(language.English, "failed_open_tty", "failed to open TTY: %w") - message.SetString(language.Spanish, "failed_open_tty", "error al abrir TTY: %w") - - // Security warning messages for insecure mode - message.SetString(language.English, "warning_insecure_mode", "Host key verification disabled!") - message.SetString(language.Spanish, "warning_insecure_mode", "¡Verificación de clave de servidor deshabilitada!") - - message.SetString(language.English, "warning_mitm_vulnerability", "This makes you vulnerable to man-in-the-middle attacks.") - message.SetString(language.Spanish, "warning_mitm_vulnerability", "Esto te hace vulnerable a ataques de intermediario (man-in-the-middle).") - - message.SetString(language.English, "warning_trusted_networks_only", "Only use this in trusted network environments.") - message.SetString(language.Spanish, "warning_trusted_networks_only", "Solo usa esto en entornos de red confiables.") - - message.SetString(language.English, "insecure_mode_forced", "Insecure mode forced via --force-insecure flag.") - message.SetString(language.Spanish, "insecure_mode_forced", "Modo inseguro forzado mediante flag --force-insecure.") - - message.SetString(language.English, "confirm_insecure_connection", "Continue with insecure connection? [y/N]:") - message.SetString(language.Spanish, "confirm_insecure_connection", "¿Continuar con conexión insegura? [y/N]:") - - message.SetString(language.English, "connection_cancelled_by_user", "connection cancelled by user") - message.SetString(language.Spanish, "connection_cancelled_by_user", "conexión cancelada por el usuario") - - message.SetString(language.English, "proceeding_with_insecure_connection", "Proceeding with insecure connection...") - message.SetString(language.Spanish, "proceeding_with_insecure_connection", "Procediendo con conexión insegura...") - - // CLI command descriptions for fang - message.SetString(language.English, "cli_description", "Secure SSH/SCP client with Tailscale connectivity for enterprise environments") - message.SetString(language.Spanish, "cli_description", "Cliente SSH/SCP seguro con conectividad Tailscale para entornos empresariales") - message.SetString(language.Chinese, "cli_description", "具有 Tailscale 连接的企业级安全 SSH/SCP 客户端") - message.SetString(language.Hindi, "cli_description", "एंटरप्राइज़ वातावरण के लिए Tailscale कनेक्टिविटी के साथ सुरक्षित SSH/SCP क्लाइंट") - message.SetString(language.Arabic, "cli_description", "عميل SSH/SCP آمن مع اتصال Tailscale للبيئات المؤسسية") - message.SetString(language.Bengali, "cli_description", "এন্টারপ্রাইজ পরিবেশের জন্য Tailscale সংযোগ সহ নিরাপদ SSH/SCP ক্লায়েন্ট") - message.SetString(language.Portuguese, "cli_description", "Cliente SSH/SCP seguro com conectividade Tailscale para ambientes empresariais") - message.SetString(language.Russian, "cli_description", "Безопасный SSH/SCP клиент с подключением Tailscale для корпоративных сред") - message.SetString(language.Japanese, "cli_description", "企業環境向けTailscale接続対応セキュアSSH/SCPクライアント") - message.SetString(language.German, "cli_description", "Sicherer SSH/SCP-Client mit Tailscale-Konnektivität für Unternehmensumgebungen") - message.SetString(language.French, "cli_description", "Client SSH/SCP sécurisé avec connectivité Tailscale pour environnements d'entreprise") - - message.SetString(language.English, "cmd_connect_desc", "Connect to a remote host via SSH (default command)") - message.SetString(language.Spanish, "cmd_connect_desc", "Conectar a un servidor remoto via SSH (comando por defecto)") - message.SetString(language.Chinese, "cmd_connect_desc", "通过 SSH 连接到远程主机(默认命令)") - message.SetString(language.Hindi, "cmd_connect_desc", "SSH के माध्यम से रिमोट होस्ट से कनेक्ट करें (डिफ़ॉल्ट कमांड)") - message.SetString(language.Arabic, "cmd_connect_desc", "الاتصال بمضيف بعيد عبر SSH (الأمر الافتراضي)") - message.SetString(language.Bengali, "cmd_connect_desc", "SSH এর মাধ্যমে রিমোট হোস্টে সংযোগ করুন (ডিফল্ট কমান্ড)") - message.SetString(language.Portuguese, "cmd_connect_desc", "Conectar a um host remoto via SSH (comando padrão)") - message.SetString(language.Russian, "cmd_connect_desc", "Подключиться к удаленному хосту через SSH (команда по умолчанию)") - message.SetString(language.Japanese, "cmd_connect_desc", "SSH経由でリモートホストに接続(デフォルトコマンド)") - message.SetString(language.German, "cmd_connect_desc", "Verbindung zu einem Remote-Host über SSH (Standardbefehl)") - message.SetString(language.French, "cmd_connect_desc", "Se connecter à un hôte distant via SSH (commande par défaut)") - - message.SetString(language.English, "cmd_scp_desc", "Transfer files securely using SCP") - message.SetString(language.Spanish, "cmd_scp_desc", "Transferir archivos de forma segura usando SCP") - message.SetString(language.Chinese, "cmd_scp_desc", "使用 SCP 安全传输文件") - message.SetString(language.Hindi, "cmd_scp_desc", "SCP का उपयोग करके फाइलों को सुरक्षित रूप से स्थानांतरित करें") - message.SetString(language.Arabic, "cmd_scp_desc", "نقل الملفات بأمان باستخدام SCP") - message.SetString(language.Bengali, "cmd_scp_desc", "SCP ব্যবহার করে নিরাপদে ফাইল স্থানান্তর") - message.SetString(language.Portuguese, "cmd_scp_desc", "Transferir arquivos com segurança usando SCP") - message.SetString(language.Russian, "cmd_scp_desc", "Безопасная передача файлов с помощью SCP") - message.SetString(language.Japanese, "cmd_scp_desc", "SCPを使用したセキュアファイル転送") - message.SetString(language.German, "cmd_scp_desc", "Dateien sicher mit SCP übertragen") - message.SetString(language.French, "cmd_scp_desc", "Transférer des fichiers en toute sécurité avec SCP") - - message.SetString(language.English, "cmd_list_desc", "List available Tailscale hosts") - message.SetString(language.Spanish, "cmd_list_desc", "Listar servidores Tailscale disponibles") - message.SetString(language.Chinese, "cmd_list_desc", "列出可用的 Tailscale 主机") - message.SetString(language.Hindi, "cmd_list_desc", "उपलब्ध Tailscale होस्ट की सूची बनाएं") - message.SetString(language.Arabic, "cmd_list_desc", "سرد مضيفي Tailscale المتاحين") - message.SetString(language.Bengali, "cmd_list_desc", "উপলব্ধ Tailscale হোস্টের তালিকা") - message.SetString(language.Portuguese, "cmd_list_desc", "Listar hosts Tailscale disponíveis") - message.SetString(language.Russian, "cmd_list_desc", "Показать доступные Tailscale хосты") - message.SetString(language.Japanese, "cmd_list_desc", "利用可能なTailscaleホストを一覧表示") - message.SetString(language.German, "cmd_list_desc", "Verfügbare Tailscale-Hosts auflisten") - message.SetString(language.French, "cmd_list_desc", "Lister les hôtes Tailscale disponibles") - - message.SetString(language.English, "cmd_exec_desc", "Execute commands on multiple hosts") - message.SetString(language.Spanish, "cmd_exec_desc", "Ejecutar comandos en múltiples servidores") - message.SetString(language.Chinese, "cmd_exec_desc", "在多个主机上执行命令") - message.SetString(language.Hindi, "cmd_exec_desc", "कई होस्ट पर कमांड का कार्यान्वयन") - message.SetString(language.Arabic, "cmd_exec_desc", "تنفيذ الأوامر على عدة مضيفين") - message.SetString(language.Bengali, "cmd_exec_desc", "একাধিক হোস্টে কমান্ড নিষ্পাদন") - message.SetString(language.Portuguese, "cmd_exec_desc", "Executar comandos em vários hosts") - message.SetString(language.Russian, "cmd_exec_desc", "Выполнить команды на нескольких хостах") - message.SetString(language.Japanese, "cmd_exec_desc", "複数のホストでコマンドを実行") - message.SetString(language.German, "cmd_exec_desc", "Befehle auf mehreren Hosts ausführen") - message.SetString(language.French, "cmd_exec_desc", "Exécuter des commandes sur plusieurs hôtes") - - message.SetString(language.English, "cmd_multi_desc", "Multi-host operations with tmux session management") - message.SetString(language.Spanish, "cmd_multi_desc", "Operaciones multi-servidor con gestión de sesiones tmux") - message.SetString(language.Chinese, "cmd_multi_desc", "使用 tmux 会话管理的多主机操作") - message.SetString(language.Hindi, "cmd_multi_desc", "tmux सत्र प्रबंधन के साथ मल्टी-होस्ट ऑपरेशन") - message.SetString(language.Arabic, "cmd_multi_desc", "عمليات متعددة المضيفين مع إدارة جلسات tmux") - message.SetString(language.Bengali, "cmd_multi_desc", "tmux সেশন পরিচালনা সহ মাল্টি-হোস্ট অপারেশন") - message.SetString(language.Portuguese, "cmd_multi_desc", "Operações multi-host com gerenciamento de sessão tmux") - message.SetString(language.Russian, "cmd_multi_desc", "Мульти-хост операции с управлением сессий tmux") - message.SetString(language.Japanese, "cmd_multi_desc", "tmuxセッション管理を伴ったマルチホスト操作") - message.SetString(language.German, "cmd_multi_desc", "Multi-Host-Operationen mit tmux-Sitzungsverwaltung") - message.SetString(language.French, "cmd_multi_desc", "Opérations multi-hôtes avec gestion de session tmux") - - message.SetString(language.English, "cmd_config_desc", "Manage application configuration") - message.SetString(language.Spanish, "cmd_config_desc", "Gestionar configuración de la aplicación") - message.SetString(language.Chinese, "cmd_config_desc", "管理应用程序配置") - message.SetString(language.Hindi, "cmd_config_desc", "एप्लिकेशन कॉन्फ़िगरेशन का प्रबंधन") - message.SetString(language.Arabic, "cmd_config_desc", "إدارة تكوين التطبيق") - message.SetString(language.Bengali, "cmd_config_desc", "অ্যাপ্লিকেশন কনফিগারেশন পরিচালনা") - message.SetString(language.Portuguese, "cmd_config_desc", "Gerenciar configuração da aplicação") - message.SetString(language.Russian, "cmd_config_desc", "Управление конфигурацией приложения") - message.SetString(language.Japanese, "cmd_config_desc", "アプリケーション設定の管理") - message.SetString(language.German, "cmd_config_desc", "Anwendungskonfiguration verwalten") - message.SetString(language.French, "cmd_config_desc", "Gérer la configuration de l'application") - - message.SetString(language.English, "cmd_pqc_desc", "Post-quantum cryptography operations and reporting") - message.SetString(language.Spanish, "cmd_pqc_desc", "Operaciones y reportes de criptografía post-cuántica") - message.SetString(language.Chinese, "cmd_pqc_desc", "后量子密码学操作和报告") - message.SetString(language.Hindi, "cmd_pqc_desc", "पोस्ट-क्वांटम क्रिप्टोग्राफी ऑपरेशन और रिपोर्टिंग") - message.SetString(language.Arabic, "cmd_pqc_desc", "عمليات وتقارير التشفير ما بعد الكمي") - message.SetString(language.Bengali, "cmd_pqc_desc", "পোস্ট-কোয়ান্টাম ক্রিপ্টোগ্রাফি অপারেশন এবং রিপোর্টিং") - message.SetString(language.Portuguese, "cmd_pqc_desc", "Operações e relatórios de criptografia pós-quântica") - message.SetString(language.Russian, "cmd_pqc_desc", "Операции и отчеты постквантовой криптографии") - message.SetString(language.Japanese, "cmd_pqc_desc", "ポスト量子暗号の操作とレポート") - message.SetString(language.German, "cmd_pqc_desc", "Post-Quanten-Kryptographie-Operationen und Berichte") - message.SetString(language.French, "cmd_pqc_desc", "Opérations et rapports de cryptographie post-quantique") - - message.SetString(language.English, "cmd_version_desc", "Show version information") - message.SetString(language.Spanish, "cmd_version_desc", "Mostrar información de versión") - message.SetString(language.Chinese, "cmd_version_desc", "显示版本信息") - message.SetString(language.Hindi, "cmd_version_desc", "वर्जन जानकारी दिखाएं") - message.SetString(language.Arabic, "cmd_version_desc", "عرض معلومات الإصدار") - message.SetString(language.Bengali, "cmd_version_desc", "ভার্সনের তথ্য দেখান") - message.SetString(language.Portuguese, "cmd_version_desc", "Mostrar informações de versão") - message.SetString(language.Russian, "cmd_version_desc", "Показать информацию о версии") - message.SetString(language.Japanese, "cmd_version_desc", "バージョン情報を表示") - message.SetString(language.German, "cmd_version_desc", "Versionsinformationen anzeigen") - message.SetString(language.French, "cmd_version_desc", "Afficher les informations de version") - - // CLI Short and Long descriptions for Cobra/Fang - message.SetString(language.English, "root_short", "SSH client with Tailscale integration") - message.SetString(language.Spanish, "root_short", "Cliente SSH con integración Tailscale") - message.SetString(language.Chinese, "root_short", "具有 Tailscale 集成的 SSH 客户端") - message.SetString(language.Hindi, "root_short", "Tailscale इंटीग्रेशन के साथ SSH क्लाइंट") - message.SetString(language.Arabic, "root_short", "عميل SSH مع تكامل Tailscale") - message.SetString(language.Bengali, "root_short", "Tailscale ইন্টিগ্রেশন সহ SSH ক্লায়েন্ট") - message.SetString(language.Portuguese, "root_short", "Cliente SSH com integração Tailscale") - message.SetString(language.Russian, "root_short", "SSH-клиент с интеграцией Tailscale") - message.SetString(language.Japanese, "root_short", "Tailscale統合SSHクライアント") - message.SetString(language.German, "root_short", "SSH-Client mit Tailscale-Integration") - message.SetString(language.French, "root_short", "Client SSH avec intégration Tailscale") - - message.SetString(language.English, "root_long", "A secure SSH client that works seamlessly with Tailscale networks") - message.SetString(language.Spanish, "root_long", "Un cliente SSH seguro que funciona perfectamente con redes Tailscale") - message.SetString(language.Chinese, "root_long", "与 Tailscale 网络无缝协作的安全 SSH 客户端") - message.SetString(language.Hindi, "root_long", "एक सुरक्षित SSH क्लाइंट जो Tailscale नेटवर्क के साथ बेहतरीन काम करता है") - message.SetString(language.Arabic, "root_long", "عميل SSH آمن يعمل بسلاسة مع شبكات Tailscale") - message.SetString(language.Bengali, "root_long", "একটি নিরাপদ SSH ক্লায়েন্ট যা Tailscale নেটওয়ার্কের সাথে নির্বিঘ্নে কাজ করে") - message.SetString(language.Portuguese, "root_long", "Um cliente SSH seguro que funciona perfeitamente com redes Tailscale") - message.SetString(language.Russian, "root_long", "Безопасный SSH-клиент, который беспрепятственно работает с сетями Tailscale") - message.SetString(language.Japanese, "root_long", "Tailscaleネットワークとシームレスに連携するセキュアSSHクライアント") - message.SetString(language.German, "root_long", "Ein sicherer SSH-Client, der nahtlos mit Tailscale-Netzwerken funktioniert") - message.SetString(language.French, "root_long", "Un client SSH sécurisé qui fonctionne parfaitement avec les réseaux Tailscale") - - message.SetString(language.English, "root_examples", ` # Connect to a host - ts-ssh user@hostname - - # Execute a command - ts-ssh hostname "ls -la" - - # Copy files with SCP - ts-ssh scp local.txt user@host:/remote/path/ - - # List available hosts - ts-ssh list - - # Interactive host selection - ts-ssh list --interactive`) - message.SetString(language.Spanish, "root_examples", ` # Conectar a un servidor - ts-ssh usuario@servidor - - # Ejecutar un comando - ts-ssh servidor "ls -la" - - # Copiar archivos con SCP - ts-ssh scp archivo.txt usuario@servidor:/ruta/remota/ - - # Listar servidores disponibles - ts-ssh list - - # Selección interactiva de servidor - ts-ssh list --interactive`) - message.SetString(language.Chinese, "root_examples", ` # 连接到主机 - ts-ssh 用户@主机名 - - # 执行命令 - ts-ssh 主机名 "ls -la" - - # 使用 SCP 复制文件 - ts-ssh scp 本地文件.txt 用户@主机:/远程/路径/ - - # 列出可用主机 - ts-ssh list - - # 交互式主机选择 - ts-ssh list --interactive`) - message.SetString(language.German, "root_examples", ` # Verbindung zu einem Host - ts-ssh benutzer@hostname - - # Befehl ausführen - ts-ssh hostname "ls -la" - - # Dateien mit SCP kopieren - ts-ssh scp datei.txt benutzer@host:/remote/pfad/ - - # Verfügbare Hosts auflisten - ts-ssh list - - # Interaktive Host-Auswahl - ts-ssh list --interactive`) - message.SetString(language.French, "root_examples", ` # Se connecter à un hôte - ts-ssh utilisateur@hostname - - # Exécuter une commande - ts-ssh hostname "ls -la" - - # Copier des fichiers avec SCP - ts-ssh scp fichier.txt utilisateur@hôte:/chemin/distant/ - - # Lister les hôtes disponibles - ts-ssh list - - # Sélection interactive d'hôte - ts-ssh list --interactive`) - - // Connect command - message.SetString(language.English, "connect_short", "Connect to a host via SSH") - message.SetString(language.Spanish, "connect_short", "Conectar a un servidor via SSH") - message.SetString(language.Chinese, "connect_short", "通过 SSH 连接到主机") - message.SetString(language.Hindi, "connect_short", "SSH के माध्यम से होस्ट से कनेक्ट करें") - message.SetString(language.Arabic, "connect_short", "الاتصال بمضيف عبر SSH") - message.SetString(language.Bengali, "connect_short", "SSH এর মাধ্যমে হোস্টে সংযুক্ত হন") - message.SetString(language.Portuguese, "connect_short", "Conectar a um host via SSH") - message.SetString(language.Russian, "connect_short", "Подключиться к хосту через SSH") - message.SetString(language.Japanese, "connect_short", "SSH経由でホストに接続") - message.SetString(language.German, "connect_short", "Verbindung zu einem Host über SSH") - message.SetString(language.French, "connect_short", "Se connecter à un hôte via SSH") - - message.SetString(language.English, "connect_long", "Establish an SSH connection to a remote host through Tailscale") - message.SetString(language.Spanish, "connect_long", "Establecer una conexión SSH a un servidor remoto a través de Tailscale") - message.SetString(language.Chinese, "connect_long", "通过 Tailscale 建立到远程主机的 SSH 连接") - message.SetString(language.Hindi, "connect_long", "Tailscale के माध्यम से रिमोट होस्ट से SSH कनेक्शन स्थापित करें") - message.SetString(language.Arabic, "connect_long", "إنشاء اتصال SSH إلى مضيف بعيد عبر Tailscale") - message.SetString(language.Bengali, "connect_long", "Tailscale এর মাধ্যমে একটি রিমোট হোস্টে SSH কনেকশন স্থাপন করুন") - message.SetString(language.Portuguese, "connect_long", "Estabelecer uma conexão SSH para um host remoto através do Tailscale") - message.SetString(language.Russian, "connect_long", "Установить SSH-соединение с удаленным хостом через Tailscale") - message.SetString(language.Japanese, "connect_long", "Tailscale経由でリモートホストへのSSH接続を確立") - message.SetString(language.German, "connect_long", "SSH-Verbindung zu einem entfernten Host über Tailscale herstellen") - message.SetString(language.French, "connect_long", "Établir une connexion SSH vers un hôte distant via Tailscale") - - message.SetString(language.English, "connect_examples", ` # Simple connection - ts-ssh connect user@hostname - - # Execute remote command - ts-ssh connect hostname "uptime" - - # Port forwarding - ts-ssh connect -W dest:port hostname`) - message.SetString(language.Chinese, "connect_examples", ` # 简单连接 - ts-ssh connect 用户@主机名 - - # 执行远程命令 - ts-ssh connect 主机名 "uptime" - - # 端口转发 - ts-ssh connect -W 目标:端口 主机名`) - message.SetString(language.German, "connect_examples", ` # Einfache Verbindung - ts-ssh connect benutzer@hostname - - # Remote-Befehl ausführen - ts-ssh connect hostname "uptime" - - # Port-Weiterleitung - ts-ssh connect -W ziel:port hostname`) - message.SetString(language.French, "connect_examples", ` # Connexion simple - ts-ssh connect utilisateur@hostname - - # Exécuter une commande distante - ts-ssh connect hostname "uptime" - - # Redirection de port - ts-ssh connect -W destination:port hostname`) - message.SetString(language.Spanish, "connect_examples", ` # Conexión simple - ts-ssh connect usuario@servidor - - # Ejecutar comando remoto - ts-ssh connect servidor "uptime" - - # Redirección de puertos - ts-ssh connect -W destino:puerto servidor`) - message.SetString(language.Hindi, "connect_examples", ` # सरल कनेक्शन - ts-ssh connect उपयोगकर्ता@होस्टनाम - - # रिमोट कमांड चलाएं - ts-ssh connect होस्टनाम "uptime" - - # पोर्ट फॉरवर्डिंग - ts-ssh connect -W गंतव्य:पोर्ट होस्टनाम`) - message.SetString(language.Arabic, "connect_examples", ` # اتصال بسيط - ts-ssh connect مستخدم@اسم_المضيف - - # تنفيذ أمر عن بُعد - ts-ssh connect اسم_المضيف "uptime" - - # إعادة توجيه المنفذ - ts-ssh connect -W وجهة:منفذ اسم_المضيف`) - message.SetString(language.Bengali, "connect_examples", ` # সরল সংযোগ - ts-ssh connect ব্যবহারকারী@হোস্টনাম - - # দূরবর্তী কমান্ড চালান - ts-ssh connect হোস্টনাম "uptime" - - # পোর্ট ফরওয়ার্ডিং - ts-ssh connect -W গন্তব্য:পোর্ট হোস্টনাম`) - message.SetString(language.Portuguese, "connect_examples", ` # Conexão simples - ts-ssh connect usuário@hostname - - # Executar comando remoto - ts-ssh connect hostname "uptime" - - # Redirecionamento de porta - ts-ssh connect -W destino:porta hostname`) - message.SetString(language.Russian, "connect_examples", ` # Простое подключение - ts-ssh connect пользователь@хост - - # Выполнить удаленную команду - ts-ssh connect хост "uptime" - - # Перенаправление портов - ts-ssh connect -W назначение:порт хост`) - message.SetString(language.Japanese, "connect_examples", ` # シンプルな接続 - ts-ssh connect ユーザー@ホスト名 - - # リモートコマンドの実行 - ts-ssh connect ホスト名 "uptime" - - # ポート転送 - ts-ssh connect -W 宛先:ポート ホスト名`) - - // SCP command - message.SetString(language.English, "scp_short", "Copy files via SCP") - message.SetString(language.Spanish, "scp_short", "Copiar archivos via SCP") - message.SetString(language.Chinese, "scp_short", "通过 SCP 复制文件") - message.SetString(language.Hindi, "scp_short", "SCP के माध्यम से फाइलें कॉपी करें") - message.SetString(language.Arabic, "scp_short", "نسخ الملفات عبر SCP") - message.SetString(language.Bengali, "scp_short", "SCP এর মাধ্যমে ফাইল কপি করুন") - message.SetString(language.Portuguese, "scp_short", "Copiar arquivos via SCP") - message.SetString(language.Russian, "scp_short", "Копировать файлы через SCP") - message.SetString(language.Japanese, "scp_short", "SCP経由でファイルをコピー") - message.SetString(language.German, "scp_short", "Dateien über SCP kopieren") - message.SetString(language.French, "scp_short", "Copier des fichiers via SCP") - - message.SetString(language.English, "scp_long", "Securely copy files between local and remote hosts using SCP protocol") - message.SetString(language.Spanish, "scp_long", "Copiar archivos de forma segura entre hosts locales y remotos usando protocolo SCP") - message.SetString(language.Chinese, "scp_long", "使用 SCP 协议在本地和远程主机之间安全复制文件") - message.SetString(language.Hindi, "scp_long", "SCP प्रोटोकॉल का उपयोग करके स्थानीय और दूरस्थ होस्ट के बीच फाइलों को सुरक्षित रूप से कॉपी करें") - message.SetString(language.Arabic, "scp_long", "نسخ آمن للملفات بين المضيفين المحليين والبعيدين باستخدام بروتوكول SCP") - message.SetString(language.Bengali, "scp_long", "SCP প্রোটোকল ব্যবহার করে স্থানীয় এবং দূরবর্তী হোস্টের মধ্যে নিরাপদে ফাইল কপি করুন") - message.SetString(language.Portuguese, "scp_long", "Copiar arquivos com segurança entre hosts locais e remotos usando protocolo SCP") - message.SetString(language.Russian, "scp_long", "Безопасное копирование файлов между локальными и удаленными хостами с использованием протокола SCP") - message.SetString(language.Japanese, "scp_long", "SCPプロトコルを使用してローカルとリモートホスト間でファイルを安全にコピー") - message.SetString(language.German, "scp_long", "Sichere Übertragung von Dateien zwischen lokalen und entfernten Hosts mit SCP-Protokoll") - message.SetString(language.French, "scp_long", "Copier en toute sécurité des fichiers entre hôtes locaux et distants en utilisant le protocole SCP") - - message.SetString(language.English, "scp_examples", ` # Copy local to remote - ts-ssh scp local.txt user@host:/path/ - - # Copy remote to local - ts-ssh scp user@host:/path/file.txt ./ - - # Recursive copy - ts-ssh scp -r ./directory/ user@host:/path/`) - message.SetString(language.Chinese, "scp_examples", ` # 本地复制到远程 - ts-ssh scp local.txt 用户@主机:/路径/ - - # 远程复制到本地 - ts-ssh scp 用户@主机:/路径/文件.txt ./ - - # 递归复制 - ts-ssh scp -r ./目录/ 用户@主机:/路径/`) - message.SetString(language.German, "scp_examples", ` # Lokal zu Remote kopieren - ts-ssh scp local.txt benutzer@host:/pfad/ - - # Remote zu Lokal kopieren - ts-ssh scp benutzer@host:/pfad/datei.txt ./ - - # Rekursiv kopieren - ts-ssh scp -r ./verzeichnis/ benutzer@host:/pfad/`) - message.SetString(language.French, "scp_examples", ` # Copier local vers distant - ts-ssh scp local.txt utilisateur@hôte:/chemin/ - - # Copier distant vers local - ts-ssh scp utilisateur@hôte:/chemin/fichier.txt ./ - - # Copie récursive - ts-ssh scp -r ./répertoire/ utilisateur@hôte:/chemin/`) - - // List command - message.SetString(language.English, "list_short", "List available hosts") - message.SetString(language.Spanish, "list_short", "Listar hosts disponibles") - message.SetString(language.Chinese, "list_short", "列出可用主机") - message.SetString(language.Hindi, "list_short", "उपलब्ध होस्ट सूची") - message.SetString(language.Arabic, "list_short", "عرض المضيفين المتاحين") - message.SetString(language.Bengali, "list_short", "উপলব্ধ হোস্ট তালিকা") - message.SetString(language.Portuguese, "list_short", "Listar hosts disponíveis") - message.SetString(language.Russian, "list_short", "Список доступных хостов") - message.SetString(language.Japanese, "list_short", "利用可能なホストをリスト") - message.SetString(language.German, "list_short", "Verfügbare Hosts auflisten") - message.SetString(language.French, "list_short", "Lister les hôtes disponibles") - - message.SetString(language.English, "list_long", "Display all available hosts on the Tailscale network") - message.SetString(language.Spanish, "list_long", "Mostrar todos los hosts disponibles en la red Tailscale") - message.SetString(language.Chinese, "list_long", "显示 Tailscale 网络上所有可用的主机") - message.SetString(language.Hindi, "list_long", "Tailscale नेटवर्क पर सभी उपलब्ध होस्ट प्रदर्शित करें") - message.SetString(language.Arabic, "list_long", "عرض جميع المضيفين المتاحين على شبكة Tailscale") - message.SetString(language.Bengali, "list_long", "Tailscale নেটওয়ার্কে সমস্ত উপলব্ধ হোস্ট প্রদর্শন করুন") - message.SetString(language.Portuguese, "list_long", "Exibir todos os hosts disponíveis na rede Tailscale") - message.SetString(language.Russian, "list_long", "Отобразить все доступные хосты в сети Tailscale") - message.SetString(language.Japanese, "list_long", "Tailscaleネットワーク上のすべての利用可能なホストを表示") - message.SetString(language.German, "list_long", "Alle verfügbaren Hosts im Tailscale-Netzwerk anzeigen") - message.SetString(language.French, "list_long", "Afficher tous les hôtes disponibles sur le réseau Tailscale") - - message.SetString(language.English, "list_examples", ` # List all hosts - ts-ssh list - - # Interactive host selection - ts-ssh list --interactive`) - message.SetString(language.Spanish, "list_examples", ` # Listar todos los hosts - ts-ssh list - - # Selección interactiva de hosts - ts-ssh list --interactive`) - message.SetString(language.Chinese, "list_examples", ` # 列出所有主机 - ts-ssh list - - # 交互式主机选择 - ts-ssh list --interactive`) - message.SetString(language.Hindi, "list_examples", ` # सभी होस्ट सूची - ts-ssh list - - # इंटरैक्टिव होस्ट चयन - ts-ssh list --interactive`) - message.SetString(language.Arabic, "list_examples", ` # عرض جميع المضيفين - ts-ssh list - - # اختيار تفاعلي للمضيف - ts-ssh list --interactive`) - message.SetString(language.Bengali, "list_examples", ` # সমস্ত হোস্ট তালিকা - ts-ssh list - - # ইন্টারঅ্যাক্টিভ হোস্ট নির্বাচন - ts-ssh list --interactive`) - message.SetString(language.Portuguese, "list_examples", ` # Listar todos os hosts - ts-ssh list - - # Seleção interativa de hosts - ts-ssh list --interactive`) - message.SetString(language.Russian, "list_examples", ` # Список всех хостов - ts-ssh list - - # Интерактивный выбор хоста - ts-ssh list --interactive`) - message.SetString(language.Japanese, "list_examples", ` # すべてのホストをリスト - ts-ssh list - - # インタラクティブなホスト選択 - ts-ssh list --interactive`) - message.SetString(language.German, "list_examples", ` # Alle Hosts auflisten - ts-ssh list - - # Interaktive Host-Auswahl - ts-ssh list --interactive`) - message.SetString(language.French, "list_examples", ` # Lister tous les hôtes - ts-ssh list - - # Sélection interactive d'hôte - ts-ssh list --interactive`) - - // Exec command - message.SetString(language.English, "exec_short", "Execute commands on multiple hosts") - message.SetString(language.Spanish, "exec_short", "Ejecutar comandos en múltiples hosts") - message.SetString(language.Chinese, "exec_short", "在多个主机上执行命令") - message.SetString(language.Hindi, "exec_short", "कई होस्ट पर कमांड चलाएं") - message.SetString(language.Arabic, "exec_short", "تنفيذ الأوامر على عدة مضيفين") - message.SetString(language.Bengali, "exec_short", "একাধিক হোস্টে কমান্ড চালান") - message.SetString(language.Portuguese, "exec_short", "Executar comandos em múltiplos hosts") - message.SetString(language.Russian, "exec_short", "Выполнить команды на нескольких хостах") - message.SetString(language.Japanese, "exec_short", "複数のホストでコマンド実行") - message.SetString(language.German, "exec_short", "Befehle auf mehreren Hosts ausführen") - message.SetString(language.French, "exec_short", "Exécuter des commandes sur plusieurs hôtes") - - message.SetString(language.English, "exec_long", "Run the same command across multiple hosts simultaneously") - message.SetString(language.Spanish, "exec_long", "Ejecutar el mismo comando en múltiples hosts simultáneamente") - message.SetString(language.Chinese, "exec_long", "同时在多个主机上运行相同的命令") - message.SetString(language.Hindi, "exec_long", "एक साथ कई होस्ट पर एक ही कमांड चलाएं") - message.SetString(language.Arabic, "exec_long", "تشغيل نفس الأمر عبر عدة مضيفين في نفس الوقت") - message.SetString(language.Bengali, "exec_long", "একই সাথে একাধিক হোস্টে একই কমান্ড চালান") - message.SetString(language.Portuguese, "exec_long", "Executar o mesmo comando em múltiplos hosts simultaneamente") - message.SetString(language.Russian, "exec_long", "Запустить одну и ту же команду на нескольких хостах одновременно") - message.SetString(language.Japanese, "exec_long", "複数のホストで同じコマンドを同時に実行") - message.SetString(language.German, "exec_long", "Denselben Befehl gleichzeitig auf mehreren Hosts ausführen") - message.SetString(language.French, "exec_long", "Exécuter la même commande sur plusieurs hôtes simultanément") - - message.SetString(language.English, "exec_examples", ` # Execute on specific hosts - ts-ssh exec host1 host2 -c "uptime" - - # Execute in parallel - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Spanish, "exec_examples", ` # Ejecutar en hosts específicos - ts-ssh exec host1 host2 -c "uptime" - - # Ejecutar en paralelo - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Chinese, "exec_examples", ` # 在特定主机上执行 - ts-ssh exec host1 host2 -c "uptime" - - # 并行执行 - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Hindi, "exec_examples", ` # विशिष्ट होस्ट पर चलाएं - ts-ssh exec host1 host2 -c "uptime" - - # समानांतर में चलाएं - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Arabic, "exec_examples", ` # تنفيذ على مضيفين محددين - ts-ssh exec host1 host2 -c "uptime" - - # تنفيذ متوازي - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Bengali, "exec_examples", ` # নির্দিষ্ট হোস্টে চালান - ts-ssh exec host1 host2 -c "uptime" - - # সমান্তরালে চালান - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Portuguese, "exec_examples", ` # Executar em hosts específicos - ts-ssh exec host1 host2 -c "uptime" - - # Executar em paralelo - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Russian, "exec_examples", ` # Выполнить на конкретных хостах - ts-ssh exec host1 host2 -c "uptime" - - # Выполнить параллельно - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.Japanese, "exec_examples", ` # 特定のホストで実行 - ts-ssh exec host1 host2 -c "uptime" - - # 並列実行 - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.German, "exec_examples", ` # Auf bestimmten Hosts ausführen - ts-ssh exec host1 host2 -c "uptime" - - # Parallel ausführen - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - message.SetString(language.French, "exec_examples", ` # Exécuter sur des hôtes spécifiques - ts-ssh exec host1 host2 -c "uptime" - - # Exécuter en parallèle - ts-ssh exec host1 host2 host3 -c "df -h" --parallel`) - - // Multi command - message.SetString(language.English, "multi_short", "Handle multi-host operations") - message.SetString(language.Spanish, "multi_short", "Manejar operaciones multi-host") - message.SetString(language.Chinese, "multi_short", "处理多主机操作") - message.SetString(language.Hindi, "multi_short", "मल्टी-होस्ट ऑपरेशन्स हैंडल करें") - message.SetString(language.Arabic, "multi_short", "التعامل مع عمليات المضيفين المتعددين") - message.SetString(language.Bengali, "multi_short", "মাল্টি-হোস্ট অপারেশন পরিচালনা") - message.SetString(language.Portuguese, "multi_short", "Gerenciar operações multi-host") - message.SetString(language.Russian, "multi_short", "Обработка операций с несколькими хостами") - message.SetString(language.Japanese, "multi_short", "マルチホスト操作の処理") - message.SetString(language.German, "multi_short", "Multi-Host-Operationen verwalten") - message.SetString(language.French, "multi_short", "Gérer les opérations multi-hôtes") - - message.SetString(language.English, "multi_long", "Manage connections to multiple hosts with advanced session handling") - message.SetString(language.Spanish, "multi_long", "Gestionar conexiones a múltiples hosts con manejo avanzado de sesiones") - message.SetString(language.Chinese, "multi_long", "使用高级会话处理管理到多个主机的连接") - message.SetString(language.Hindi, "multi_long", "उन्नत सेशन हैंडलिंग के साथ कई होस्ट के कनेक्शन प्रबंधित करें") - message.SetString(language.Arabic, "multi_long", "إدارة الاتصالات بعدة مضيفين مع معالجة جلسات متقدمة") - message.SetString(language.Bengali, "multi_long", "উন্নত সেশন হ্যান্ডলিং সহ একাধিক হোস্টে সংযোগ পরিচালনা") - message.SetString(language.Portuguese, "multi_long", "Gerenciar conexões para múltiplos hosts com tratamento avançado de sessões") - message.SetString(language.Russian, "multi_long", "Управление подключениями к нескольким хостам с расширенной обработкой сеансов") - message.SetString(language.Japanese, "multi_long", "高度なセッション処理で複数ホストへの接続を管理") - message.SetString(language.German, "multi_long", "Verbindungen zu mehreren Hosts mit erweiterte Sitzungsbehandlung verwalten") - message.SetString(language.French, "multi_long", "Gérer les connexions à plusieurs hôtes avec gestion avancée de session") - - message.SetString(language.English, "multi_examples", ` # Connect to multiple hosts - ts-ssh multi --hosts "host1,host2,host3" - - # Use tmux for session management - ts-ssh multi --hosts "host1,host2" --tmux`) - message.SetString(language.Spanish, "multi_examples", ` # Conectar a múltiples hosts - ts-ssh multi --hosts "host1,host2,host3" - - # Usar tmux para gestión de sesiones - ts-ssh multi --hosts "host1,host2" --tmux`) - message.SetString(language.Chinese, "multi_examples", ` # 连接到多个主机 - ts-ssh multi --hosts "host1,host2,host3" - - # 使用 tmux 进行会话管理 - ts-ssh multi --hosts "host1,host2" --tmux`) - message.SetString(language.German, "multi_examples", ` # Zu mehreren Hosts verbinden - ts-ssh multi --hosts "host1,host2,host3" - - # Tmux für Sitzungsmanagement verwenden - ts-ssh multi --hosts "host1,host2" --tmux`) - message.SetString(language.French, "multi_examples", ` # Connecter à plusieurs hôtes - ts-ssh multi --hosts "host1,host2,host3" - - # Utiliser tmux pour la gestion de session - ts-ssh multi --hosts "host1,host2" --tmux`) - - // Config command - message.SetString(language.English, "config_short", "Manage configuration") - message.SetString(language.Spanish, "config_short", "Gestionar configuración") - message.SetString(language.Chinese, "config_short", "管理配置") - message.SetString(language.Hindi, "config_short", "कॉन्फ़िगरेशन प्रबंधित करें") - message.SetString(language.Arabic, "config_short", "إدارة التكوين") - message.SetString(language.Bengali, "config_short", "কনফিগারেশন পরিচালনা") - message.SetString(language.Portuguese, "config_short", "Gerenciar configuração") - message.SetString(language.Russian, "config_short", "Управление конфигурацией") - message.SetString(language.Japanese, "config_short", "設定管理") - message.SetString(language.German, "config_short", "Konfiguration verwalten") - message.SetString(language.French, "config_short", "Gérer la configuration") - - message.SetString(language.English, "config_long", "View and modify ts-ssh configuration settings") - message.SetString(language.Spanish, "config_long", "Ver y modificar configuraciones de ts-ssh") - message.SetString(language.Chinese, "config_long", "查看和修改 ts-ssh 配置设置") - message.SetString(language.Hindi, "config_long", "ts-ssh कॉन्फ़िगरेशन सेटिंग्स देखें और संशोधित करें") - message.SetString(language.Arabic, "config_long", "عرض وتعديل إعدادات تكوين ts-ssh") - message.SetString(language.Bengali, "config_long", "ts-ssh কনফিগারেশন সেটিংস দেখুন এবং পরিবর্তন করুন") - message.SetString(language.Portuguese, "config_long", "Visualizar e modificar configurações do ts-ssh") - message.SetString(language.Russian, "config_long", "Просмотр и изменение настроек конфигурации ts-ssh") - message.SetString(language.Japanese, "config_long", "ts-ssh設定の表示と変更") - message.SetString(language.German, "config_long", "ts-ssh Konfigurationseinstellungen anzeigen und ändern") - message.SetString(language.French, "config_long", "Afficher et modifier les paramètres de configuration ts-ssh") - - message.SetString(language.English, "config_examples", ` # Show configuration - ts-ssh config --show - - # Set a value - ts-ssh config --set "user=myuser" - - # Reset to defaults - ts-ssh config --reset`) - message.SetString(language.Spanish, "config_examples", ` # Mostrar configuración - ts-ssh config --show - - # Establecer un valor - ts-ssh config --set "user=myuser" - - # Restaurar valores predeterminados - ts-ssh config --reset`) - message.SetString(language.Chinese, "config_examples", ` # 显示配置 - ts-ssh config --show - - # 设置值 - ts-ssh config --set "user=myuser" - - # 重置为默认值 - ts-ssh config --reset`) - message.SetString(language.German, "config_examples", ` # Konfiguration anzeigen - ts-ssh config --show - - # Einen Wert setzen - ts-ssh config --set "user=myuser" - - # Auf Standardwerte zurücksetzen - ts-ssh config --reset`) - message.SetString(language.French, "config_examples", ` # Afficher la configuration - ts-ssh config --show - - # Définir une valeur - ts-ssh config --set "user=myuser" - - # Réinitialiser aux valeurs par défaut - ts-ssh config --reset`) - - // PQC command - message.SetString(language.English, "pqc_short", "Post-quantum cryptography operations") - message.SetString(language.Spanish, "pqc_short", "Operaciones de criptografía post-cuántica") - message.SetString(language.Chinese, "pqc_short", "后量子密码学操作") - message.SetString(language.Hindi, "pqc_short", "पोस्ट-क्वांटम क्रिप्टोग्राफी ऑपरेशन्स") - message.SetString(language.Arabic, "pqc_short", "عمليات التشفير ما بعد الكمومي") - message.SetString(language.Bengali, "pqc_short", "পোস্ট-কোয়ান্টাম ক্রিপ্টোগ্রাফি অপারেশন") - message.SetString(language.Portuguese, "pqc_short", "Operações de criptografia pós-quântica") - message.SetString(language.Russian, "pqc_short", "Операции постквантовой криптографии") - message.SetString(language.Japanese, "pqc_short", "ポスト量子暗号操作") - message.SetString(language.German, "pqc_short", "Post-Quanten-Kryptographie-Operationen") - message.SetString(language.French, "pqc_short", "Opérations de cryptographie post-quantique") - - message.SetString(language.English, "pqc_long", "Manage and test post-quantum cryptographic features") - message.SetString(language.Spanish, "pqc_long", "Gestionar y probar características de criptografía post-cuántica") - message.SetString(language.Chinese, "pqc_long", "管理和测试后量子密码学特性") - message.SetString(language.Hindi, "pqc_long", "पोस्ट-क्वांटम क्रिप्टोग्राफिक फीचर्स का प्रबंधन और परीक्षण करें") - message.SetString(language.Arabic, "pqc_long", "إدارة واختبار ميزات التشفير ما بعد الكمومي") - message.SetString(language.Bengali, "pqc_long", "পোস্ট-কোয়ান্টাম ক্রিপ্টোগ্রাফিক বৈশিষ্ট্য পরিচালনা এবং পরীক্ষা") - message.SetString(language.Portuguese, "pqc_long", "Gerenciar e testar recursos de criptografia pós-quântica") - message.SetString(language.Russian, "pqc_long", "Управление и тестирование возможностей постквантовой криптографии") - message.SetString(language.Japanese, "pqc_long", "ポスト量子暗号機能の管理とテスト") - message.SetString(language.German, "pqc_long", "Post-Quanten-Kryptographie-Features verwalten und testen") - message.SetString(language.French, "pqc_long", "Gérer et tester les fonctionnalités de cryptographie post-quantique") - - message.SetString(language.English, "pqc_examples", ` # Show PQC status - ts-ssh pqc - - # Generate report - ts-ssh pqc --report - - # Run benchmarks - ts-ssh pqc --benchmark`) - message.SetString(language.Spanish, "pqc_examples", ` # Mostrar estado PQC - ts-ssh pqc - - # Generar informe - ts-ssh pqc --report - - # Ejecutar benchmarks - ts-ssh pqc --benchmark`) - message.SetString(language.Chinese, "pqc_examples", ` # 显示 PQC 状态 - ts-ssh pqc - - # 生成报告 - ts-ssh pqc --report - - # 运行基准测试 - ts-ssh pqc --benchmark`) - message.SetString(language.German, "pqc_examples", ` # PQC-Status anzeigen - ts-ssh pqc - - # Bericht generieren - ts-ssh pqc --report - - # Benchmarks ausführen - ts-ssh pqc --benchmark`) - message.SetString(language.French, "pqc_examples", ` # Afficher le statut PQC - ts-ssh pqc - - # Générer un rapport - ts-ssh pqc --report - - # Exécuter des benchmarks - ts-ssh pqc --benchmark`) - - // Version command - message.SetString(language.English, "version_short", "Show version information") - message.SetString(language.Spanish, "version_short", "Mostrar información de versión") - message.SetString(language.Chinese, "version_short", "显示版本信息") - message.SetString(language.Hindi, "version_short", "संस्करण जानकारी दिखाएं") - message.SetString(language.Arabic, "version_short", "عرض معلومات الإصدار") - message.SetString(language.Bengali, "version_short", "সংস্করণ তথ্য দেখান") - message.SetString(language.Portuguese, "version_short", "Mostrar informações da versão") - message.SetString(language.Russian, "version_short", "Показать информацию о версии") - message.SetString(language.Japanese, "version_short", "バージョン情報を表示") - message.SetString(language.German, "version_short", "Versionsinformationen anzeigen") - message.SetString(language.French, "version_short", "Afficher les informations de version") - - message.SetString(language.English, "version_long", "Display version and build information for ts-ssh") - message.SetString(language.Spanish, "version_long", "Mostrar información de versión y compilación para ts-ssh") - message.SetString(language.Chinese, "version_long", "显示 ts-ssh 的版本和构建信息") - message.SetString(language.Hindi, "version_long", "ts-ssh के लिए संस्करण और बिल्ड जानकारी प्रदर्शित करें") - message.SetString(language.Arabic, "version_long", "عرض معلومات الإصدار والبناء لـ ts-ssh") - message.SetString(language.Bengali, "version_long", "ts-ssh এর জন্য সংস্করণ এবং বিল্ড তথ্য প্রদর্শন") - message.SetString(language.Portuguese, "version_long", "Exibir informações de versão e build para ts-ssh") - message.SetString(language.Russian, "version_long", "Отобразить информацию о версии и сборке для ts-ssh") - message.SetString(language.Japanese, "version_long", "ts-sshのバージョンとビルド情報を表示") - message.SetString(language.German, "version_long", "Versions- und Build-Informationen für ts-ssh anzeigen") - message.SetString(language.French, "version_long", "Afficher les informations de version et de build pour ts-ssh") - - // Flag descriptions - message.SetString(language.English, "flag_user_help", "SSH username for connection") - message.SetString(language.Spanish, "flag_user_help", "Nombre de usuario SSH para la conexión") - message.SetString(language.Chinese, "flag_user_help", "连接用的 SSH 用户名") - message.SetString(language.Hindi, "flag_user_help", "कनेक्शन के लिए SSH यूज़रनेम") - message.SetString(language.Arabic, "flag_user_help", "اسم مستخدم SSH للاتصال") - message.SetString(language.Bengali, "flag_user_help", "কনেকশনের জন্য SSH ব্যবহারকারীর নাম") - message.SetString(language.Portuguese, "flag_user_help", "Nome de usuário SSH para conexão") - message.SetString(language.Russian, "flag_user_help", "Имя пользователя SSH для подключения") - message.SetString(language.Japanese, "flag_user_help", "接続用SSHユーザー名") - message.SetString(language.German, "flag_user_help", "SSH-Benutzername für Verbindung") - message.SetString(language.French, "flag_user_help", "Nom d'utilisateur SSH pour la connexion") - - message.SetString(language.English, "flag_identity_help", "Path to SSH private key file") - message.SetString(language.Spanish, "flag_identity_help", "Ruta al archivo de clave privada SSH") - message.SetString(language.Chinese, "flag_identity_help", "SSH 私钥文件路径") - message.SetString(language.Hindi, "flag_identity_help", "SSH प्राइवेट की फाइल का पाथ") - message.SetString(language.Arabic, "flag_identity_help", "مسار ملف مفتاح SSH الخاص") - message.SetString(language.Bengali, "flag_identity_help", "SSH প্রাইভেট কী ফাইলের পথ") - message.SetString(language.Portuguese, "flag_identity_help", "Caminho para arquivo de chave privada SSH") - message.SetString(language.Russian, "flag_identity_help", "Путь к файлу частного ключа SSH") - message.SetString(language.Japanese, "flag_identity_help", "SSH秘密鍵ファイルのパス") - message.SetString(language.German, "flag_identity_help", "Pfad zur SSH-Private-Key-Datei") - message.SetString(language.French, "flag_identity_help", "Chemin vers le fichier de clé privée SSH") - - message.SetString(language.English, "flag_lang_help", "Set language for output (en, es, fr, de, etc.)") - message.SetString(language.Spanish, "flag_lang_help", "Establecer idioma para la salida (en, es, fr, de, etc.)") - message.SetString(language.Chinese, "flag_lang_help", "设置输出语言 (en, es, fr, de, 等)") - message.SetString(language.Hindi, "flag_lang_help", "आउटपुट के लिए भाषा सेट करें (en, es, fr, de, आदि)") - message.SetString(language.Arabic, "flag_lang_help", "تعيين لغة الإخراج (en, es, fr, de, إلخ)") - message.SetString(language.Bengali, "flag_lang_help", "আউটপুটের জন্য ভাষা সেট করুন (en, es, fr, de, ইত্যাদি)") - message.SetString(language.Portuguese, "flag_lang_help", "Definir idioma para saída (en, es, fr, de, etc.)") - message.SetString(language.Russian, "flag_lang_help", "Установить язык вывода (en, es, fr, de, и т.д.)") - message.SetString(language.Japanese, "flag_lang_help", "出力言語を設定 (en, es, fr, de, など)") - message.SetString(language.German, "flag_lang_help", "Sprache für Ausgabe festlegen (en, es, fr, de, usw.)") - message.SetString(language.French, "flag_lang_help", "Définir la langue de sortie (en, es, fr, de, etc.)") - - // Additional flag descriptions - message.SetString(language.English, "flag_config_help", "SSH config file path") - message.SetString(language.Spanish, "flag_config_help", "Ruta del archivo de configuración SSH") - message.SetString(language.Chinese, "flag_config_help", "SSH 配置文件路径") - message.SetString(language.German, "flag_config_help", "SSH-Konfigurationsdateipfad") - message.SetString(language.French, "flag_config_help", "Chemin du fichier de configuration SSH") - - message.SetString(language.English, "flag_tsnet_help", "Directory for tsnet state and logs") - message.SetString(language.Spanish, "flag_tsnet_help", "Directorio para estado y logs de tsnet") - message.SetString(language.Chinese, "flag_tsnet_help", "tsnet 状态和日志目录") - message.SetString(language.German, "flag_tsnet_help", "Verzeichnis für tsnet-Status und -Logs") - message.SetString(language.French, "flag_tsnet_help", "Répertoire pour l'état et les journaux tsnet") - - message.SetString(language.English, "flag_control_help", "Tailscale control server URL") - message.SetString(language.Spanish, "flag_control_help", "URL del servidor de control Tailscale") - message.SetString(language.Chinese, "flag_control_help", "Tailscale 控制服务器 URL") - message.SetString(language.German, "flag_control_help", "Tailscale-Kontrollserver-URL") - message.SetString(language.French, "flag_control_help", "URL du serveur de contrôle Tailscale") - - message.SetString(language.English, "flag_verbose_help", "Enable verbose logging") - message.SetString(language.Spanish, "flag_verbose_help", "Habilitar logging detallado") - message.SetString(language.Chinese, "flag_verbose_help", "启用详细日志") - message.SetString(language.German, "flag_verbose_help", "Ausführliche Protokollierung aktivieren") - message.SetString(language.French, "flag_verbose_help", "Activer la journalisation détaillée") - - message.SetString(language.English, "flag_insecure_help", "Skip host key verification (insecure)") - message.SetString(language.Spanish, "flag_insecure_help", "Omitir verificación de clave del servidor (inseguro)") - message.SetString(language.Chinese, "flag_insecure_help", "跳过主机密钥验证(不安全)") - message.SetString(language.German, "flag_insecure_help", "Host-Schlüssel-Verifikation überspringen (unsicher)") - message.SetString(language.French, "flag_insecure_help", "Ignorer la vérification de clé d'hôte (non sécurisé)") - - message.SetString(language.English, "flag_force_insecure_help", "Force insecure mode without confirmation") - message.SetString(language.Spanish, "flag_force_insecure_help", "Forzar modo inseguro sin confirmación") - message.SetString(language.Chinese, "flag_force_insecure_help", "强制不安全模式而不确认") - message.SetString(language.German, "flag_force_insecure_help", "Unsicheren Modus ohne Bestätigung erzwingen") - message.SetString(language.French, "flag_force_insecure_help", "Forcer le mode non sécurisé sans confirmation") - - message.SetString(language.English, "flag_pqc_help", "Enable post-quantum cryptography") - message.SetString(language.Spanish, "flag_pqc_help", "Habilitar criptografía post-cuántica") - message.SetString(language.Chinese, "flag_pqc_help", "启用后量子密码学") - message.SetString(language.German, "flag_pqc_help", "Post-Quanten-Kryptographie aktivieren") - message.SetString(language.French, "flag_pqc_help", "Activer la cryptographie post-quantique") - - message.SetString(language.English, "flag_pqc_level_help", "PQC level: 0=none, 1=hybrid, 2=strict") - message.SetString(language.Spanish, "flag_pqc_level_help", "Nivel PQC: 0=ninguno, 1=híbrido, 2=estricto") - message.SetString(language.Chinese, "flag_pqc_level_help", "PQC 级别: 0=无, 1=混合, 2=严格") - message.SetString(language.German, "flag_pqc_level_help", "PQC-Level: 0=keine, 1=hybrid, 2=strikt") - message.SetString(language.French, "flag_pqc_level_help", "Niveau PQC: 0=aucun, 1=hybride, 2=strict") - - // Connection status messages - message.SetString(language.English, "starting_tailscale_connection", "Starting Tailscale connection...") - message.SetString(language.Spanish, "starting_tailscale_connection", "Iniciando conexión Tailscale...") - message.SetString(language.Chinese, "starting_tailscale_connection", "正在启动 Tailscale 连接...") - message.SetString(language.Hindi, "starting_tailscale_connection", "Tailscale कनेक्शन शुरू कर रहे हैं...") - message.SetString(language.Arabic, "starting_tailscale_connection", "بدء الاتصال بـ Tailscale...") - message.SetString(language.Bengali, "starting_tailscale_connection", "Tailscale সংযোগ শুরু করা হচ্ছে...") - message.SetString(language.Portuguese, "starting_tailscale_connection", "Iniciando conexão Tailscale...") - message.SetString(language.Russian, "starting_tailscale_connection", "Запуск подключения Tailscale...") - message.SetString(language.Japanese, "starting_tailscale_connection", "Tailscale接続を開始しています...") - message.SetString(language.German, "starting_tailscale_connection", "Tailscale-Verbindung wird gestartet...") - message.SetString(language.French, "starting_tailscale_connection", "Démarrage de la connexion Tailscale...") - - message.SetString(language.English, "to_authenticate_visit", "To authenticate, visit:") - message.SetString(language.Spanish, "to_authenticate_visit", "Para autenticarse, visite:") - message.SetString(language.Chinese, "to_authenticate_visit", "要进行身份验证,请访问:") - message.SetString(language.Hindi, "to_authenticate_visit", "प्रमाणीकरण के लिए, यहाँ जाएँ:") - message.SetString(language.Arabic, "to_authenticate_visit", "للمصادقة، تفضل بزيارة:") - message.SetString(language.Bengali, "to_authenticate_visit", "প্রমাণীকরণের জন্য, এখানে যান:") - message.SetString(language.Portuguese, "to_authenticate_visit", "Para autenticar, visite:") - message.SetString(language.Russian, "to_authenticate_visit", "Для аутентификации перейдите по адресу:") - message.SetString(language.Japanese, "to_authenticate_visit", "認証するには、次のアドレスにアクセスしてください:") - message.SetString(language.German, "to_authenticate_visit", "Zur Authentifizierung besuchen Sie:") - message.SetString(language.French, "to_authenticate_visit", "Pour vous authentifier, visitez:") - - // Error messages - message.SetString(language.English, "target_hostname_required", "target hostname required") - message.SetString(language.Spanish, "target_hostname_required", "se requiere nombre de host de destino") - message.SetString(language.Chinese, "target_hostname_required", "需要目标主机名") - message.SetString(language.Hindi, "target_hostname_required", "लक्ष्य होस्टनाम आवश्यक है") - message.SetString(language.Arabic, "target_hostname_required", "اسم المضيف المستهدف مطلوب") - message.SetString(language.Bengali, "target_hostname_required", "লক্ষ্য হোস্টনাম প্রয়োজন") - message.SetString(language.Portuguese, "target_hostname_required", "nome do host de destino obrigatório") - message.SetString(language.Russian, "target_hostname_required", "требуется имя целевого хоста") - message.SetString(language.Japanese, "target_hostname_required", "ターゲットホスト名が必要です") - message.SetString(language.German, "target_hostname_required", "Ziel-Hostname erforderlich") - message.SetString(language.French, "target_hostname_required", "nom d'hôte cible requis") - - message.SetString(language.English, "failed_to_apply_defaults", "failed to apply defaults") - message.SetString(language.Spanish, "failed_to_apply_defaults", "error al aplicar valores predeterminados") - message.SetString(language.Chinese, "failed_to_apply_defaults", "应用默认值失败") - message.SetString(language.Hindi, "failed_to_apply_defaults", "डिफ़ॉल्ट लागू करने में विफल") - message.SetString(language.Arabic, "failed_to_apply_defaults", "فشل في تطبيق القيم الافتراضية") - message.SetString(language.Bengali, "failed_to_apply_defaults", "ডিফল্ট প্রয়োগ করতে ব্যর্থ") - message.SetString(language.Portuguese, "failed_to_apply_defaults", "falha ao aplicar padrões") - message.SetString(language.Russian, "failed_to_apply_defaults", "не удалось применить значения по умолчанию") - message.SetString(language.Japanese, "failed_to_apply_defaults", "デフォルトの適用に失敗しました") - message.SetString(language.German, "failed_to_apply_defaults", "Anwenden der Standardwerte fehlgeschlagen") - message.SetString(language.French, "failed_to_apply_defaults", "échec de l'application des valeurs par défaut") - - message.SetString(language.English, "no_host_selected", "no host selected") - message.SetString(language.Spanish, "no_host_selected", "ningún host seleccionado") - message.SetString(language.Chinese, "no_host_selected", "未选择主机") - message.SetString(language.Hindi, "no_host_selected", "कोई होस्ट चयनित नहीं") - message.SetString(language.Arabic, "no_host_selected", "لم يتم اختيار مضيف") - message.SetString(language.Bengali, "no_host_selected", "কোন হোস্ট নির্বাচিত নয়") - message.SetString(language.Portuguese, "no_host_selected", "nenhum host selecionado") - message.SetString(language.Russian, "no_host_selected", "хост не выбран") - message.SetString(language.Japanese, "no_host_selected", "ホストが選択されていません") - message.SetString(language.German, "no_host_selected", "kein Host ausgewählt") - message.SetString(language.French, "no_host_selected", "aucun hôte sélectionné") - - // Security TTY messages - message.SetString(language.English, "tty_path_validation_failed", "TTY path validation failed") - message.SetString(language.Spanish, "tty_path_validation_failed", "Error en la validación de la ruta TTY") - message.SetString(language.Chinese, "tty_path_validation_failed", "TTY路径验证失败") - message.SetString(language.Hindi, "tty_path_validation_failed", "TTY पथ सत्यापन विफल") - message.SetString(language.Arabic, "tty_path_validation_failed", "فشل التحقق من مسار TTY") - message.SetString(language.Bengali, "tty_path_validation_failed", "TTY পথ যাচাইকরণ ব্যর্থ") - message.SetString(language.Portuguese, "tty_path_validation_failed", "Falha na validação do caminho TTY") - message.SetString(language.Russian, "tty_path_validation_failed", "Ошибка проверки пути TTY") - message.SetString(language.Japanese, "tty_path_validation_failed", "TTYパスの検証に失敗しました") - message.SetString(language.German, "tty_path_validation_failed", "TTY-Pfad-Validierung fehlgeschlagen") - message.SetString(language.French, "tty_path_validation_failed", "échec de validation du chemin TTY") - - message.SetString(language.English, "tty_ownership_check_failed", "TTY ownership check failed") - message.SetString(language.Spanish, "tty_ownership_check_failed", "Error en la verificación de propiedad TTY") - message.SetString(language.Chinese, "tty_ownership_check_failed", "TTY所有权检查失败") - message.SetString(language.Hindi, "tty_ownership_check_failed", "TTY स्वामित्व जाँच विफल") - message.SetString(language.Arabic, "tty_ownership_check_failed", "فشل فحص ملكية TTY") - message.SetString(language.Bengali, "tty_ownership_check_failed", "TTY মালিকানা পরীক্ষা ব্যর্থ") - message.SetString(language.Portuguese, "tty_ownership_check_failed", "Falha na verificação de propriedade TTY") - message.SetString(language.Russian, "tty_ownership_check_failed", "Ошибка проверки владения TTY") - message.SetString(language.Japanese, "tty_ownership_check_failed", "TTY所有権チェックに失敗しました") - message.SetString(language.German, "tty_ownership_check_failed", "TTY-Eigentümerschaftsprüfung fehlgeschlagen") - message.SetString(language.French, "tty_ownership_check_failed", "échec de vérification de propriété TTY") - - message.SetString(language.English, "tty_permission_check_failed", "TTY permission check failed") - message.SetString(language.Spanish, "tty_permission_check_failed", "Error en la verificación de permisos TTY") - message.SetString(language.Chinese, "tty_permission_check_failed", "TTY权限检查失败") - message.SetString(language.Hindi, "tty_permission_check_failed", "TTY अनुमति जाँच विफल") - message.SetString(language.Arabic, "tty_permission_check_failed", "فشل فحص صلاحية TTY") - message.SetString(language.Bengali, "tty_permission_check_failed", "TTY অনুমতি পরীক্ষা ব্যর্থ") - message.SetString(language.Portuguese, "tty_permission_check_failed", "Falha na verificação de permissão TTY") - message.SetString(language.Russian, "tty_permission_check_failed", "Ошибка проверки разрешений TTY") - message.SetString(language.Japanese, "tty_permission_check_failed", "TTY権限チェックに失敗しました") - message.SetString(language.German, "tty_permission_check_failed", "TTY-Berechtigungsprüfung fehlgeschlagen") - message.SetString(language.French, "tty_permission_check_failed", "échec de vérification des permissions TTY") - - message.SetString(language.English, "not_running_in_terminal", "not running in terminal") - message.SetString(language.Spanish, "not_running_in_terminal", "no se ejecuta en terminal") - message.SetString(language.Chinese, "not_running_in_terminal", "未在终端中运行") - message.SetString(language.Hindi, "not_running_in_terminal", "टर्मिनल में नहीं चल रहा") - message.SetString(language.Arabic, "not_running_in_terminal", "لا يعمل في المحطة الطرفية") - message.SetString(language.Bengali, "not_running_in_terminal", "টার্মিনালে চলছে না") - message.SetString(language.Portuguese, "not_running_in_terminal", "não está executando no terminal") - message.SetString(language.Russian, "not_running_in_terminal", "не работает в терминале") - message.SetString(language.Japanese, "not_running_in_terminal", "ターミナルで実行されていません") - message.SetString(language.German, "not_running_in_terminal", "läuft nicht im Terminal") - message.SetString(language.French, "not_running_in_terminal", "ne fonctionne pas dans le terminal") - - message.SetString(language.English, "tty_security_validation_failed", "TTY security validation failed") - message.SetString(language.Spanish, "tty_security_validation_failed", "Error en la validación de seguridad TTY") - message.SetString(language.Chinese, "tty_security_validation_failed", "TTY安全验证失败") - message.SetString(language.Hindi, "tty_security_validation_failed", "TTY सुरक्षा सत्यापन विफल") - message.SetString(language.Arabic, "tty_security_validation_failed", "فشل التحقق من أمان TTY") - message.SetString(language.Bengali, "tty_security_validation_failed", "TTY নিরাপত্তা যাচাইকরণ ব্যর্থ") - message.SetString(language.Portuguese, "tty_security_validation_failed", "Falha na validação de segurança TTY") - message.SetString(language.Russian, "tty_security_validation_failed", "Ошибка проверки безопасности TTY") - message.SetString(language.Japanese, "tty_security_validation_failed", "TTYセキュリティ検証に失敗しました") - message.SetString(language.German, "tty_security_validation_failed", "TTY-Sicherheitsvalidierung fehlgeschlagen") - message.SetString(language.French, "tty_security_validation_failed", "échec de validation de sécurité TTY") - - message.SetString(language.English, "failed_open_tty", "failed to open TTY") - message.SetString(language.Spanish, "failed_open_tty", "error al abrir TTY") - message.SetString(language.Chinese, "failed_open_tty", "无法打开TTY") - message.SetString(language.Hindi, "failed_open_tty", "TTY खोलने में विफल") - message.SetString(language.Arabic, "failed_open_tty", "فشل فتح TTY") - message.SetString(language.Bengali, "failed_open_tty", "TTY খুলতে ব্যর্থ") - message.SetString(language.Portuguese, "failed_open_tty", "falha ao abrir TTY") - message.SetString(language.Russian, "failed_open_tty", "не удалось открыть TTY") - message.SetString(language.Japanese, "failed_open_tty", "TTYを開くことができませんでした") - message.SetString(language.German, "failed_open_tty", "TTY konnte nicht geöffnet werden") - message.SetString(language.French, "failed_open_tty", "échec d'ouverture TTY") - - // SSH connection messages - message.SetString(language.English, "host_key_warning", "WARNING: Host key verification is disabled") - message.SetString(language.Spanish, "host_key_warning", "ADVERTENCIA: La verificación de clave de host está deshabilitada") - message.SetString(language.Chinese, "host_key_warning", "警告:主机密钥验证已禁用") - message.SetString(language.Hindi, "host_key_warning", "चेतावनी: होस्ट की सत्यापन अक्षम है") - message.SetString(language.Arabic, "host_key_warning", "تحذير: تحقق مفتاح المضيف معطل") - message.SetString(language.Bengali, "host_key_warning", "সতর্কতা: হোস্ট কী যাচাইকরণ অক্ষম") - message.SetString(language.Portuguese, "host_key_warning", "AVISO: A verificação da chave do host está desabilitada") - message.SetString(language.Russian, "host_key_warning", "ПРЕДУПРЕЖДЕНИЕ: Проверка ключа хоста отключена") - message.SetString(language.Japanese, "host_key_warning", "警告:ホストキーの検証が無効になっています") - message.SetString(language.German, "host_key_warning", "WARNUNG: Host-Schlüssel-Überprüfung ist deaktiviert") - message.SetString(language.French, "host_key_warning", "AVERTISSEMENT : La vérification de la clé d'hôte est désactivée") - - message.SetString(language.English, "dial_via_tsnet", "Connecting via tsnet...") - message.SetString(language.Spanish, "dial_via_tsnet", "Conectando vía tsnet...") - message.SetString(language.Chinese, "dial_via_tsnet", "通过tsnet连接中...") - message.SetString(language.Hindi, "dial_via_tsnet", "tsnet के माध्यम से कनेक्ट हो रहा है...") - message.SetString(language.Arabic, "dial_via_tsnet", "الاتصال عبر tsnet...") - message.SetString(language.Bengali, "dial_via_tsnet", "tsnet এর মাধ্যমে সংযোগ করা হচ্ছে...") - message.SetString(language.Portuguese, "dial_via_tsnet", "Conectando via tsnet...") - message.SetString(language.Russian, "dial_via_tsnet", "Подключение через tsnet...") - message.SetString(language.Japanese, "dial_via_tsnet", "tsnet経由で接続中...") - message.SetString(language.German, "dial_via_tsnet", "Verbindung über tsnet...") - message.SetString(language.French, "dial_via_tsnet", "Connexion via tsnet...") - - message.SetString(language.English, "ssh_handshake", "Performing SSH handshake...") - message.SetString(language.Spanish, "ssh_handshake", "Realizando protocolo SSH...") - message.SetString(language.Chinese, "ssh_handshake", "正在执行SSH握手...") - message.SetString(language.Hindi, "ssh_handshake", "SSH हैंडशेक कर रहा है...") - message.SetString(language.Arabic, "ssh_handshake", "إجراء مصافحة SSH...") - message.SetString(language.Bengali, "ssh_handshake", "SSH হ্যান্ডশেক সম্পাদন করা হচ্ছে...") - message.SetString(language.Portuguese, "ssh_handshake", "Realizando handshake SSH...") - message.SetString(language.Russian, "ssh_handshake", "Выполнение рукопожатия SSH...") - message.SetString(language.Japanese, "ssh_handshake", "SSHハンドシェイクを実行中...") - message.SetString(language.German, "ssh_handshake", "SSH-Handshake wird durchgeführt...") - message.SetString(language.French, "ssh_handshake", "Exécution de la poignée de main SSH...") - - message.SetString(language.English, "dial_failed", "connection failed") - message.SetString(language.Spanish, "dial_failed", "conexión falló") - message.SetString(language.Chinese, "dial_failed", "连接失败") - message.SetString(language.Hindi, "dial_failed", "कनेक्शन विफल") - message.SetString(language.Arabic, "dial_failed", "فشل الاتصال") - message.SetString(language.Bengali, "dial_failed", "সংযোগ ব্যর্থ") - message.SetString(language.Portuguese, "dial_failed", "conexão falhou") - message.SetString(language.Russian, "dial_failed", "соединение не удалось") - message.SetString(language.Japanese, "dial_failed", "接続に失敗しました") - message.SetString(language.German, "dial_failed", "Verbindung fehlgeschlagen") - message.SetString(language.French, "dial_failed", "échec de connexion") - - message.SetString(language.English, "ssh_connection_failed", "SSH connection failed") - message.SetString(language.Spanish, "ssh_connection_failed", "Conexión SSH falló") - message.SetString(language.Chinese, "ssh_connection_failed", "SSH连接失败") - message.SetString(language.Hindi, "ssh_connection_failed", "SSH कनेक्शन विफल") - message.SetString(language.Arabic, "ssh_connection_failed", "فشل اتصال SSH") - message.SetString(language.Bengali, "ssh_connection_failed", "SSH সংযোগ ব্যর্থ") - message.SetString(language.Portuguese, "ssh_connection_failed", "Conexão SSH falhou") - message.SetString(language.Russian, "ssh_connection_failed", "SSH соединение не удалось") - message.SetString(language.Japanese, "ssh_connection_failed", "SSH接続に失敗しました") - message.SetString(language.German, "ssh_connection_failed", "SSH-Verbindung fehlgeschlagen") - message.SetString(language.French, "ssh_connection_failed", "échec de connexion SSH") - - message.SetString(language.English, "ssh_connection_established", "SSH connection established") - message.SetString(language.Spanish, "ssh_connection_established", "Conexión SSH establecida") - message.SetString(language.Chinese, "ssh_connection_established", "SSH连接已建立") - message.SetString(language.Hindi, "ssh_connection_established", "SSH कनेक्शन स्थापित") - message.SetString(language.Arabic, "ssh_connection_established", "تم تأسيس اتصال SSH") - message.SetString(language.Bengali, "ssh_connection_established", "SSH সংযোগ প্রতিষ্ঠিত") - message.SetString(language.Portuguese, "ssh_connection_established", "Conexão SSH estabelecida") - message.SetString(language.Russian, "ssh_connection_established", "SSH соединение установлено") - message.SetString(language.Japanese, "ssh_connection_established", "SSH接続が確立されました") - message.SetString(language.German, "ssh_connection_established", "SSH-Verbindung hergestellt") - message.SetString(language.French, "ssh_connection_established", "Connexion SSH établie") - - // SCP operation messages - message.SetString(language.English, "scp_empty_path", "SCP path cannot be empty") - message.SetString(language.Spanish, "scp_empty_path", "La ruta SCP no puede estar vacía") - message.SetString(language.Chinese, "scp_empty_path", "SCP路径不能为空") - message.SetString(language.Hindi, "scp_empty_path", "SCP पथ खाली नहीं हो सकता") - message.SetString(language.Arabic, "scp_empty_path", "مسار SCP لا يمكن أن يكون فارغاً") - message.SetString(language.Bengali, "scp_empty_path", "SCP পথ খালি থাকতে পারে না") - message.SetString(language.Portuguese, "scp_empty_path", "O caminho SCP não pode estar vazio") - message.SetString(language.Russian, "scp_empty_path", "Путь SCP не может быть пустым") - message.SetString(language.Japanese, "scp_empty_path", "SCPパスは空にできません") - message.SetString(language.German, "scp_empty_path", "SCP-Pfad darf nicht leer sein") - message.SetString(language.French, "scp_empty_path", "Le chemin SCP ne peut pas être vide") - - message.SetString(language.English, "scp_enter_password", "Enter password for %s@%s: ") - message.SetString(language.Spanish, "scp_enter_password", "Ingrese contraseña para %s@%s: ") - message.SetString(language.Chinese, "scp_enter_password", "为 %s@%s 输入密码: ") - message.SetString(language.Hindi, "scp_enter_password", "%s@%s के लिए पासवर्ड दर्ज करें: ") - message.SetString(language.Arabic, "scp_enter_password", "أدخل كلمة المرور لـ %s@%s: ") - message.SetString(language.Bengali, "scp_enter_password", "%s@%s এর জন্য পাসওয়ার্ড লিখুন: ") - message.SetString(language.Portuguese, "scp_enter_password", "Digite a senha para %s@%s: ") - message.SetString(language.Russian, "scp_enter_password", "Введите пароль для %s@%s: ") - message.SetString(language.Japanese, "scp_enter_password", "%s@%s のパスワードを入力してください: ") - message.SetString(language.German, "scp_enter_password", "Passwort für %s@%s eingeben: ") - message.SetString(language.French, "scp_enter_password", "Entrez le mot de passe pour %s@%s: ") - - message.SetString(language.English, "scp_host_key_warning", "WARNING: SCP host key verification disabled") - message.SetString(language.Spanish, "scp_host_key_warning", "ADVERTENCIA: Verificación de clave de host SCP deshabilitada") - message.SetString(language.Chinese, "scp_host_key_warning", "警告:SCP主机密钥验证已禁用") - message.SetString(language.Hindi, "scp_host_key_warning", "चेतावनी: SCP होस्ट की सत्यापन अक्षम") - message.SetString(language.Arabic, "scp_host_key_warning", "تحذير: تحقق مفتاح مضيف SCP معطل") - message.SetString(language.Bengali, "scp_host_key_warning", "সতর্কতা: SCP হোস্ট কী যাচাইকরণ অক্ষম") - message.SetString(language.Portuguese, "scp_host_key_warning", "AVISO: Verificação de chave de host SCP desabilitada") - message.SetString(language.Russian, "scp_host_key_warning", "ПРЕДУПРЕЖДЕНИЕ: Проверка ключа хоста SCP отключена") - message.SetString(language.Japanese, "scp_host_key_warning", "警告:SCPホストキーの検証が無効になっています") - message.SetString(language.German, "scp_host_key_warning", "WARNUNG: SCP-Host-Schlüssel-Überprüfung ist deaktiviert") - message.SetString(language.French, "scp_host_key_warning", "AVERTISSEMENT : La vérification de la clé d'hôte SCP est désactivée") - - message.SetString(language.English, "scp_upload_complete", "Upload complete") - message.SetString(language.Spanish, "scp_upload_complete", "Carga completada") - message.SetString(language.Chinese, "scp_upload_complete", "上传完成") - message.SetString(language.Hindi, "scp_upload_complete", "अपलोड पूर्ण") - message.SetString(language.Arabic, "scp_upload_complete", "اكتمل الرفع") - message.SetString(language.Bengali, "scp_upload_complete", "আপলোড সম্পন্ন") - message.SetString(language.Portuguese, "scp_upload_complete", "Upload concluído") - message.SetString(language.Russian, "scp_upload_complete", "Загрузка завершена") - message.SetString(language.Japanese, "scp_upload_complete", "アップロード完了") - message.SetString(language.German, "scp_upload_complete", "Upload abgeschlossen") - message.SetString(language.French, "scp_upload_complete", "Téléchargement terminé") - - message.SetString(language.English, "scp_download_complete", "Download complete") - message.SetString(language.Spanish, "scp_download_complete", "Descarga completada") - message.SetString(language.Chinese, "scp_download_complete", "下载完成") - message.SetString(language.Hindi, "scp_download_complete", "डाउनलोड पूर्ण") - message.SetString(language.Arabic, "scp_download_complete", "اكتمل التنزيل") - message.SetString(language.Bengali, "scp_download_complete", "ডাউনলোড সম্পন্ন") - message.SetString(language.Portuguese, "scp_download_complete", "Download concluído") - message.SetString(language.Russian, "scp_download_complete", "Загрузка завершена") - message.SetString(language.Japanese, "scp_download_complete", "ダウンロード完了") - message.SetString(language.German, "scp_download_complete", "Download abgeschlossen") - message.SetString(language.French, "scp_download_complete", "Téléchargement terminé") -} - -// T returns a localized string using the global printer thread-safely -func T(key string, args ...interface{}) string { - // Read printer with read lock for concurrent access - printerMu.RLock() - p := printer - printerMu.RUnlock() - - // Initialize if not yet done - if p == nil { - initI18n("") - printerMu.RLock() - p = printer - printerMu.RUnlock() - } - - // Use local copy to avoid holding lock during sprintf - return p.Sprintf(key, args...) -} - -// detectLanguageFromArgs parses command line arguments early to detect --lang flag -// This allows us to initialize i18n with the correct language before creating Cobra commands -func detectLanguageFromArgs(args []string) string { - for i, arg := range args { - if arg == "--lang" && i+1 < len(args) { - return args[i+1] - } - if strings.HasPrefix(arg, "--lang=") { - return strings.TrimPrefix(arg, "--lang=") - } - } - return "" // Default language will be determined by initI18n -} - -// initI18nForCLI initializes i18n early for Cobra CLI with language detection from args -func initI18nForCLI(args []string) { - lang := detectLanguageFromArgs(args) - initI18n(lang) -} diff --git a/_old_complex/i18n_test.go b/_old_complex/i18n_test.go deleted file mode 100644 index db0529f..0000000 --- a/_old_complex/i18n_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package main - -import ( - "strings" - "sync" - "testing" - "time" -) - -func TestI18nInitialization(t *testing.T) { - // Test that i18n can be initialized multiple times safely - initI18n("") - initI18n("en") - initI18n("es") - initI18n("invalid-lang") -} - -func TestTranslationFunction(t *testing.T) { - // Initialize with English - initI18n("en") - - tests := []struct { - key string - expectEmpty bool - }{ - {"flag_lang_desc", false}, - {"no_peers_found", false}, - {"nonexistent_key", true}, - {"", true}, - } - - for _, tt := range tests { - t.Run(tt.key, func(t *testing.T) { - result := T(tt.key) - isEmpty := result == "" || result == tt.key - - if tt.expectEmpty && !isEmpty { - t.Errorf("T(%q) should return empty or key for nonexistent key, got %q", tt.key, result) - } - - if !tt.expectEmpty && isEmpty { - t.Errorf("T(%q) should return translation, got %q", tt.key, result) - } - }) - } -} - -func TestTranslationWithArgs(t *testing.T) { - initI18n("en") - - // Test translation with arguments - result := T("connecting_to", "testhost") - if result == "" || result == "connecting_to" { - t.Errorf("T() with args should return formatted string, got %q", result) - } - - // Should contain the argument - if !strings.Contains(result, "testhost") { - t.Errorf("T() result should contain argument 'testhost', got %q", result) - } -} - -func TestI18nConcurrentAccess(t *testing.T) { - // This tests the race condition fix in i18n - done := make(chan bool, 20) - - // Start multiple goroutines that access i18n functions concurrently - for i := 0; i < 10; i++ { - go func() { - defer func() { done <- true }() - - for j := 0; j < 100; j++ { - initI18n("en") - T("flag_lang_desc") - } - }() - } - - for i := 0; i < 10; i++ { - go func() { - defer func() { done <- true }() - - for j := 0; j < 100; j++ { - initI18n("es") - T("no_peers_found") - } - }() - } - - // Wait for all goroutines with timeout - for i := 0; i < 20; i++ { - select { - case <-done: - // Success - case <-time.After(10 * time.Second): - t.Fatal("Timeout waiting for concurrent i18n access test") - } - } -} - -func TestI18nLanguageSwitching(t *testing.T) { - // Test switching between languages - initI18n("en") - englishResult := T("flag_lang_desc") - - initI18n("es") - spanishResult := T("flag_lang_desc") - - // Results should be different (assuming we have Spanish translations) - // If Spanish translations aren't available, they might be the same - if englishResult == "" { - t.Error("English translation should not be empty") - } - - if spanishResult == "" { - t.Error("Spanish translation should not be empty") - } -} - -func TestI18nThreadSafety(t *testing.T) { - // Test for data races in i18n system - var wg sync.WaitGroup - numGoroutines := 50 - numOperations := 100 - - wg.Add(numGoroutines) - - for i := 0; i < numGoroutines; i++ { - go func(routineID int) { - defer wg.Done() - - for j := 0; j < numOperations; j++ { - // Mix of operations to stress test the race condition fixes - if j%3 == 0 { - initI18n("en") - } else if j%3 == 1 { - initI18n("es") - } - - // Use different translation keys - keys := []string{"flag_lang_desc", "no_peers_found", "status_online", "status_offline"} - key := keys[j%len(keys)] - T(key) - } - }(i) - } - - // Wait with timeout - done := make(chan bool) - go func() { - wg.Wait() - done <- true - }() - - select { - case <-done: - // Success - case <-time.After(30 * time.Second): - t.Fatal("Timeout waiting for i18n thread safety test") - } -} - -func TestI18nNewLanguages(t *testing.T) { - // Test new language support - testCases := []struct { - lang string - key string - shouldExist bool - }{ - {"zh", "no_peers_found", true}, - {"hi", "no_peers_found", true}, - {"ar", "no_peers_found", true}, - {"bn", "no_peers_found", true}, - {"pt", "no_peers_found", true}, - {"ru", "no_peers_found", true}, - {"ja", "no_peers_found", true}, - {"de", "no_peers_found", true}, - {"fr", "no_peers_found", true}, - {"zh", "flag_lang_desc", true}, - {"de", "flag_lang_desc", true}, - {"fr", "flag_lang_desc", true}, - } - - for _, tc := range testCases { - t.Run(tc.lang+"_"+tc.key, func(t *testing.T) { - initI18n(tc.lang) - result := T(tc.key) - - if tc.shouldExist { - if result == tc.key { - t.Errorf("Translation for key '%s' in language '%s' not found", tc.key, tc.lang) - } - // Verify it's different from English - initI18n("en") - english := T(tc.key) - if result == english && tc.lang != "en" { - t.Errorf("Translation for '%s' in '%s' is same as English", tc.key, tc.lang) - } - } - }) - } -} - -func TestI18nLanguageNormalization(t *testing.T) { - // Test language normalization for new languages - testCases := []struct { - input string - expected string - }{ - {"zh", "zh"}, - {"chinese", "zh"}, - {"中文", "zh"}, - {"zh-CN", "zh"}, - {"de", "de"}, - {"german", "de"}, - {"deutsch", "de"}, - {"de-DE", "de"}, - {"fr", "fr"}, - {"french", "fr"}, - {"français", "fr"}, - {"fr-FR", "fr"}, - {"pt", "pt"}, - {"portuguese", "pt"}, - {"pt-BR", "pt"}, - {"ru", "ru"}, - {"russian", "ru"}, - {"ja", "ja"}, - {"japanese", "ja"}, - {"hi", "hi"}, - {"hindi", "hi"}, - {"ar", "ar"}, - {"arabic", "ar"}, - {"bn", "bn"}, - {"bengali", "bn"}, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - result := normalizeLanguage(tc.input) - if result != tc.expected { - t.Errorf("normalizeLanguage(%q) = %q, want %q", tc.input, result, tc.expected) - } - }) - } -} diff --git a/_old_complex/main.go.old b/_old_complex/main.go.old deleted file mode 100644 index f48cfb5..0000000 --- a/_old_complex/main.go.old +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "strings" - - "github.com/derekg/ts-ssh/internal/security" -) - -// version is set at build time via -ldflags "-X main.version=..."; default is "dev". -var version = "dev" - -func main() { - // Initialize security audit logging early (if enabled via environment variables) - if err := security.InitSecurityLogger(); err != nil { - fmt.Fprintf(os.Stderr, "Warning: Failed to initialize security audit logging: %v\n", err) - } - // Ensure security logger is properly closed on exit - defer security.CloseSecurityLogger() - - // Check if we should use the legacy CLI (for backwards compatibility) - if shouldUseLegacyCLI() { - runLegacyCLI() - return - } - - // Use the new Fang-enhanced CLI - ctx := context.Background() - if err := ExecuteWithFang(ctx); err != nil { - // Don't print the error here as Fang will handle it with proper styling - os.Exit(1) - } -} - -// shouldUseLegacyCLI determines if we should use the legacy CLI -// This can be controlled by an environment variable for compatibility -func shouldUseLegacyCLI() bool { - // Check for explicit legacy mode - if os.Getenv("TS_SSH_LEGACY_CLI") == "1" { - return true - } - - // Check if the command line looks like it's using the old style - // (this helps with backwards compatibility during transition) - if len(os.Args) > 1 { - firstArg := os.Args[1] - // If first arg starts with user@ or contains :, it's likely a connection target - if strings.Contains(firstArg, "@") || - (strings.Contains(firstArg, ":") && !strings.HasPrefix(firstArg, "-")) { - // Insert "connect" subcommand for backwards compatibility - newArgs := []string{os.Args[0], "connect"} - newArgs = append(newArgs, os.Args[1:]...) - os.Args = newArgs - } - } - - return false -} - -// runLegacyCLI runs the original simple CLI implementation -func runLegacyCLI() { - // Create the CLI application - cli := NewCLI() - - // Handle special case for backwards compatibility: if first arg looks like a target, - // and no subcommand is specified, default to connect command - if len(os.Args) > 1 && !isSubcommand(os.Args[1]) { - // Insert "connect" as the first argument to maintain compatibility - args := []string{os.Args[0], "connect"} - args = append(args, os.Args[1:]...) - os.Args = args - } - - // Run the CLI - ctx := context.Background() - if err := cli.Run(ctx, os.Args[1:]); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} - -// isSubcommand checks if the given argument is a known subcommand -func isSubcommand(arg string) bool { - subcommands := []string{ - "connect", "scp", "list", "exec", "multi", "config", "pqc", "version", - "help", "-h", "--help", "-v", "--version", - } - - for _, cmd := range subcommands { - if arg == cmd { - return true - } - } - - return false -} diff --git a/_old_complex/main_helpers.go b/_old_complex/main_helpers.go deleted file mode 100644 index fcc40eb..0000000 --- a/_old_complex/main_helpers.go +++ /dev/null @@ -1,511 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "io" - "log" - "os" - "os/user" - "path/filepath" - "strings" - - "golang.org/x/crypto/ssh" - "golang.org/x/term" - "tailscale.com/tsnet" - - "github.com/derekg/ts-ssh/internal/client/scp" - sshclient "github.com/derekg/ts-ssh/internal/client/ssh" - "github.com/derekg/ts-ssh/internal/crypto/pqc" - "github.com/derekg/ts-ssh/internal/security" -) - -// AppConfig holds all the configuration for the application -type AppConfig struct { - SSHUser string - SSHKeyPath string - TsnetDir string - TsControlURL string - Target string - Verbose bool - InsecureHostKey bool - ForwardDest string - ShowVersion bool - LangFlag string - // Power CLI features - ListHosts bool - MultiHosts string - ExecCmd string - CopyFiles string - PickHost bool - Parallel bool - // Post-quantum cryptography - EnablePQC bool - PQCLevel int - PQCReport bool - // Derived values - RemoteCmd []string - Logger *log.Logger -} - -// parseCommandLineArgs sets up and parses all command line arguments -func parseCommandLineArgs() *AppConfig { - config := &AppConfig{} - - // Get current user for defaults - currentUser, err := user.Current() - if err != nil { - fmt.Fprintf(os.Stderr, "Warning: Could not determine current user: %v. Using 'user' as default.\n", err) - currentUser = &user.User{Username: "user", HomeDir: "/home/user"} - } - - // Set up defaults - defaultUser := currentUser.Username - defaultKeyPath := filepath.Join(currentUser.HomeDir, ".ssh", "id_rsa") - defaultTsnetDir := filepath.Join(currentUser.HomeDir, ".config", ClientName) - - // Initialize i18n early to support flag descriptions - initI18n("") - - // Define flags - flag.StringVar(&config.LangFlag, "lang", "", T("flag_lang_desc")) - flag.StringVar(&config.SSHUser, "l", defaultUser, T("flag_user_desc")) - flag.StringVar(&config.SSHKeyPath, "i", defaultKeyPath, T("flag_key_desc")) - flag.StringVar(&config.TsnetDir, "tsnet-dir", defaultTsnetDir, T("flag_tsnet_desc")) - flag.StringVar(&config.TsControlURL, "control-url", "", T("flag_control_desc")) - flag.BoolVar(&config.Verbose, "v", false, T("flag_verbose_desc")) - flag.BoolVar(&config.InsecureHostKey, "insecure", false, T("flag_insecure_desc")) - flag.StringVar(&config.ForwardDest, "W", "", T("flag_forward_desc")) - flag.BoolVar(&config.ShowVersion, "version", false, T("flag_version_desc")) - - // Power CLI features - flag.BoolVar(&config.ListHosts, "list", false, T("flag_list_desc")) - flag.StringVar(&config.MultiHosts, "multi", "", T("flag_multi_desc")) - flag.StringVar(&config.ExecCmd, "exec", "", T("flag_exec_desc")) - flag.StringVar(&config.CopyFiles, "copy", "", T("flag_copy_desc")) - flag.BoolVar(&config.PickHost, "pick", false, T("flag_pick_desc")) - flag.BoolVar(&config.Parallel, "parallel", false, T("flag_parallel_desc")) - - // Set up dynamic usage function for language support - flag.Usage = createUsageFunction() - - flag.Parse() - - // Reinitialize i18n with the actual language flag after parsing - initI18n(config.LangFlag) - - // Set up logger - if config.Verbose { - config.Logger = log.Default() - } else { - config.Logger = log.New(io.Discard, "", 0) - } - - // Get remaining arguments as remote command - config.RemoteCmd = flag.Args()[1:] - - return config -} - -// createUsageFunction returns a usage function that supports dynamic language switching -func createUsageFunction() func() { - return func() { - // Parse args to get language flag before displaying help - tempLang := "" - for i, arg := range os.Args[1:] { - if arg == "--lang" && i+1 < len(os.Args[1:]) { - tempLang = os.Args[i+2] - break - } else if strings.HasPrefix(arg, "--lang=") { - tempLang = strings.SplitN(arg, "=", 2)[1] - break - } - } - - // Temporarily reinitialize i18n for help display - if tempLang != "" { - initI18n(tempLang) - } - - fmt.Fprint(os.Stderr, T("usage_header", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("usage_list", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("usage_multi", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("usage_exec", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("usage_copy", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("usage_pick", os.Args[0])+"\n\n") - fmt.Fprint(os.Stderr, T("usage_description")+"\n") - flag.PrintDefaults() - fmt.Fprint(os.Stderr, T("examples_header")+"\n") - fmt.Fprint(os.Stderr, T("examples_basic_ssh")+"\n") - fmt.Fprint(os.Stderr, T("examples_interactive", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_remote_cmd", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_host_discovery")+"\n") - fmt.Fprint(os.Stderr, T("examples_list_hosts", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_pick_host", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_multi_host")+"\n") - fmt.Fprint(os.Stderr, T("examples_tmux", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_exec_multi", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_parallel", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_file_transfer")+"\n") - fmt.Fprint(os.Stderr, T("examples_scp_single", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_scp_multi", os.Args[0])+"\n") - fmt.Fprint(os.Stderr, T("examples_proxy")+"\n") - fmt.Fprint(os.Stderr, T("examples_proxy_cmd", os.Args[0])+"\n") - } -} - -// handleVersionFlag displays version information and exits if requested -func handleVersionFlag(config *AppConfig) { - if config.ShowVersion { - fmt.Println(version) - os.Exit(0) - } -} - -// isPowerCLIMode determines if we're in power CLI mode (list, multi, exec, copy, pick) -func isPowerCLIMode(config *AppConfig) bool { - return config.ListHosts || config.MultiHosts != "" || config.ExecCmd != "" || - config.CopyFiles != "" || config.PickHost -} - -// handlePowerCLI handles all power CLI operations -func handlePowerCLI(config *AppConfig) error { - srv, ctx, status, err := initTsNet(config.TsnetDir, ClientName, config.Logger, config.TsControlURL, config.Verbose) - if err != nil { - return fmt.Errorf("%s", T("error_init_tailscale")) - } - - // Get current user - currentUser, err := user.Current() - if err != nil { - config.Logger.Printf("Warning: Could not determine current user: %v", err) - currentUser = &user.User{Username: config.SSHUser} - } - - // Handle each power CLI operation - if config.ListHosts { - return handleListHosts(status, config.Verbose) - } - - if config.MultiHosts != "" { - return handleMultiHosts(config.MultiHosts, config.Logger, config.SSHUser, - config.SSHKeyPath, config.InsecureHostKey) - } - - if config.ExecCmd != "" { - hosts := parseHostList(flag.Args()) - return handleExecCommand(srv, ctx, config.ExecCmd, hosts, config.Logger, - config.SSHUser, config.SSHKeyPath, config.InsecureHostKey, - config.Parallel, config.Verbose) - } - - if config.CopyFiles != "" { - return handleCopyFiles(srv, ctx, config.CopyFiles, config.Logger, - config.SSHUser, config.SSHKeyPath, config.InsecureHostKey, - config.Verbose) - } - - if config.PickHost { - return handlePickHost(srv, ctx, status, config.Logger, config.SSHUser, - config.SSHKeyPath, config.InsecureHostKey, currentUser, - config.Verbose) - } - - return nil -} - -// detectSCPOperation checks if this is an SCP operation and returns parsed arguments -func detectSCPOperation(config *AppConfig) *scpArgs { - // TODO: Implement SCP detection logic from original main.go - return nil -} - -// handleSCPOperation performs the SCP file transfer -func handleSCPOperation(scpArgs *scpArgs, config *AppConfig) error { - srv, ctx, _, err := initTsNet(config.TsnetDir, ClientName, config.Logger, config.TsControlURL, config.Verbose) - if err != nil { - return fmt.Errorf("%s", T("error_init_tailscale")) - } - - // Get current user - currentUser, err := user.Current() - if err != nil { - config.Logger.Printf("Warning: Could not determine current user: %v", err) - currentUser = &user.User{Username: config.SSHUser} - } - - err = scp.HandleCliScp(srv, ctx, config.Logger, scpArgs.sshUser, config.SSHKeyPath, - config.InsecureHostKey, currentUser, scpArgs.localPath, - scpArgs.remotePath, scpArgs.targetHost, true, config.Verbose) - - if err != nil { - return fmt.Errorf("%s", T("error_scp_failed")) - } - - fmt.Println(T("scp_success")) - return nil -} - -// handleSSHOperation performs a regular SSH connection -func handleSSHOperation(config *AppConfig) error { - // Validate target is provided - if config.Target == "" { - return fmt.Errorf("target hostname required") - } - - targetHost, targetPort, err := parseTarget(config.Target, DefaultSshPort) - if err != nil { - return fmt.Errorf("%s", T("error_parsing_target")) - } - - // Determine SSH user - sshSpecificUser := config.SSHUser - if strings.Contains(targetHost, "@") { - parts := strings.SplitN(targetHost, "@", 2) - sshSpecificUser = parts[0] - targetHost = parts[1] - - // SECURITY: Validate extracted SSH user and hostname - if err := security.ValidateSSHUser(sshSpecificUser); err != nil { - return fmt.Errorf("SSH user validation failed: %w", err) - } - if err := security.ValidateHostname(targetHost); err != nil { - return fmt.Errorf("extracted hostname validation failed: %w", err) - } - } - - // SECURITY: Validate all components - if err := security.ValidateSSHUser(sshSpecificUser); err != nil { - return fmt.Errorf("SSH user validation failed: %w", err) - } - if err := security.ValidatePort(targetPort); err != nil { - return fmt.Errorf("port validation failed: %w", err) - } - - // Initialize tsnet - srv, nonTuiCtx, _, err := initTsNet(config.TsnetDir, ClientName, config.Logger, config.TsControlURL, config.Verbose) - if err != nil { - return fmt.Errorf("%s", T("error_init_ssh")) - } - - // Handle ProxyCommand mode - if config.ForwardDest != "" { - return handleProxyCommand(srv, nonTuiCtx, config.ForwardDest, config.Logger) - } - - // Get current user - currentUser, err := user.Current() - if err != nil { - config.Logger.Printf("Warning: Could not determine current user: %v", err) - currentUser = &user.User{Username: sshSpecificUser} - } - - // Configure PQC settings - var pqcConfig *pqc.Config - if config.EnablePQC { - pqcConfig = pqc.DefaultConfig() - pqcConfig.QuantumResistance = pqc.QuantumResistanceLevel(config.PQCLevel) - pqcConfig.EnablePQC = true - pqcConfig.LogPQCUsage = config.Verbose - if config.Verbose { - config.Logger.Printf("PQC: Enabled with resistance level %d", config.PQCLevel) - } - } - - // Establish SSH connection - sshConfig := sshclient.SSHConnectionConfig{ - User: sshSpecificUser, - KeyPath: config.SSHKeyPath, - TargetHost: targetHost, - TargetPort: targetPort, - InsecureHostKey: config.InsecureHostKey, - Verbose: config.Verbose, - CurrentUser: currentUser, - Logger: config.Logger, - PQCConfig: pqcConfig, - } - - client, err := sshclient.EstablishSSHConnection(srv, nonTuiCtx, sshConfig) - if err != nil { - return fmt.Errorf("failed to establish SSH connection: %w", err) - } - defer client.Close() - - // Handle remote command or interactive session - if len(config.RemoteCmd) > 0 { - return executeRemoteCommand(client, config.RemoteCmd, config.Logger) - } - - return startInteractiveSession(client, config.Logger) -} - -// handleProxyCommand handles ProxyCommand stdio forwarding -func handleProxyCommand(srv *tsnet.Server, ctx context.Context, forwardDest string, logger *log.Logger) error { - logger.Printf("Forwarding stdio to %s via tsnet...", forwardDest) - fwdConn, err := srv.Dial(ctx, "tcp", forwardDest) - if err != nil { - return fmt.Errorf("failed to dial %s via tsnet for forwarding: %w", forwardDest, err) - } - - go func() { - _, _ = io.Copy(fwdConn, os.Stdin) - fwdConn.Close() - }() - - _, _ = io.Copy(os.Stdout, fwdConn) - return nil -} - -// executeRemoteCommand executes a command on the remote host and returns -func executeRemoteCommand(client *ssh.Client, remoteCmd []string, logger *log.Logger) error { - logger.Printf("Running remote command: %v", remoteCmd) - session, err := sshclient.CreateSSHSession(client) - if err != nil { - return fmt.Errorf("failed to create SSH session for remote command: %w", err) - } - defer session.Close() - - session.Stdout = os.Stdout - session.Stderr = os.Stderr - session.Stdin = os.Stdin - - cmdStr := strings.Join(remoteCmd, " ") - err = session.Run(cmdStr) - if err != nil { - if exitError, ok := err.(*ssh.ExitError); ok { - os.Exit(exitError.ExitStatus()) - } - return fmt.Errorf("remote command failed: %w", err) - } - - return nil -} - -// startInteractiveSession starts an interactive SSH session with PTY support -func startInteractiveSession(client *ssh.Client, logger *log.Logger) error { - logger.Println("Starting interactive SSH session...") - session, err := sshclient.CreateSSHSession(client) - if err != nil { - return fmt.Errorf("failed to create SSH session: %w", err) - } - defer session.Close() - - // Set up stdin pipe - stdinPipe, err := session.StdinPipe() - if err != nil { - return fmt.Errorf("failed to create stdin pipe for SSH session: %w", err) - } - session.Stdout = os.Stdout - session.Stderr = os.Stderr - - // Set up terminal if running in one - fd := int(os.Stdin.Fd()) - if term.IsTerminal(fd) { - err = setupTerminal(session, fd, logger) - if err != nil { - return fmt.Errorf("failed to setup terminal: %w", err) - } - } - - // Print escape sequence message before starting shell (in terminal mode only) - if term.IsTerminal(fd) { - fmt.Fprint(os.Stderr, T("escape_sequence")+"\n") - } - - // Start the shell - err = session.Shell() - if err != nil { - return fmt.Errorf("failed to start shell: %w", err) - } - - // Handle terminal resizing and escape sequences - return handleInteractiveSession(session, stdinPipe, fd, logger) -} - -// setupTerminal configures the terminal for interactive SSH session -func setupTerminal(session *ssh.Session, fd int, logger *log.Logger) error { - termWidth, termHeight, err := term.GetSize(fd) - if err != nil { - logger.Printf("Warning: Failed to get terminal size: %v. Using default %dx%d.", err, DefaultTerminalWidth, DefaultTerminalHeight) - termWidth = DefaultTerminalWidth - termHeight = DefaultTerminalHeight - } - - termType := os.Getenv("TERM") - if termType == "" { - termType = DefaultTerminalType - } - - err = session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{}) - if err != nil { - return fmt.Errorf("failed to request pseudo-terminal: %w", err) - } - - return nil -} - -// handleInteractiveSession manages the interactive SSH session with proper terminal handling -func handleInteractiveSession(session *ssh.Session, stdinPipe io.WriteCloser, fd int, logger *log.Logger) error { - termState := GetGlobalTerminalState() - - // Set up terminal in raw mode if we're in a terminal - if term.IsTerminal(fd) { - err := termState.MakeRaw(fd) - if err != nil { - logger.Printf("Warning: Failed to set terminal to raw mode: %v", err) - } else { - // Ensure terminal is restored on exit - defer func() { - if err := termState.Restore(); err != nil { - logger.Printf("Warning: Failed to restore terminal: %v", err) - } - }() - } - } - - // Set up signal handling for graceful shutdown - done := make(chan bool, 1) - go handleInputWithTerminalState(stdinPipe, done, logger, termState) - - // Handle window resize signals if in terminal - if term.IsTerminal(fd) { - go handleSignalsAndResizeWithTerminalState(session, termState, logger) - } - - // Wait for session to complete - err := session.Wait() - done <- true // Signal input handler to stop - - return err -} - -// handleInputWithTerminalState handles stdin input with terminal state awareness -func handleInputWithTerminalState(stdinPipe io.WriteCloser, done chan bool, logger *log.Logger, termState *TerminalStateManager) { - defer stdinPipe.Close() - - // Create a buffered reader for stdin - input := make([]byte, 1024) - - for { - select { - case <-done: - return - default: - n, err := os.Stdin.Read(input) - if err != nil { - if err != io.EOF { - logger.Printf("Error reading stdin: %v", err) - } - return - } - - // Write to SSH session - _, writeErr := stdinPipe.Write(input[:n]) - if writeErr != nil { - logger.Printf("Error writing to SSH session: %v", writeErr) - return - } - } - } -} diff --git a/_old_complex/main_legacy.go b/_old_complex/main_legacy.go deleted file mode 100644 index fa49551..0000000 --- a/_old_complex/main_legacy.go +++ /dev/null @@ -1,22 +0,0 @@ -// This file contains the original main.go implementation before fang CLI integration -// It's kept for reference and potential fallback scenarios - -package main - -// NOTE: This file is not compiled (no main function) -// Original implementation moved to main_helpers.go and cli.go - -/* -Original main.go content: -- Used Go standard flag library -- Large main() function with many individual flags -- Direct argument parsing and command dispatching -- All logic inline in main() function - -New implementation: -- Uses Charmbracelet fang CLI framework -- Structured subcommands (connect, scp, list, exec, multi, config, pqc, version) -- Modular command structure with individual Run() methods -- Better help output and usage documentation -- Backwards compatibility through automatic command detection -*/ diff --git a/_old_complex/power_cli.go b/_old_complex/power_cli.go deleted file mode 100644 index be799c3..0000000 --- a/_old_complex/power_cli.go +++ /dev/null @@ -1,503 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "log" - "net" - "os" - "os/user" - "sort" - "strings" - "sync" - - "golang.org/x/crypto/ssh" - "tailscale.com/ipn/ipnstate" - "tailscale.com/tsnet" - - "github.com/derekg/ts-ssh/internal/client/scp" - sshclient "github.com/derekg/ts-ssh/internal/client/ssh" - "github.com/derekg/ts-ssh/internal/security" -) - -// handleListHosts lists all available Tailscale hosts -func handleListHosts(status *ipnstate.Status, verbose bool) error { - if status == nil || len(status.Peer) == 0 { - fmt.Println(T("no_peers_found")) - return nil - } - - // Collect and sort hosts - type hostInfo struct { - name string - ip string - online bool - os string - } - - var hosts []hostInfo - for _, peer := range status.Peer { - name := getHostDisplayName(peer) - ip := "" - if len(peer.TailscaleIPs) > 0 { - ip = peer.TailscaleIPs[0].String() - } - - hosts = append(hosts, hostInfo{ - name: name, - ip: ip, - online: peer.Online, - os: peer.OS, - }) - } - - // Sort by name - sort.Slice(hosts, func(i, j int) bool { - return hosts[i].name < hosts[j].name - }) - - // Print results - if verbose { - // Get individual labels for the header - labels := strings.Split(T("host_list_labels"), ",") - separators := strings.Split(T("host_list_separator"), ",") - - // Default fallback if translation fails - if len(labels) < 4 { - labels = []string{"HOST", "IP", "STATUS", "OS"} - } - if len(separators) < 4 { - separators = []string{"----", "--", "------", "--"} - } - - fmt.Printf("%-25s %-15s %-8s %s\n", labels[0], labels[1], labels[2], labels[3]) - fmt.Printf("%-25s %-15s %-8s %s\n", separators[0], separators[1], separators[2], separators[3]) - - for _, host := range hosts { - status := T("status_offline") - if host.online { - status = T("status_online") - } - fmt.Printf("%-25s %-15s %-8s %s\n", host.name, host.ip, status, host.os) - } - } else { - // Simple format - just online hosts - for _, host := range hosts { - if host.online { - fmt.Println(host.name) - } - } - } - - return nil -} - -// handlePickHost provides simple interactive host selection -func handlePickHost(srv *tsnet.Server, ctx context.Context, status *ipnstate.Status, logger *log.Logger, - sshUser, sshKeyPath string, insecureHostKey bool, currentUser *user.User, verbose bool) error { - - if status == nil || len(status.Peer) == 0 { - return fmt.Errorf("%s", T("no_peers_found")) - } - - // Collect online hosts - var onlineHosts []string - for _, peer := range status.Peer { - if peer.Online { - onlineHosts = append(onlineHosts, getHostDisplayName(peer)) - } - } - - if len(onlineHosts) == 0 { - return fmt.Errorf("%s", T("no_online_hosts")) - } - - sort.Strings(onlineHosts) - - // Simple selection interface - fmt.Println(T("available_hosts")) - for i, host := range onlineHosts { - fmt.Printf(" %d) %s\n", i+1, host) - } - fmt.Print(T("select_host", len(onlineHosts))) - - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil { - return fmt.Errorf("failed to read input: %w", err) - } - - var selection int - if _, err := fmt.Sscanf(strings.TrimSpace(input), "%d", &selection); err != nil { - return fmt.Errorf("%s", T("invalid_selection")) - } - - if selection < 1 || selection > len(onlineHosts) { - return fmt.Errorf("%s", T("selection_out_of_range")) - } - - selectedHost := onlineHosts[selection-1] - fmt.Println(T("connecting_to", selectedHost)) - - // Connect to selected host - return sshclient.ConnectToHost(srv, ctx, logger, selectedHost, sshUser, sshKeyPath, insecureHostKey, currentUser, verbose) -} - -// handleMultiHosts starts a tmux session with multiple hosts -func handleMultiHosts(multiHosts string, logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool) error { - hosts := strings.Split(multiHosts, ",") - if len(hosts) == 0 { - return fmt.Errorf("%s", T("no_hosts_specified")) - } - - // Clean up host names - for i, host := range hosts { - hosts[i] = strings.TrimSpace(host) - } - - if logger != nil { - logger.Printf("Starting tmux session with hosts: %v", hosts) - } - - tmuxManager := NewTmuxManager(logger, sshUser, sshKeyPath, insecureHostKey) - - // Ensure cleanup happens on exit - defer tmuxManager.cleanupTempConfigFiles() - - return tmuxManager.StartMultiSession(hosts) -} - -// handleExecCommand executes a command on multiple hosts -func handleExecCommand(srv *tsnet.Server, ctx context.Context, execCmd string, hosts []string, - logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool, parallel, verbose bool) error { - - if len(hosts) == 0 { - return fmt.Errorf("%s", T("no_hosts_for_exec")) - } - - if parallel { - return executeParallel(srv, ctx, execCmd, hosts, logger, sshUser, sshKeyPath, insecureHostKey, verbose) - } else { - return executeSequential(srv, ctx, execCmd, hosts, logger, sshUser, sshKeyPath, insecureHostKey, verbose) - } -} - -// handleCopyFiles copies files to multiple hosts -func handleCopyFiles(srv *tsnet.Server, ctx context.Context, copyFiles string, logger *log.Logger, - sshUser, sshKeyPath string, insecureHostKey bool, verbose bool) error { - - // Parse format: localfile host1,host2:/path/ - parts := strings.Split(copyFiles, " ") - if len(parts) != 2 { - return fmt.Errorf("%s", T("invalid_copy_format")) - } - - localFile := parts[0] - remoteSpec := parts[1] - - // Split remote spec into hosts and path - if !strings.Contains(remoteSpec, ":") { - return fmt.Errorf("%s", T("invalid_remote_spec")) - } - - colonIdx := strings.LastIndex(remoteSpec, ":") - hostsStr := remoteSpec[:colonIdx] - remotePath := remoteSpec[colonIdx+1:] - - hosts := strings.Split(hostsStr, ",") - for i, host := range hosts { - hosts[i] = strings.TrimSpace(host) - } - - // Copy to each host sequentially - for _, host := range hosts { - fmt.Println(T("copying_to", localFile, host, remotePath)) - - // Use our existing SCP logic - err := scp.HandleCliScp(srv, ctx, logger, sshUser, sshKeyPath, insecureHostKey, nil, - localFile, remotePath, host, true, verbose) - - if err != nil { - fmt.Println(T("copy_failed", host, err)) - continue - } - - if verbose { - fmt.Println(T("copy_success", host)) - } - } - - return nil -} - -// executeParallel runs commands on multiple hosts in parallel with race condition protection -func executeParallel(srv *tsnet.Server, ctx context.Context, execCmd string, hosts []string, - logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool, verbose bool) error { - - var wg sync.WaitGroup - results := make(chan string, len(hosts)) - - // Create a mutex to protect against concurrent password prompts - var authMutex sync.Mutex - - for _, host := range hosts { - wg.Add(1) - go func(h string) { - defer wg.Done() - - // Create a host-specific logger to avoid concurrent access to shared logger - hostLogger := log.New(logger.Writer(), fmt.Sprintf("[%s] ", h), logger.Flags()) - - output, err := executeOnHostSafe(srv, ctx, execCmd, h, hostLogger, sshUser, sshKeyPath, insecureHostKey, verbose, &authMutex) - if err != nil { - results <- fmt.Sprintf("[%s] ERROR: %v", h, err) - } else { - results <- fmt.Sprintf("[%s]\n%s", h, output) - } - }(host) - } - - // Close results channel when all goroutines complete - go func() { - wg.Wait() - close(results) - }() - - // Print results as they come in - for result := range results { - fmt.Printf("%s\n", result) - } - - return nil -} - -// executeSequential runs commands on hosts one by one -func executeSequential(srv *tsnet.Server, ctx context.Context, execCmd string, hosts []string, - logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool, verbose bool) error { - - for _, host := range hosts { - fmt.Printf("=== %s ===\n", host) - - output, err := executeOnHost(srv, ctx, execCmd, host, logger, sshUser, sshKeyPath, insecureHostKey, verbose) - if err != nil { - fmt.Printf("ERROR: %v\n", err) - continue - } - - fmt.Printf("%s\n", output) - } - - return nil -} - -// executeCommandOnHost executes a command on a remote host using SSH helpers -func executeCommandOnHost(srv *tsnet.Server, ctx context.Context, execCmd, host string, - logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool, authMutex *sync.Mutex) (string, error) { - - // SECURITY: Validate command and hostname to prevent injection attacks - if err := security.ValidateCommand(execCmd); err != nil { - return "", fmt.Errorf("command validation failed: %w", err) - } - - if err := security.ValidateHostname(host); err != nil { - return "", fmt.Errorf("hostname validation failed: %w", err) - } - - // Parse target and user - targetHost, targetPort, err := parseTarget(host, DefaultSshPort) - if err != nil { - return "", fmt.Errorf("error parsing target %s: %w", host, err) - } - - effectiveUser := sshUser - if strings.Contains(targetHost, "@") { - parts := strings.SplitN(targetHost, "@", 2) - effectiveUser = parts[0] - targetHost = parts[1] - - // SECURITY: Validate extracted SSH user - if err := security.ValidateSSHUser(effectiveUser); err != nil { - return "", fmt.Errorf("SSH user validation failed: %w", err) - } - - // SECURITY: Re-validate hostname after extraction - if err := security.ValidateHostname(targetHost); err != nil { - return "", fmt.Errorf("extracted hostname validation failed: %w", err) - } - } - - // SECURITY: Validate SSH user in all cases - if err := security.ValidateSSHUser(effectiveUser); err != nil { - return "", fmt.Errorf("SSH user validation failed: %w", err) - } - - // SECURITY: Validate port - if err := security.ValidatePort(targetPort); err != nil { - return "", fmt.Errorf("port validation failed: %w", err) - } - - // Create SSH config using standard helpers - sshConfig := sshclient.SSHConnectionConfig{ - User: effectiveUser, - KeyPath: sshKeyPath, - TargetHost: targetHost, - TargetPort: targetPort, - InsecureHostKey: insecureHostKey, - Verbose: false, - CurrentUser: nil, // Not needed for command execution - Logger: logger, - } - - // Override auth methods for thread-safe password prompts if needed - if authMutex != nil { - authMethods, err := createSSHAuthMethodsWithMutex(sshKeyPath, effectiveUser, targetHost, logger, authMutex) - if err != nil { - return "", fmt.Errorf("failed to create auth methods: %w", err) - } - - // Create client config manually for custom auth - clientConfig := &ssh.ClientConfig{ - User: effectiveUser, - Auth: authMethods, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Simplified for parallel execution - Timeout: DefaultSSHTimeout, - } - - return executeSSHCommandWithConfig(srv, ctx, clientConfig, targetHost, targetPort, execCmd) - } - - // Use standard SSH helper for non-parallel execution - client, err := sshclient.EstablishSSHConnection(srv, ctx, sshConfig) - if err != nil { - return "", fmt.Errorf("failed to establish SSH connection: %w", err) - } - defer client.Close() - - return executeSSHCommand(client, execCmd) -} - -// createSSHAuthMethodsWithMutex creates auth methods with thread-safe password prompts -func createSSHAuthMethodsWithMutex(keyPath, user, targetHost string, logger *log.Logger, authMutex *sync.Mutex) ([]ssh.AuthMethod, error) { - var authMethods []ssh.AuthMethod - - // Try to load SSH key if provided - if keyPath != "" { - keyAuth, err := sshclient.LoadPrivateKey(keyPath, logger) - if err == nil { - authMethods = append(authMethods, keyAuth) - } - } - - // Add thread-safe password authentication using secure TTY - authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) { - authMutex.Lock() - defer authMutex.Unlock() - - fmt.Print(T("enter_password", user, targetHost)) - password, err := security.ReadPasswordSecurely() - fmt.Println() - if err != nil { - return "", fmt.Errorf("failed to read password securely: %w", err) - } - return password, nil - })) - - return authMethods, nil -} - -// executeSSHCommand runs a command on an established SSH connection -func executeSSHCommand(client *ssh.Client, execCmd string) (string, error) { - session, err := client.NewSession() - if err != nil { - return "", fmt.Errorf("failed to create SSH session: %w", err) - } - defer session.Close() - - output, err := session.CombinedOutput(execCmd) - if err != nil { - return string(output), fmt.Errorf("command failed: %w", err) - } - - return string(output), nil -} - -// executeSSHCommandWithConfig runs a command using low-level SSH connection -func executeSSHCommandWithConfig(srv *tsnet.Server, ctx context.Context, sshConfig *ssh.ClientConfig, targetHost, targetPort, execCmd string) (string, error) { - sshTargetAddr := net.JoinHostPort(targetHost, targetPort) - - conn, err := srv.Dial(ctx, "tcp", sshTargetAddr) - if err != nil { - return "", fmt.Errorf("failed to dial %s via tsnet: %w", sshTargetAddr, err) - } - - sshConn, chans, reqs, err := ssh.NewClientConn(conn, sshTargetAddr, sshConfig) - if err != nil { - conn.Close() - return "", fmt.Errorf("failed to establish SSH connection: %w", err) - } - defer sshConn.Close() - - client := ssh.NewClient(sshConn, chans, reqs) - defer client.Close() - - return executeSSHCommand(client, execCmd) -} - -// executeOnHost executes a command on a single host and returns the output -func executeOnHost(srv *tsnet.Server, ctx context.Context, execCmd, host string, - logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool, verbose bool) (string, error) { - - return executeCommandOnHost(srv, ctx, execCmd, host, logger, sshUser, sshKeyPath, insecureHostKey, nil) -} - -// executeOnHostSafe executes a command on a single host with thread-safe authentication -func executeOnHostSafe(srv *tsnet.Server, ctx context.Context, execCmd, host string, - logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool, verbose bool, authMutex *sync.Mutex) (string, error) { - - return executeCommandOnHost(srv, ctx, execCmd, host, logger, sshUser, sshKeyPath, insecureHostKey, authMutex) -} - -// parseHostList parses comma-separated host list from args -func parseHostList(args []string) []string { - if len(args) == 0 { - return nil - } - - // Pre-calculate capacity to reduce allocations - // Estimate based on comma count + number of args - capacity := len(args) - for _, arg := range args { - capacity += strings.Count(arg, ",") - } - - hosts := make([]string, 0, capacity) - for _, arg := range args { - if strings.Contains(arg, ",") { - parts := strings.Split(arg, ",") - // Trim spaces in place to avoid additional allocation - for i, part := range parts { - parts[i] = strings.TrimSpace(part) - } - hosts = append(hosts, parts...) - } else { - hosts = append(hosts, strings.TrimSpace(arg)) - } - } - - return hosts -} - -// getHostDisplayName extracts the best display name for a host -func getHostDisplayName(peer *ipnstate.PeerStatus) string { - if peer.DNSName != "" { - return strings.TrimSuffix(peer.DNSName, ".") - } - if peer.HostName != "" { - return peer.HostName - } - if len(peer.TailscaleIPs) > 0 { - return peer.TailscaleIPs[0].String() - } - return fmt.Sprintf("unknown-%s", peer.ID) -} diff --git a/_old_complex/signals_unix.go b/_old_complex/signals_unix.go deleted file mode 100644 index 518528c..0000000 --- a/_old_complex/signals_unix.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !windows - -package main - -import ( - "log" - "os" - "os/signal" - "syscall" - - "golang.org/x/crypto/ssh" - "golang.org/x/term" -) - -// handleSignalsAndResizeWithTerminalState handles signals and window resizing with terminal state -func handleSignalsAndResizeWithTerminalState(session *ssh.Session, termState *TerminalStateManager, logger *log.Logger) { - // Set up signal channel for SIGWINCH (window resize) - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGWINCH) - - for { - select { - case <-sigCh: - if termState.IsRaw() { - fd := termState.GetFD() - if width, height, err := term.GetSize(fd); err == nil { - // Send window size change to remote - session.WindowChange(height, width) - } - } - } - } -} diff --git a/_old_complex/signals_windows.go b/_old_complex/signals_windows.go deleted file mode 100644 index e229d2a..0000000 --- a/_old_complex/signals_windows.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build windows - -package main - -import ( - "log" - - "golang.org/x/crypto/ssh" -) - -// handleSignalsAndResizeWithTerminalState handles signals and window resizing with terminal state -// On Windows, SIGWINCH doesn't exist, so this is a no-op -func handleSignalsAndResizeWithTerminalState(session *ssh.Session, termState *TerminalStateManager, logger *log.Logger) { - // Windows doesn't support SIGWINCH signal for terminal resize - // This function is a no-op on Windows - return -} diff --git a/_old_complex/terminal_state.go b/_old_complex/terminal_state.go deleted file mode 100644 index cc0ffd5..0000000 --- a/_old_complex/terminal_state.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "sync" - - "golang.org/x/term" -) - -// TerminalStateManager provides thread-safe terminal state management -type TerminalStateManager struct { - mu sync.RWMutex - oldState *term.State - fd int - isRaw bool -} - -// NewTerminalStateManager creates a new terminal state manager -func NewTerminalStateManager() *TerminalStateManager { - return &TerminalStateManager{ - fd: -1, - } -} - -// MakeRaw sets the terminal to raw mode safely -func (tsm *TerminalStateManager) MakeRaw(fd int) error { - tsm.mu.Lock() - defer tsm.mu.Unlock() - - if tsm.isRaw { - return nil // Already in raw mode - } - - oldState, err := term.MakeRaw(fd) - if err != nil { - return err - } - - tsm.oldState = oldState - tsm.fd = fd - tsm.isRaw = true - return nil -} - -// Restore restores the terminal to its original state safely -func (tsm *TerminalStateManager) Restore() error { - tsm.mu.Lock() - defer tsm.mu.Unlock() - - if !tsm.isRaw || tsm.oldState == nil { - return nil // Nothing to restore - } - - err := term.Restore(tsm.fd, tsm.oldState) - if err == nil { - tsm.isRaw = false - tsm.oldState = nil - tsm.fd = -1 - } - return err -} - -// IsRaw returns whether the terminal is currently in raw mode -func (tsm *TerminalStateManager) IsRaw() bool { - tsm.mu.RLock() - defer tsm.mu.RUnlock() - return tsm.isRaw -} - -// GetFD returns the current file descriptor -func (tsm *TerminalStateManager) GetFD() int { - tsm.mu.RLock() - defer tsm.mu.RUnlock() - return tsm.fd -} - -// Global terminal state manager instance -var globalTerminalState = NewTerminalStateManager() - -// GetGlobalTerminalState returns the global terminal state manager -func GetGlobalTerminalState() *TerminalStateManager { - return globalTerminalState -} diff --git a/_old_complex/terminal_state_test.go b/_old_complex/terminal_state_test.go deleted file mode 100644 index 6672ad5..0000000 --- a/_old_complex/terminal_state_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "testing" - "time" -) - -func TestTerminalStateManager(t *testing.T) { - // Test the terminal state manager for thread safety - manager := GetGlobalTerminalState() - - if manager == nil { - t.Fatal("GetGlobalTerminalState() should not return nil") - } - - // Test initial state - if manager.IsRaw() { - t.Error("Terminal should not be in raw mode initially") - } - - // Test GetFD with invalid fd - fd := manager.GetFD() - if fd != -1 && fd < 0 { - t.Error("GetFD() should return -1 for uninitialized state") - } - - // Test concurrent access - this is the main race condition test - t.Run("concurrent access", func(t *testing.T) { - done := make(chan bool, 10) - - // Start multiple goroutines that access the terminal state - for i := 0; i < 10; i++ { - go func() { - defer func() { done <- true }() - - // Simulate rapid access to terminal state methods - for j := 0; j < 100; j++ { - manager.IsRaw() - manager.GetFD() - // Note: We can't test MakeRaw/Restore without actual terminal - } - }() - } - - // Wait for all goroutines with timeout - for i := 0; i < 10; i++ { - select { - case <-done: - // Success - case <-time.After(5 * time.Second): - t.Fatal("Timeout waiting for concurrent access test") - } - } - }) -} - -func TestTerminalStateInitialization(t *testing.T) { - // Test that multiple calls to GetGlobalTerminalState return the same instance - manager1 := GetGlobalTerminalState() - manager2 := GetGlobalTerminalState() - - if manager1 != manager2 { - t.Error("GetGlobalTerminalState() should return the same instance (singleton)") - } -} - -func TestTerminalStateManagerMethods(t *testing.T) { - manager := GetGlobalTerminalState() - - // Test that calling methods on uninitialized state doesn't panic - t.Run("safe method calls", func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("Terminal state methods should not panic: %v", r) - } - }() - - manager.IsRaw() - manager.GetFD() - // Note: We avoid testing MakeRaw/Restore as they require actual terminal - }) -} diff --git a/_old_complex/tmux_manager.go b/_old_complex/tmux_manager.go deleted file mode 100644 index 6da8548..0000000 --- a/_old_complex/tmux_manager.go +++ /dev/null @@ -1,415 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "golang.org/x/term" - - "github.com/derekg/ts-ssh/internal/config" - "github.com/derekg/ts-ssh/internal/security" -) - -// TmuxManager handles creating and managing tmux sessions for SSH connections -type TmuxManager struct { - logger *log.Logger - sessionName string - sshUser string - sshKeyPath string - insecureHostKey bool - tempConfigFiles []string // Track temporary config files for cleanup -} - -// NewTmuxManager creates a new tmux session manager -func NewTmuxManager(logger *log.Logger, sshUser, sshKeyPath string, insecureHostKey bool) *TmuxManager { - // Create a unique session name based on timestamp - sessionName := fmt.Sprintf("ts-ssh-%d", time.Now().Unix()) - - return &TmuxManager{ - logger: logger, - sessionName: sessionName, - sshUser: sshUser, - sshKeyPath: sshKeyPath, - insecureHostKey: insecureHostKey, - } -} - -// StartMultiSession creates a tmux session with multiple SSH connections -func (tm *TmuxManager) StartMultiSession(hosts []string) error { - if len(hosts) == 0 { - return fmt.Errorf("no hosts provided") - } - - tm.logger.Printf("Creating tmux session '%s' with %d hosts", tm.sessionName, len(hosts)) - - // Check if tmux is available - if !tm.isTmuxAvailable() { - return fmt.Errorf("tmux is not installed or not available in PATH") - } - - // Kill any existing session with the same name - tm.killExistingSession() - - // Create new tmux session with the first host - firstHost := hosts[0] - err := tm.createInitialSession(firstHost) - if err != nil { - return fmt.Errorf("failed to create initial tmux session: %w", err) - } - - // Add additional hosts as new windows - for i, host := range hosts[1:] { - windowName := fmt.Sprintf("ssh-%d", i+2) - err := tm.addWindow(windowName, host) - if err != nil { - tm.logger.Printf("Warning: failed to add window for %s: %v", host, err) - // Continue with other hosts even if one fails - } - } - - // Set up tmux configuration for better experience - tm.configureTmux() - - // Attach to the session - return tm.attachToSession() -} - -// isTmuxAvailable checks if tmux is installed and available -func (tm *TmuxManager) isTmuxAvailable() bool { - _, err := exec.LookPath("tmux") - return err == nil -} - -// killExistingSession kills any existing tmux session with our name -func (tm *TmuxManager) killExistingSession() { - cmd := exec.Command("tmux", "kill-session", "-t", tm.sessionName) - // Ignore errors - session might not exist - cmd.Run() -} - -// createInitialSession creates the first tmux session with SSH to the first host -func (tm *TmuxManager) createInitialSession(host string) error { - sshCmd, configFile, err := tm.buildSecureSSHCommand(host) - if err != nil { - return err - } - - // Store config file for cleanup - tm.tempConfigFiles = append(tm.tempConfigFiles, configFile) - - // Create new tmux session with SSH command - cmd := exec.Command("tmux", "new-session", "-d", "-s", tm.sessionName, "-n", "ssh-1") - cmd.Env = os.Environ() - - tm.logger.Printf("Creating tmux session with secure command (credentials protected)") - - err = cmd.Run() - if err != nil { - return err - } - - // Send SSH command to the session - return tm.sendKeysToWindow("ssh-1", sshCmd) -} - -// createInitialSessionDryRun creates the first tmux session without sending SSH commands (for testing) -func (tm *TmuxManager) createInitialSessionDryRun(host string) error { - // Create new tmux session (detached, no commands sent) - cmd := exec.Command("tmux", "new-session", "-d", "-s", tm.sessionName, "-n", "ssh-1") - cmd.Env = os.Environ() - - tm.logger.Printf("Creating tmux session (dry run) with command: %s", strings.Join(cmd.Args, " ")) - - return cmd.Run() -} - -// addWindow adds a new window to the tmux session with SSH to the specified host -func (tm *TmuxManager) addWindow(windowName, host string) error { - sshCmd, configFile, err := tm.buildSecureSSHCommand(host) - if err != nil { - return err - } - - // Store config file for cleanup - tm.tempConfigFiles = append(tm.tempConfigFiles, configFile) - - // Create new window - cmd := exec.Command("tmux", "new-window", "-t", tm.sessionName, "-n", windowName) - err = cmd.Run() - if err != nil { - return err - } - - // Send SSH command to the new window - return tm.sendKeysToWindow(windowName, sshCmd) -} - -// sendKeysToWindow sends a command to a specific tmux window -func (tm *TmuxManager) sendKeysToWindow(windowName, command string) error { - // SECURITY: Validate window name to prevent injection - if err := security.ValidateWindowName(windowName); err != nil { - return fmt.Errorf("window name validation failed: %w", err) - } - - // SECURITY: Validate command to prevent injection - if err := security.ValidateCommand(command); err != nil { - return fmt.Errorf("command validation failed: %w", err) - } - - target := fmt.Sprintf("%s:%s", tm.sessionName, windowName) - cmd := exec.Command("tmux", "send-keys", "-t", target, command, "Enter") - - tm.logger.Printf("Sending to window %s: [command validated]", windowName) - - return cmd.Run() -} - -// buildSecureSSHCommand constructs a secure SSH command using temporary config files -// to avoid exposing credentials in process lists -func (tm *TmuxManager) buildSecureSSHCommand(host string) (string, string, error) { - // SECURITY: Validate hostname to prevent command injection - if err := security.ValidateHostname(host); err != nil { - return "", "", fmt.Errorf("hostname validation failed: %w", err) - } - - // Create temporary SSH config file to avoid credential exposure - tempConfigFile, err := tm.createTemporarySSHConfig(host) - if err != nil { - return "", "", fmt.Errorf("failed to create temporary SSH config: %w", err) - } - - // Build command using config file instead of command line args - cmdParts := []string{os.Args[0]} // Our binary path - cmdParts = append(cmdParts, "-F", tempConfigFile) // Use SSH config file - - // SECURITY: Sanitize hostname for shell execution - sanitizedHost := security.SanitizeShellArg(host) - cmdParts = append(cmdParts, sanitizedHost) // Safely escaped hostname - - return strings.Join(cmdParts, " "), tempConfigFile, nil -} - -// createTemporarySSHConfig creates a temporary SSH config file with secure permissions -func (tm *TmuxManager) createTemporarySSHConfig(host string) (string, error) { - // SECURITY: Validate hostname again for config file creation - if err := security.ValidateHostname(host); err != nil { - return "", fmt.Errorf("hostname validation failed: %w", err) - } - - // Generate secure filename for temporary config using multiple sanitization layers - // First, use filepath.Base to strip any path components (prevent directory traversal) - safeHostname := filepath.Base(host) - - // Remove or replace ALL potentially dangerous characters for filesystem safety - // This is more comprehensive than just : and @ - dangerousChars := ":@/\\<>|*?\"'" - for _, char := range dangerousChars { - safeHostname = strings.ReplaceAll(safeHostname, string(char), "_") - } - - // Remove any control characters or non-printable characters - var cleanHostname strings.Builder - for _, r := range safeHostname { - if r >= 32 && r <= 126 { // Only allow printable ASCII - cleanHostname.WriteRune(r) - } else { - cleanHostname.WriteRune('_') - } - } - safeHostname = cleanHostname.String() - - // Ensure we don't start with dangerous characters like dots or hyphens - safeHostname = strings.TrimLeft(safeHostname, ".-_") - if safeHostname == "" { - safeHostname = "host" // Fallback if hostname becomes empty after sanitization - } - - // Limit length to prevent filesystem issues - if len(safeHostname) > config.MaxHostnameLength { - safeHostname = safeHostname[:config.MaxHostnameLength] - } - tempFileName := fmt.Sprintf("/tmp/ts-ssh-config-%s-%s.conf", safeHostname, security.GenerateRandomSuffix()) - - // Create temporary file with secure permissions atomically - tempFile, err := security.CreateSecureFile(tempFileName, 0600) - if err != nil { - return "", fmt.Errorf("failed to create secure temporary SSH config: %w", err) - } - - // Generate SSH config content - config := fmt.Sprintf(`# Temporary SSH config for ts-ssh tmux session -Host %s - User %s -`, host, tm.sshUser) - - if tm.sshKeyPath != "" { - config += fmt.Sprintf(" IdentityFile %s\n", tm.sshKeyPath) - } - - if tm.insecureHostKey { - config += " StrictHostKeyChecking no\n" - config += " UserKnownHostsFile /dev/null\n" - } else { - config += " StrictHostKeyChecking yes\n" - } - - config += " LogLevel QUIET\n" - config += " BatchMode no\n" // Allow password prompts - - // Write config to file - if _, err := tempFile.WriteString(config); err != nil { - tempFile.Close() - os.Remove(tempFile.Name()) - return "", err - } - - tempFile.Close() - return tempFile.Name(), nil -} - -// configureTmux sets up tmux configuration for a better multi-session experience -func (tm *TmuxManager) configureTmux() { - configs := [][]string{ - // Set status bar to show window list - {"set-option", "-t", tm.sessionName, "status", "on"}, - // Enable mouse support - {"set-option", "-t", tm.sessionName, "mouse", "on"}, - // Set window titles to show hostname - {"set-option", "-t", tm.sessionName, "automatic-rename", "on"}, - // Set base index to 1 for easier switching - {"set-option", "-t", tm.sessionName, "base-index", "1"}, - // Enable activity monitoring - {"set-option", "-t", tm.sessionName, "monitor-activity", "on"}, - // Set escape time for better responsiveness - {"set-option", "-t", tm.sessionName, "escape-time", "10"}, - } - - for _, config := range configs { - cmd := exec.Command("tmux", config...) - err := cmd.Run() - if err != nil { - tm.logger.Printf("Warning: failed to set tmux option %v: %v", config, err) - } - } - - // Display helpful message in each window - tm.displayWelcomeMessage() -} - -// displayWelcomeMessage shows a helpful message about tmux controls -func (tm *TmuxManager) displayWelcomeMessage() { - message := "# ts-ssh Multi-Session Mode\\n" + - "# Tmux Controls:\\n" + - "# Ctrl+B n - Next window\\n" + - "# Ctrl+B p - Previous window\\n" + - "# Ctrl+B 1-9 - Switch to window number\\n" + - "# Ctrl+B c - Create new window\\n" + - "# Ctrl+B x - Close current window\\n" + - "# Ctrl+B d - Detach from session\\n" + - "# Ctrl+B ? - Show all key bindings\\n" + - "# Connecting..." - - // Display message in the first window - target := fmt.Sprintf("%s:ssh-1", tm.sessionName) - cmd := exec.Command("tmux", "display-message", "-t", target, "-d", "3000", message) - cmd.Run() // Ignore errors -} - -// attachToSession attaches to the tmux session (this will block until detached) -func (tm *TmuxManager) attachToSession() error { - tm.logger.Printf("Attaching to tmux session '%s'", tm.sessionName) - - // Check if we're in a terminal environment - if !term.IsTerminal(int(os.Stdin.Fd())) { - tm.logger.Printf("Not running in a terminal, cannot attach to tmux session") - fmt.Printf("Tmux session '%s' created successfully!\n", tm.sessionName) - fmt.Printf("To connect manually, run: tmux attach-session -t %s\n", tm.sessionName) - fmt.Printf("Or list sessions with: tmux list-sessions\n") - return nil - } - - // Attach to session - this will transfer control to tmux - cmd := exec.Command("tmux", "attach-session", "-t", tm.sessionName) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - err := cmd.Run() - - // When we get here, user has detached from tmux or session ended - tm.logger.Printf("Detached from tmux session '%s'", tm.sessionName) - - return err -} - -// CleanupSession kills the tmux session and cleans up temporary files -func (tm *TmuxManager) CleanupSession() error { - tm.logger.Printf("Cleaning up tmux session '%s'", tm.sessionName) - - // Kill tmux session - cmd := exec.Command("tmux", "kill-session", "-t", tm.sessionName) - err := cmd.Run() - - // Clean up temporary SSH config files - tm.cleanupTempConfigFiles() - - return err -} - -// cleanupTempConfigFiles removes all temporary SSH config files -func (tm *TmuxManager) cleanupTempConfigFiles() { - for _, configFile := range tm.tempConfigFiles { - if err := os.Remove(configFile); err != nil { - tm.logger.Printf("Warning: failed to remove temporary config file %s: %v", configFile, err) - } else { - tm.logger.Printf("Cleaned up temporary config file: %s", configFile) - } - } - tm.tempConfigFiles = nil -} - -// AddHost adds a new host to the existing tmux session -func (tm *TmuxManager) AddHost(host string) error { - if !tm.isSessionActive() { - return fmt.Errorf("tmux session '%s' is not active", tm.sessionName) - } - - // Find next available window number - windowNum := tm.getNextWindowNumber() - windowName := fmt.Sprintf("ssh-%d", windowNum) - - return tm.addWindow(windowName, host) -} - -// isSessionActive checks if the tmux session is still active -func (tm *TmuxManager) isSessionActive() bool { - cmd := exec.Command("tmux", "has-session", "-t", tm.sessionName) - return cmd.Run() == nil -} - -// getNextWindowNumber finds the next available window number -func (tm *TmuxManager) getNextWindowNumber() int { - cmd := exec.Command("tmux", "list-windows", "-t", tm.sessionName, "-F", "#{window_index}") - output, err := cmd.Output() - if err != nil { - return 1 - } - - // Parse existing window numbers and find the next available - lines := strings.Split(strings.TrimSpace(string(output)), "\n") - maxNum := 0 - for _, line := range lines { - var num int - n, _ := fmt.Sscanf(line, "%d", &num) - if n == 1 && num > maxNum { - maxNum = num - } - } - - return maxNum + 1 -} diff --git a/_old_complex/tsnet_handler.go b/_old_complex/tsnet_handler.go deleted file mode 100644 index 3b1f734..0000000 --- a/_old_complex/tsnet_handler.go +++ /dev/null @@ -1,223 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "log" - "os" - "strings" - "time" - - "tailscale.com/ipn/ipnstate" - "tailscale.com/tsnet" -) - -// init suppresses tsnet logging early unless verbose mode is detected. -// This runs before main() to catch any import-time logging from tsnet. -func init() { - if !isVerboseMode() { - log.SetOutput(io.Discard) - } -} - -// isVerboseMode checks command line arguments and environment variables -// to determine if verbose logging should be enabled. -func isVerboseMode() bool { - // Check command line arguments - for _, arg := range os.Args { - if arg == "-v" || arg == "--verbose" || arg == "-verbose" { - return true - } - // Handle combined flags like -va or --verbose-auth - if strings.HasPrefix(arg, "-v") && len(arg) > 2 { - return true - } - if strings.HasPrefix(arg, "--verbose") { - return true - } - } - - // Check environment variables - return os.Getenv("TS_DEBUG") != "" || os.Getenv("TS_VERBOSE") != "" -} - -// initTsNet initializes the tsnet server and returns the server instance, context, -// current Tailscale status, and any error that occurred. -func initTsNet(tsnetDir string, clientHostname string, logger *log.Logger, tsControlURL string, verbose bool) (*tsnet.Server, context.Context, *ipnstate.Status, error) { - // Re-apply logging configuration to ensure persistence - if !verbose { - log.SetOutput(io.Discard) - } - - // Setup tsnet directory - if err := setupTsNetDir(&tsnetDir, clientHostname, logger); err != nil { - return nil, nil, nil, err - } - - // Create and configure tsnet server - srv := createTsNetServer(tsnetDir, clientHostname, tsControlURL, logger, verbose) - - ctx, cancel := context.WithCancel(context.Background()) - // Ensure server is closed when context is done. - go func() { - <-ctx.Done() - logger.Println("initTsNet: Main context cancelled, ensuring tsnet server is closed.") - if err := srv.Close(); err != nil { - // Log, but don't make this fatal as we might be shutting down anyway. - logger.Printf("initTsNet: Error closing tsnet server: %v", err) - } - cancel() // Ensure cancel is called to clean up resources associated with this context. - }() - - logger.Printf("Initializing tsnet in directory: %s for client %s", tsnetDir, clientHostname) - - // Attempt to bring up the tsnet server. - // srv.Up will block until the server is up or context is cancelled. - if !verbose { - fmt.Fprintf(os.Stderr, "%s\n", T("starting_tailscale_connection")) - } - - status, err := srv.Up(ctx) - if err != nil { - // If context was cancelled, it might be because of a signal during srv.Up. - // AvoidFatalf here if ctx.Err is not nil, as it's an expected shutdown. - if ctx.Err() != nil { - logger.Printf("initTsNet: Context cancelled during srv.Up: %v", ctx.Err()) - return nil, nil, nil, fmt.Errorf("tsnet setup cancelled: %w", ctx.Err()) - } - logger.Fatalf("Failed to bring up tsnet: %v. If authentication is required, run with -v to see the auth URL.", err) - return nil, nil, nil, err // Fatalf will exit, but return for completeness. - } - - // Display auth URL if available from status - if status != nil && status.AuthURL != "" { - displayAuthURL(status.AuthURL) - } - - // It can take a moment for the connection to be fully established and peers to be visible. - // A small delay can improve reliability of fetching peers immediately after Up. - logger.Println("Waiting briefly for Tailscale connection to establish...") - select { - case <-time.After(3 * time.Second): // ConnectionWaitTime - // Continue after delay - case <-ctx.Done(): - logger.Println("initTsNet: Context cancelled while waiting for connection to establish.") - return nil, nil, nil, fmt.Errorf("tsnet setup cancelled during peer wait: %w", ctx.Err()) - } - - // Refresh status to get the most current information - currentStatus := refreshTailscaleStatus(ctx, srv, status, logger) - - return srv, ctx, currentStatus, nil -} - -// setupTsNetDir ensures the tsnet state directory exists and is properly configured. -func setupTsNetDir(tsnetDir *string, clientHostname string, logger *log.Logger) error { - if *tsnetDir == "" { - // Fallback directory name if user.Current() failed in main or not provided. - // This is less ideal as it's not user-specific. - *tsnetDir = clientHostname + "-state-dir" - logger.Printf("Warning: Using default tsnet state directory: %s (consider setting -tsnet-dir)", *tsnetDir) - } - if err := os.MkdirAll(*tsnetDir, 0700); err != nil && !os.IsExist(err) { - logger.Fatalf("Failed to create tsnet state directory %q: %v", *tsnetDir, err) - return err - } - return nil -} - -// createTsNetServer creates and configures a tsnet server with appropriate logging. -func createTsNetServer(tsnetDir, clientHostname, tsControlURL string, logger *log.Logger, verbose bool) *tsnet.Server { - srv := &tsnet.Server{ - Dir: tsnetDir, - Hostname: clientHostname, - ControlURL: tsControlURL, - } - - // Configure logging based on verbose mode - if verbose { - srv.Logf = logger.Printf - srv.UserLogf = logger.Printf - } else { - // Use a filtered logger that only shows auth URLs once - srv.UserLogf = createAuthURLLogger() - srv.Logf = func(string, ...interface{}) {} // Suppress backend logs - } - - return srv -} - -// createAuthURLLogger returns a logging function that filters out noise -// but displays authentication URLs in a clean format. -func createAuthURLLogger() func(string, ...interface{}) { - var authURLShown bool - - return func(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - // Only show messages that contain authentication URLs - if strings.Contains(msg, "https://login.tailscale.com/") && !authURLShown { - // Extract just the URL from the message - if idx := strings.Index(msg, "https://"); idx != -1 { - url := msg[idx:] - // Find the end of the URL (space or newline) - if endIdx := strings.IndexAny(url, " \n\r\t"); endIdx != -1 { - url = url[:endIdx] - } - displayAuthURL(url) - authURLShown = true - } - } - } -} - -// displayAuthURL shows the authentication URL in a clean, consistent format. -func displayAuthURL(url string) { - fmt.Fprintf(os.Stderr, "\n%s\n%s\n\n", T("to_authenticate_visit"), url) -} - -// refreshTailscaleStatus attempts to get the most current Tailscale status -// with retry logic for improved reliability. -func refreshTailscaleStatus(ctx context.Context, srv *tsnet.Server, initialStatus *ipnstate.Status, logger *log.Logger) *ipnstate.Status { - currentStatus := initialStatus - var stateErr error - - for i := 0; i < 3; i++ { // MaxStateRetries - if i > 0 { - logger.Printf("Attempting to refresh Tailscale status (attempt %d/%d)...", i+1, 3) // MaxStateRetries - select { - case <-time.After(1 * time.Second): // StateRetryDelay - // Continue after delay - case <-ctx.Done(): - logger.Println("initTsNet: Context cancelled during status refresh retry.") - return currentStatus // Return what we have - } - } - - // Attempt to get current status after connection is established - client, err := srv.LocalClient() - if err != nil { - stateErr = fmt.Errorf("failed to get local client: %w", err) - logger.Printf("Warning: %v", stateErr) - continue - } - - updatedStatus, err := client.Status(ctx) - if err != nil { - stateErr = fmt.Errorf("failed to get updated status: %w", err) - logger.Printf("Warning: %v", stateErr) - continue - } - - currentStatus = updatedStatus - stateErr = nil - break - } - - if stateErr != nil { - // We have a connection but couldn't refresh status - proceed with initial status - logger.Printf("Warning: Using initial status due to refresh failures: %v", stateErr) - } - - return currentStatus -} diff --git a/_old_complex/utils.go b/_old_complex/utils.go deleted file mode 100644 index ef6ddc1..0000000 --- a/_old_complex/utils.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "fmt" - "log" - "net" - "os" - "strings" - - "github.com/derekg/ts-ssh/internal/security" -) - -// parseTarget takes a string like "host", "host:port", or "[ipv6host]:port" -// and returns the host and port. If no port is specified, it uses defaultSSHPort. -func parseTarget(target string, defaultPort string) (host, port string, err error) { - host = target - port = defaultPort - - if strings.HasPrefix(host, "[") { - endBracketIndex := strings.LastIndex(host, "]") - if endBracketIndex == -1 { - return "", "", fmt.Errorf("mismatched brackets in IPv6 address: %s", host) - } - if len(host) > endBracketIndex+1 && host[endBracketIndex+1] == ':' { - port = host[endBracketIndex+2:] - host = host[1:endBracketIndex] - } else if len(host) > endBracketIndex+1 { - return "", "", fmt.Errorf("unexpected characters after ']' in IPv6 address: %s", host) - } else { - host = host[1:endBracketIndex] - } - } else { - h, p, errSplit := net.SplitHostPort(target) - if errSplit == nil { - host = h - port = p - } else { - if strings.Contains(target, ":") && !strings.HasPrefix(target, "[") { - return "", "", fmt.Errorf(T("invalid_host_port_format"), target, errSplit) - } - } - } - - if host == "" { - return "", "", errors.New(T("hostname_cannot_be_empty")) - } - if port == "" { - port = defaultPort - } - - // SECURITY: Validate extracted components - // Handle case where host might contain user@hostname format - actualHost := host - if strings.Contains(host, "@") { - // Extract just the hostname part for validation - parts := strings.SplitN(host, "@", 2) - if len(parts) == 2 { - // Validate the user part - if err := security.ValidateSSHUser(parts[0]); err != nil { - return "", "", fmt.Errorf("SSH user validation failed: %w", err) - } - actualHost = parts[1] - } - } - - if err := security.ValidateHostname(actualHost); err != nil { - return "", "", fmt.Errorf("hostname validation failed: %w", err) - } - - if err := security.ValidatePort(port); err != nil { - return "", "", fmt.Errorf("port validation failed: %w", err) - } - - return host, port, nil -} - -// promptUserViaTTY prompts the user for input using secure TTY validation. -func promptUserViaTTY(prompt string, logger *log.Logger) (string, error) { - // Try secure TTY access first - result, err := security.PromptUserSecurely(prompt) - if err != nil { - logger.Printf("Warning: Could not use secure TTY for prompt: %v. Falling back to stdin.", err) - fmt.Fprint(os.Stderr, "(secure TTY unavailable, reading from stdin): ") - reader := bufio.NewReader(os.Stdin) - line, errRead := reader.ReadString('\n') - if errRead != nil { - return "", fmt.Errorf("failed to read from stdin fallback: %w", errRead) - } - return strings.ToLower(strings.TrimSpace(line)), nil - } - return strings.ToLower(strings.TrimSpace(result)), nil -} - -// parseScpRemoteArg parses an SCP remote argument string (e.g., "user@host:path" or "host:path") -// It returns the host, path, and user. If user is not in the string, it returns the default SSH user. -func parseScpRemoteArg(remoteArg string, defaultSshUser string) (host, path, user string, err error) { - user = defaultSshUser // Start with the default/flag-provided user - - parts := strings.SplitN(remoteArg, ":", 2) - if len(parts) != 2 || parts[1] == "" { // Ensure path part exists - return "", "", "", fmt.Errorf("%s", T("invalid_scp_remote")) - } - path = parts[1] - hostPart := parts[0] - - if strings.Contains(hostPart, "@") { - // Split user@host - userHostParts := strings.SplitN(hostPart, "@", 2) - if len(userHostParts) != 2 { - return "", "", "", fmt.Errorf("%s", T("invalid_user_host")) - } - user = userHostParts[0] - host = userHostParts[1] - } else { - host = hostPart - } - - if host == "" { - return "", "", "", fmt.Errorf("%s", T("empty_host_scp")) - } - return host, path, user, nil -} - -// validateInsecureMode validates and handles insecure host key verification mode -func validateInsecureMode(insecureHostKey, forceInsecure bool, host, user string) error { - if !insecureHostKey { - return nil - } - - // Display security warnings - fmt.Fprint(os.Stderr, "⚠️ "+T("warning_insecure_mode")+"\n") - fmt.Fprint(os.Stderr, "⚠️ "+T("warning_mitm_vulnerability")+"\n") - fmt.Fprint(os.Stderr, "⚠️ "+T("warning_trusted_networks_only")+"\n") - fmt.Fprint(os.Stderr, "\n") - - if forceInsecure { - fmt.Fprint(os.Stderr, T("insecure_mode_forced")+"\n") - fmt.Fprint(os.Stderr, T("proceeding_with_insecure_connection")+"\n\n") - return nil - } - - // Get user confirmation - response, err := promptUserViaTTY(T("confirm_insecure_connection")+" ", log.New(os.Stderr, "", 0)) - if err != nil { - return fmt.Errorf("%s: %w", T("failed_read_user_input"), err) - } - - if response != "y" && response != "yes" { - return fmt.Errorf("%s", T("connection_cancelled_by_user")) - } - - fmt.Fprint(os.Stderr, T("proceeding_with_insecure_connection")+"\n\n") - return nil -} diff --git a/internal/client/scp/client.go b/internal/client/scp/client.go index d15a139..cb3f0d5 100644 --- a/internal/client/scp/client.go +++ b/internal/client/scp/client.go @@ -16,7 +16,6 @@ import ( sshclient "github.com/derekg/ts-ssh/internal/client/ssh" "github.com/derekg/ts-ssh/internal/config" - "github.com/derekg/ts-ssh/internal/i18n" "github.com/derekg/ts-ssh/internal/security" ) @@ -48,7 +47,7 @@ func HandleCliScp( targetHost, sshUser, localPath, remotePath, isUpload, sshKeyPath) if localPath == "" || remotePath == "" { - return errors.New(i18n.T("scp_empty_path")) + return errors.New("empty local or remote path") } // Ensure defaultSSHPort is accessible. For now, define locally if not shared. @@ -71,7 +70,7 @@ func HandleCliScp( } authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) { - fmt.Print(i18n.T("scp_enter_password", sshUser, targetHost)) + fmt.Printf("Enter password for %s@%s: ", sshUser, targetHost) password, passErr := security.ReadPasswordSecurely() fmt.Println() if passErr != nil { @@ -83,7 +82,7 @@ func HandleCliScp( var hostKeyCallback ssh.HostKeyCallback var hkErr error if insecureHostKey { - logger.Println(i18n.T("scp_host_key_warning")) + logger.Println("WARNING: Skipping host key verification (insecure)") hostKeyCallback = ssh.InsecureIgnoreHostKey() } else { // Call the exported function from ssh_client.go @@ -143,7 +142,7 @@ func HandleCliScp( if errCopy != nil { return fmt.Errorf("CLI SCP: error uploading file: %w", errCopy) } - logger.Println(i18n.T("scp_upload_complete")) + logger.Println("Upload complete") } else { // Download logger.Printf("CLI SCP: Downloading %s@%s:%s to %s", sshUser, targetHost, remotePath, localPath) @@ -166,7 +165,7 @@ func HandleCliScp( } return fmt.Errorf("CLI SCP: error downloading file: %w", errCopy) } - logger.Println(i18n.T("scp_download_complete")) + logger.Println("Download complete") } return nil } diff --git a/internal/client/scp/client_test.go b/internal/client/scp/client_test.go index bf052f2..d1e61de 100644 --- a/internal/client/scp/client_test.go +++ b/internal/client/scp/client_test.go @@ -6,8 +6,6 @@ import ( "log" "os/user" "testing" - - "github.com/derekg/ts-ssh/internal/i18n" ) // TestConstants verifies SCP constants are defined correctly @@ -21,50 +19,6 @@ func TestConstants(t *testing.T) { } } -// TestTranslationFunction tests the T function -func TestTranslationFunction(t *testing.T) { - tests := []struct { - name string - key string - args []interface{} - expected string - }{ - { - name: "empty path message", - key: "scp_empty_path", - args: nil, - expected: "SCP path cannot be empty", - }, - { - name: "password prompt with args", - key: "scp_enter_password", - args: []interface{}{"user", "host"}, - expected: "Enter password for user@host: ", - }, - { - name: "dial via tsnet", - key: "dial_via_tsnet", - args: nil, - expected: "Connecting via tsnet...", - }, - { - name: "unknown key returns key", - key: "unknown_key", - args: nil, - expected: "unknown_key", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := i18n.T(tt.key, tt.args...) - if result != tt.expected { - t.Errorf("T(%s, %v) = %s, want %s", tt.key, tt.args, result, tt.expected) - } - }) - } -} - // TestHandleCliScpValidation tests input validation func TestHandleCliScpValidation(t *testing.T) { // Create silent logger for tests @@ -83,21 +37,21 @@ func TestHandleCliScpValidation(t *testing.T) { localPath: "", remotePath: "/remote/path", expectError: true, - errorSubstring: "SCP path cannot be empty", + errorSubstring: "empty local or remote path", }, { name: "empty remote path", localPath: "/local/path", remotePath: "", expectError: true, - errorSubstring: "SCP path cannot be empty", + errorSubstring: "empty local or remote path", }, { name: "both paths empty", localPath: "", remotePath: "", expectError: true, - errorSubstring: "SCP path cannot be empty", + errorSubstring: "empty local or remote path", }, } @@ -163,7 +117,7 @@ func TestScpErrorHandling(t *testing.T) { } // Error should be about empty paths - if err.Error() != "SCP path cannot be empty" { + if err.Error() != "empty local or remote path" { t.Errorf("Expected validation error, got: %s", err.Error()) } } @@ -195,7 +149,7 @@ func TestScpFunctionSignature(t *testing.T) { if err == nil { t.Error("Expected validation error") } - if err.Error() != "SCP path cannot be empty" { + if err.Error() != "empty local or remote path" { t.Error("Should get validation error with empty path") } } @@ -229,7 +183,7 @@ func TestScpWithSSHKeyPath(t *testing.T) { } // Should be a validation error - if err.Error() != "SCP path cannot be empty" { + if err.Error() != "empty local or remote path" { t.Errorf("Expected validation error, got: %s", err.Error()) } } @@ -262,7 +216,7 @@ func TestScpInsecureMode(t *testing.T) { } // Should be validation error - if err.Error() != "SCP path cannot be empty" { + if err.Error() != "empty local or remote path" { t.Errorf("Expected validation error, got: %s", err.Error()) } } diff --git a/internal/client/ssh/helpers.go b/internal/client/ssh/helpers.go index 96ce2b0..30148ed 100644 --- a/internal/client/ssh/helpers.go +++ b/internal/client/ssh/helpers.go @@ -13,7 +13,6 @@ import ( "github.com/derekg/ts-ssh/internal/config" "github.com/derekg/ts-ssh/internal/crypto/pqc" - "github.com/derekg/ts-ssh/internal/i18n" ) // Constants needed by SSH package @@ -88,7 +87,7 @@ func createSSHConfig(config SSHConnectionConfig) (*ssh.ClientConfig, error) { var hostKeyCallback ssh.HostKeyCallback if config.InsecureHostKey { if config.Logger != nil { - config.Logger.Printf("%s", i18n.T("host_key_warning")) + config.Logger.Printf("WARNING: Skipping host key verification (insecure)") } hostKeyCallback = ssh.InsecureIgnoreHostKey() } else { @@ -151,26 +150,26 @@ func EstablishSSHConnection(srv *tsnet.Server, ctx context.Context, config SSHCo sshTargetAddr := net.JoinHostPort(config.TargetHost, config.TargetPort) if config.Logger != nil { - config.Logger.Printf("%s", i18n.T("dial_via_tsnet")) + config.Logger.Printf("Dialing via tsnet...") } // Dial via tsnet conn, err := srv.Dial(ctx, "tcp", sshTargetAddr) if err != nil { - return nil, fmt.Errorf("%s", i18n.T("dial_failed")) + return nil, fmt.Errorf("tsnet dial failed") } // Establish SSH connection sshConn, chans, reqs, err := ssh.NewClientConn(conn, sshTargetAddr, sshConfig) if err != nil { conn.Close() - return nil, fmt.Errorf("%s: %w", i18n.T("ssh_connection_failed"), err) + return nil, fmt.Errorf("SSH connection failed: %w", err) } client := ssh.NewClient(sshConn, chans, reqs) if config.Logger != nil { - config.Logger.Printf("%s", i18n.T("ssh_connection_established")) + config.Logger.Printf("SSH connection established") } return client, nil diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go deleted file mode 100644 index f67dd5b..0000000 --- a/internal/i18n/i18n.go +++ /dev/null @@ -1,362 +0,0 @@ -package i18n - -import ( - "os" - "strings" - "sync" - - "golang.org/x/text/language" - "golang.org/x/text/message" -) - -// Re-export supported languages from main package -const ( - LangEnglish = "en" - LangSpanish = "es" - LangChinese = "zh" - LangHindi = "hi" - LangArabic = "ar" - LangBengali = "bn" - LangPortuguese = "pt" - LangRussian = "ru" - LangJapanese = "ja" - LangGerman = "de" - LangFrench = "fr" -) - -var ( - // Global printer for internationalization - printer *message.Printer - - // Synchronization for thread-safe access - initI18nOnce sync.Once - printerMu sync.RWMutex - - // Available languages - supportedLanguages = map[string]language.Tag{ - LangEnglish: language.English, - LangSpanish: language.Spanish, - LangChinese: language.Chinese, - LangHindi: language.Hindi, - LangArabic: language.Arabic, - LangBengali: language.Bengali, - LangPortuguese: language.Portuguese, - LangRussian: language.Russian, - LangJapanese: language.Japanese, - LangGerman: language.German, - LangFrench: language.French, - } -) - -// InitI18n initializes the internationalization system thread-safely -func InitI18n(langFlag string) { - // Ensure messages are registered only once across all goroutines - initI18nOnce.Do(func() { - registerInternalMessages() - }) - - // Determine language preference: CLI flag > env var > default - lang := determineLang(langFlag) - - // Get language tag - tag, exists := supportedLanguages[lang] - if !exists { - tag = language.English // fallback to English - } - - // Create printer for the selected language with thread-safe access - printerMu.Lock() - printer = message.NewPrinter(tag) - printerMu.Unlock() -} - -// determineLang determines which language to use based on priority: -// 1. CLI flag (--lang) -// 2. Environment variable (TS_SSH_LANG) -// 3. Standard locale environment variables (LC_ALL, LANG) -// 4. Default (English) -func determineLang(langFlag string) string { - // Check CLI flag first - if langFlag != "" { - return normalizeLanguage(langFlag) - } - - // Check custom environment variable - if envLang := os.Getenv("TS_SSH_LANG"); envLang != "" { - return normalizeLanguage(envLang) - } - - // Check standard locale environment variables - if envLang := os.Getenv("LC_ALL"); envLang != "" { - return normalizeLanguage(envLang) - } - - if envLang := os.Getenv("LANG"); envLang != "" { - return normalizeLanguage(envLang) - } - - // Default to English - return LangEnglish -} - -// normalizeLanguage normalizes language codes to our supported format -func normalizeLanguage(lang string) string { - lang = strings.ToLower(strings.TrimSpace(lang)) - - // Handle common variations - switch { - case strings.HasPrefix(lang, "en") || lang == "english": - return LangEnglish - case strings.HasPrefix(lang, "es") || lang == "spanish" || lang == "español": - return LangSpanish - case strings.HasPrefix(lang, "zh") || lang == "chinese" || lang == "中文": - return LangChinese - case strings.HasPrefix(lang, "hi") || lang == "hindi" || lang == "हिन्दी": - return LangHindi - case strings.HasPrefix(lang, "ar") || lang == "arabic" || lang == "العربية": - return LangArabic - case strings.HasPrefix(lang, "bn") || lang == "bengali" || lang == "বাংলা": - return LangBengali - case strings.HasPrefix(lang, "pt") || lang == "portuguese" || lang == "português": - return LangPortuguese - case strings.HasPrefix(lang, "ru") || lang == "russian" || lang == "русский": - return LangRussian - case strings.HasPrefix(lang, "ja") || lang == "japanese" || lang == "日本語": - return LangJapanese - case strings.HasPrefix(lang, "de") || lang == "german" || lang == "deutsch": - return LangGerman - case strings.HasPrefix(lang, "fr") || lang == "french" || lang == "français": - return LangFrench - default: - return LangEnglish // fallback - } -} - -// T returns a localized string using the global printer thread-safely -func T(key string, args ...interface{}) string { - // Read printer with read lock for concurrent access - printerMu.RLock() - p := printer - printerMu.RUnlock() - - // Initialize if not yet done - if p == nil { - InitI18n("") - printerMu.RLock() - p = printer - printerMu.RUnlock() - } - - // Use local copy to avoid holding lock during sprintf - return p.Sprintf(key, args...) -} - -// registerInternalMessages registers translatable messages used by internal packages -func registerInternalMessages() { - // Security TTY messages - message.SetString(language.English, "tty_path_validation_failed", "TTY path validation failed") - message.SetString(language.Spanish, "tty_path_validation_failed", "Error en la validación de la ruta TTY") - message.SetString(language.Chinese, "tty_path_validation_failed", "TTY路径验证失败") - message.SetString(language.Hindi, "tty_path_validation_failed", "TTY पथ सत्यापन विफल") - message.SetString(language.Arabic, "tty_path_validation_failed", "فشل التحقق من مسار TTY") - message.SetString(language.Bengali, "tty_path_validation_failed", "TTY পথ যাচাইকরণ ব্যর্থ") - message.SetString(language.Portuguese, "tty_path_validation_failed", "Falha na validação do caminho TTY") - message.SetString(language.Russian, "tty_path_validation_failed", "Ошибка проверки пути TTY") - message.SetString(language.Japanese, "tty_path_validation_failed", "TTYパスの検証に失敗しました") - message.SetString(language.German, "tty_path_validation_failed", "TTY-Pfad-Validierung fehlgeschlagen") - message.SetString(language.French, "tty_path_validation_failed", "échec de validation du chemin TTY") - - message.SetString(language.English, "tty_ownership_check_failed", "TTY ownership check failed") - message.SetString(language.Spanish, "tty_ownership_check_failed", "Error en la verificación de propiedad TTY") - message.SetString(language.Chinese, "tty_ownership_check_failed", "TTY所有权检查失败") - message.SetString(language.Hindi, "tty_ownership_check_failed", "TTY स्वामित्व जाँच विफल") - message.SetString(language.Arabic, "tty_ownership_check_failed", "فشل فحص ملكية TTY") - message.SetString(language.Bengali, "tty_ownership_check_failed", "TTY মালিকানা পরীক্ষা ব্যর্থ") - message.SetString(language.Portuguese, "tty_ownership_check_failed", "Falha na verificação de propriedade TTY") - message.SetString(language.Russian, "tty_ownership_check_failed", "Ошибка проверки владения TTY") - message.SetString(language.Japanese, "tty_ownership_check_failed", "TTY所有権チェックに失敗しました") - message.SetString(language.German, "tty_ownership_check_failed", "TTY-Eigentümerschaftsprüfung fehlgeschlagen") - message.SetString(language.French, "tty_ownership_check_failed", "échec de vérification de propriété TTY") - - message.SetString(language.English, "tty_permission_check_failed", "TTY permission check failed") - message.SetString(language.Spanish, "tty_permission_check_failed", "Error en la verificación de permisos TTY") - message.SetString(language.Chinese, "tty_permission_check_failed", "TTY权限检查失败") - message.SetString(language.Hindi, "tty_permission_check_failed", "TTY अनुमति जाँच विफल") - message.SetString(language.Arabic, "tty_permission_check_failed", "فشل فحص صلاحية TTY") - message.SetString(language.Bengali, "tty_permission_check_failed", "TTY অনুমতি পরীক্ষা ব্যর্থ") - message.SetString(language.Portuguese, "tty_permission_check_failed", "Falha na verificação de permissão TTY") - message.SetString(language.Russian, "tty_permission_check_failed", "Ошибка проверки разрешений TTY") - message.SetString(language.Japanese, "tty_permission_check_failed", "TTY権限チェックに失敗しました") - message.SetString(language.German, "tty_permission_check_failed", "TTY-Berechtigungsprüfung fehlgeschlagen") - message.SetString(language.French, "tty_permission_check_failed", "échec de vérification des permissions TTY") - - message.SetString(language.English, "not_running_in_terminal", "not running in terminal") - message.SetString(language.Spanish, "not_running_in_terminal", "no se ejecuta en terminal") - message.SetString(language.Chinese, "not_running_in_terminal", "未在终端中运行") - message.SetString(language.Hindi, "not_running_in_terminal", "टर्मिनल में नहीं चल रहा") - message.SetString(language.Arabic, "not_running_in_terminal", "لا يعمل في المحطة الطرفية") - message.SetString(language.Bengali, "not_running_in_terminal", "টার্মিনালে চলছে না") - message.SetString(language.Portuguese, "not_running_in_terminal", "não está executando no terminal") - message.SetString(language.Russian, "not_running_in_terminal", "не работает в терминале") - message.SetString(language.Japanese, "not_running_in_terminal", "ターミナルで実行されていません") - message.SetString(language.German, "not_running_in_terminal", "läuft nicht im Terminal") - message.SetString(language.French, "not_running_in_terminal", "ne fonctionne pas dans le terminal") - - message.SetString(language.English, "tty_security_validation_failed", "TTY security validation failed") - message.SetString(language.Spanish, "tty_security_validation_failed", "Error en la validación de seguridad TTY") - message.SetString(language.Chinese, "tty_security_validation_failed", "TTY安全验证失败") - message.SetString(language.Hindi, "tty_security_validation_failed", "TTY सुरक्षा सत्यापन विफल") - message.SetString(language.Arabic, "tty_security_validation_failed", "فشل التحقق من أمان TTY") - message.SetString(language.Bengali, "tty_security_validation_failed", "TTY নিরাপত্তা যাচাইকরণ ব্যর্থ") - message.SetString(language.Portuguese, "tty_security_validation_failed", "Falha na validação de segurança TTY") - message.SetString(language.Russian, "tty_security_validation_failed", "Ошибка проверки безопасности TTY") - message.SetString(language.Japanese, "tty_security_validation_failed", "TTYセキュリティ検証に失敗しました") - message.SetString(language.German, "tty_security_validation_failed", "TTY-Sicherheitsvalidierung fehlgeschlagen") - message.SetString(language.French, "tty_security_validation_failed", "échec de validation de sécurité TTY") - - message.SetString(language.English, "failed_open_tty", "failed to open TTY") - message.SetString(language.Spanish, "failed_open_tty", "error al abrir TTY") - message.SetString(language.Chinese, "failed_open_tty", "无法打开TTY") - message.SetString(language.Hindi, "failed_open_tty", "TTY खोलने में विफल") - message.SetString(language.Arabic, "failed_open_tty", "فشل فتح TTY") - message.SetString(language.Bengali, "failed_open_tty", "TTY খুলতে ব্যর্থ") - message.SetString(language.Portuguese, "failed_open_tty", "falha ao abrir TTY") - message.SetString(language.Russian, "failed_open_tty", "не удалось открыть TTY") - message.SetString(language.Japanese, "failed_open_tty", "TTYを開くことができませんでした") - message.SetString(language.German, "failed_open_tty", "TTY konnte nicht geöffnet werden") - message.SetString(language.French, "failed_open_tty", "échec d'ouverture TTY") - - // SSH connection messages - message.SetString(language.English, "host_key_warning", "WARNING: Host key verification is disabled") - message.SetString(language.Spanish, "host_key_warning", "ADVERTENCIA: La verificación de clave de host está deshabilitada") - message.SetString(language.Chinese, "host_key_warning", "警告:主机密钥验证已禁用") - message.SetString(language.Hindi, "host_key_warning", "चेतावनी: होस्ट की सत्यापन अक्षम है") - message.SetString(language.Arabic, "host_key_warning", "تحذير: تحقق مفتاح المضيف معطل") - message.SetString(language.Bengali, "host_key_warning", "সতর্কতা: হোস্ট কী যাচাইকরণ অক্ষম") - message.SetString(language.Portuguese, "host_key_warning", "AVISO: A verificação da chave do host está desabilitada") - message.SetString(language.Russian, "host_key_warning", "ПРЕДУПРЕЖДЕНИЕ: Проверка ключа хоста отключена") - message.SetString(language.Japanese, "host_key_warning", "警告:ホストキーの検証が無効になっています") - message.SetString(language.German, "host_key_warning", "WARNUNG: Host-Schlüssel-Überprüfung ist deaktiviert") - message.SetString(language.French, "host_key_warning", "AVERTISSEMENT : La vérification de la clé d'hôte est désactivée") - - message.SetString(language.English, "dial_via_tsnet", "Connecting via tsnet...") - message.SetString(language.Spanish, "dial_via_tsnet", "Conectando vía tsnet...") - message.SetString(language.Chinese, "dial_via_tsnet", "通过tsnet连接中...") - message.SetString(language.Hindi, "dial_via_tsnet", "tsnet के माध्यम से कनेक्ट हो रहा है...") - message.SetString(language.Arabic, "dial_via_tsnet", "الاتصال عبر tsnet...") - message.SetString(language.Bengali, "dial_via_tsnet", "tsnet এর মাধ্যমে সংযোগ করা হচ্ছে...") - message.SetString(language.Portuguese, "dial_via_tsnet", "Conectando via tsnet...") - message.SetString(language.Russian, "dial_via_tsnet", "Подключение через tsnet...") - message.SetString(language.Japanese, "dial_via_tsnet", "tsnet経由で接続中...") - message.SetString(language.German, "dial_via_tsnet", "Verbindung über tsnet...") - message.SetString(language.French, "dial_via_tsnet", "Connexion via tsnet...") - - message.SetString(language.English, "ssh_handshake", "Performing SSH handshake...") - message.SetString(language.Spanish, "ssh_handshake", "Realizando protocolo SSH...") - message.SetString(language.Chinese, "ssh_handshake", "正在执行SSH握手...") - message.SetString(language.Hindi, "ssh_handshake", "SSH हैंडशेक कर रहा है...") - message.SetString(language.Arabic, "ssh_handshake", "إجراء مصافحة SSH...") - message.SetString(language.Bengali, "ssh_handshake", "SSH হ্যান্ডশেক সম্পাদন করা হচ্ছে...") - message.SetString(language.Portuguese, "ssh_handshake", "Realizando handshake SSH...") - message.SetString(language.Russian, "ssh_handshake", "Выполнение рукопожатия SSH...") - message.SetString(language.Japanese, "ssh_handshake", "SSHハンドシェイクを実行中...") - message.SetString(language.German, "ssh_handshake", "SSH-Handshake wird durchgeführt...") - message.SetString(language.French, "ssh_handshake", "Exécution de la poignée de main SSH...") - - message.SetString(language.English, "dial_failed", "connection failed") - message.SetString(language.Spanish, "dial_failed", "conexión falló") - message.SetString(language.Chinese, "dial_failed", "连接失败") - message.SetString(language.Hindi, "dial_failed", "कनेक्शन विफल") - message.SetString(language.Arabic, "dial_failed", "فشل الاتصال") - message.SetString(language.Bengali, "dial_failed", "সংযোগ ব্যর্থ") - message.SetString(language.Portuguese, "dial_failed", "conexão falhou") - message.SetString(language.Russian, "dial_failed", "соединение не удалось") - message.SetString(language.Japanese, "dial_failed", "接続に失敗しました") - message.SetString(language.German, "dial_failed", "Verbindung fehlgeschlagen") - message.SetString(language.French, "dial_failed", "échec de connexion") - - message.SetString(language.English, "ssh_connection_failed", "SSH connection failed") - message.SetString(language.Spanish, "ssh_connection_failed", "Conexión SSH falló") - message.SetString(language.Chinese, "ssh_connection_failed", "SSH连接失败") - message.SetString(language.Hindi, "ssh_connection_failed", "SSH कनेक्शन विफल") - message.SetString(language.Arabic, "ssh_connection_failed", "فشل اتصال SSH") - message.SetString(language.Bengali, "ssh_connection_failed", "SSH সংযোগ ব্যর্থ") - message.SetString(language.Portuguese, "ssh_connection_failed", "Conexão SSH falhou") - message.SetString(language.Russian, "ssh_connection_failed", "SSH соединение не удалось") - message.SetString(language.Japanese, "ssh_connection_failed", "SSH接続に失敗しました") - message.SetString(language.German, "ssh_connection_failed", "SSH-Verbindung fehlgeschlagen") - message.SetString(language.French, "ssh_connection_failed", "échec de connexion SSH") - - message.SetString(language.English, "ssh_connection_established", "SSH connection established") - message.SetString(language.Spanish, "ssh_connection_established", "Conexión SSH establecida") - message.SetString(language.Chinese, "ssh_connection_established", "SSH连接已建立") - message.SetString(language.Hindi, "ssh_connection_established", "SSH कनेक्शन स्थापित") - message.SetString(language.Arabic, "ssh_connection_established", "تم تأسيس اتصال SSH") - message.SetString(language.Bengali, "ssh_connection_established", "SSH সংযোগ প্রতিষ্ঠিত") - message.SetString(language.Portuguese, "ssh_connection_established", "Conexão SSH estabelecida") - message.SetString(language.Russian, "ssh_connection_established", "SSH соединение установлено") - message.SetString(language.Japanese, "ssh_connection_established", "SSH接続が確立されました") - message.SetString(language.German, "ssh_connection_established", "SSH-Verbindung hergestellt") - message.SetString(language.French, "ssh_connection_established", "Connexion SSH établie") - - // SCP operation messages - message.SetString(language.English, "scp_empty_path", "SCP path cannot be empty") - message.SetString(language.Spanish, "scp_empty_path", "La ruta SCP no puede estar vacía") - message.SetString(language.Chinese, "scp_empty_path", "SCP路径不能为空") - message.SetString(language.Hindi, "scp_empty_path", "SCP पथ खाली नहीं हो सकता") - message.SetString(language.Arabic, "scp_empty_path", "مسار SCP لا يمكن أن يكون فارغاً") - message.SetString(language.Bengali, "scp_empty_path", "SCP পথ খালি থাকতে পারে না") - message.SetString(language.Portuguese, "scp_empty_path", "O caminho SCP não pode estar vazio") - message.SetString(language.Russian, "scp_empty_path", "Путь SCP не может быть пустым") - message.SetString(language.Japanese, "scp_empty_path", "SCPパスは空にできません") - message.SetString(language.German, "scp_empty_path", "SCP-Pfad darf nicht leer sein") - message.SetString(language.French, "scp_empty_path", "Le chemin SCP ne peut pas être vide") - - message.SetString(language.English, "scp_enter_password", "Enter password for %s@%s: ") - message.SetString(language.Spanish, "scp_enter_password", "Ingrese contraseña para %s@%s: ") - message.SetString(language.Chinese, "scp_enter_password", "为 %s@%s 输入密码: ") - message.SetString(language.Hindi, "scp_enter_password", "%s@%s के लिए पासवर्ड दर्ज करें: ") - message.SetString(language.Arabic, "scp_enter_password", "أدخل كلمة المرور لـ %s@%s: ") - message.SetString(language.Bengali, "scp_enter_password", "%s@%s এর জন্য পাসওয়ার্ড লিখুন: ") - message.SetString(language.Portuguese, "scp_enter_password", "Digite a senha para %s@%s: ") - message.SetString(language.Russian, "scp_enter_password", "Введите пароль для %s@%s: ") - message.SetString(language.Japanese, "scp_enter_password", "%s@%s のパスワードを入力してください: ") - message.SetString(language.German, "scp_enter_password", "Passwort für %s@%s eingeben: ") - message.SetString(language.French, "scp_enter_password", "Entrez le mot de passe pour %s@%s: ") - - message.SetString(language.English, "scp_host_key_warning", "WARNING: SCP host key verification disabled") - message.SetString(language.Spanish, "scp_host_key_warning", "ADVERTENCIA: Verificación de clave de host SCP deshabilitada") - message.SetString(language.Chinese, "scp_host_key_warning", "警告:SCP主机密钥验证已禁用") - message.SetString(language.Hindi, "scp_host_key_warning", "चेतावनी: SCP होस्ट की सत्यापन अक्षम") - message.SetString(language.Arabic, "scp_host_key_warning", "تحذير: تحقق مفتاح مضيف SCP معطل") - message.SetString(language.Bengali, "scp_host_key_warning", "সতর্কতা: SCP হোস্ট কী যাচাইকরণ অক্ষম") - message.SetString(language.Portuguese, "scp_host_key_warning", "AVISO: Verificação de chave de host SCP desabilitada") - message.SetString(language.Russian, "scp_host_key_warning", "ПРЕДУПРЕЖДЕНИЕ: Проверка ключа хоста SCP отключена") - message.SetString(language.Japanese, "scp_host_key_warning", "警告:SCPホストキーの検証が無効になっています") - message.SetString(language.German, "scp_host_key_warning", "WARNUNG: SCP-Host-Schlüssel-Überprüfung ist deaktiviert") - message.SetString(language.French, "scp_host_key_warning", "AVERTISSEMENT : La vérification de la clé d'hôte SCP est désactivée") - - message.SetString(language.English, "scp_upload_complete", "Upload complete") - message.SetString(language.Spanish, "scp_upload_complete", "Carga completada") - message.SetString(language.Chinese, "scp_upload_complete", "上传完成") - message.SetString(language.Hindi, "scp_upload_complete", "अपलोड पूर्ण") - message.SetString(language.Arabic, "scp_upload_complete", "اكتمل الرفع") - message.SetString(language.Bengali, "scp_upload_complete", "আপলোড সম্পন্ন") - message.SetString(language.Portuguese, "scp_upload_complete", "Upload concluído") - message.SetString(language.Russian, "scp_upload_complete", "Загрузка завершена") - message.SetString(language.Japanese, "scp_upload_complete", "アップロード完了") - message.SetString(language.German, "scp_upload_complete", "Upload abgeschlossen") - message.SetString(language.French, "scp_upload_complete", "Téléchargement terminé") - - message.SetString(language.English, "scp_download_complete", "Download complete") - message.SetString(language.Spanish, "scp_download_complete", "Descarga completada") - message.SetString(language.Chinese, "scp_download_complete", "下载完成") - message.SetString(language.Hindi, "scp_download_complete", "डाउनलोड पूर्ण") - message.SetString(language.Arabic, "scp_download_complete", "اكتمل التنزيل") - message.SetString(language.Bengali, "scp_download_complete", "ডাউনলোড সম্পন্ন") - message.SetString(language.Portuguese, "scp_download_complete", "Download concluído") - message.SetString(language.Russian, "scp_download_complete", "Загрузка завершена") - message.SetString(language.Japanese, "scp_download_complete", "ダウンロード完了") - message.SetString(language.German, "scp_download_complete", "Download abgeschlossen") - message.SetString(language.French, "scp_download_complete", "Téléchargement terminé") -} diff --git a/internal/security/tty.go b/internal/security/tty.go index ed1e0c1..1ed3a7a 100644 --- a/internal/security/tty.go +++ b/internal/security/tty.go @@ -6,8 +6,6 @@ import ( "os" "golang.org/x/term" - - "github.com/derekg/ts-ssh/internal/i18n" ) // getSecureTTY validates and opens a secure TTY connection @@ -15,7 +13,7 @@ import ( func getSecureTTY() (*os.File, error) { // First, verify we're running in a real terminal if !term.IsTerminal(int(os.Stdin.Fd())) { - return nil, errors.New(i18n.T("not_running_in_terminal")) + return nil, errors.New("not running in a terminal") } // Get TTY path with validation @@ -26,13 +24,13 @@ func getSecureTTY() (*os.File, error) { // Validate TTY security before opening if err := validateTTYSecurity(ttyPath); err != nil { - return nil, fmt.Errorf(i18n.T("tty_security_validation_failed"), err) + return nil, fmt.Errorf("TTY security validation failed: %w", err) } // Open TTY with explicit permissions check ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) if err != nil { - return nil, fmt.Errorf(i18n.T("failed_open_tty"), err) + return nil, fmt.Errorf("failed to open TTY: %w", err) } // Additional security check after opening diff --git a/main_simple.go.bak b/main_simple.go.bak deleted file mode 100644 index 36ba60e..0000000 --- a/main_simple.go.bak +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - "github.com/derekg/ts-ssh/internal/security" -) - -// version is set at build time via -ldflags "-X main.version=..."; default is "dev". -var version = "dev" - -func main() { - // Initialize security audit logging early (if enabled via environment variables) - if err := security.InitSecurityLogger(); err != nil { - fmt.Fprintf(os.Stderr, "Warning: Failed to initialize security audit logging: %v\n", err) - } - // Ensure security logger is properly closed on exit - defer security.CloseSecurityLogger() - - // Create the CLI application - cli := NewCLI() - - // Handle special case for backwards compatibility: if first arg looks like a target, - // and no subcommand is specified, default to connect command - if len(os.Args) > 1 && !isSubcommand(os.Args[1]) { - // Insert "connect" as the first argument to maintain compatibility - args := []string{os.Args[0], "connect"} - args = append(args, os.Args[1:]...) - os.Args = args - } - - // Run the CLI - ctx := context.Background() - if err := cli.Run(ctx, os.Args[1:]); err != nil { - log.Fatalf("Error: %v", err) - } -} - -// isSubcommand checks if the given argument is a known subcommand -func isSubcommand(arg string) bool { - subcommands := []string{ - "connect", "scp", "list", "exec", "multi", "config", "pqc", "version", - "help", "-h", "--help", "-v", "--version", - } - - for _, cmd := range subcommands { - if arg == cmd { - return true - } - } - - return false -} \ No newline at end of file diff --git a/test-version b/test-version deleted file mode 100755 index 4bed377..0000000 Binary files a/test-version and /dev/null differ diff --git a/test-version-fixed b/test-version-fixed deleted file mode 100755 index 4bed377..0000000 Binary files a/test-version-fixed and /dev/null differ