### Minimal Wish Server Setup Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/README.md Example of creating and running a minimal Wish server on a specific address. ```go s, _ := wish.NewServer( wish.WithAddress(":2222"), ) s.ListenAndServe() ``` -------------------------------- ### Create Full Test Environment with New Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Use `testsession.New` to create a complete test environment. It starts a local SSH server, connects a client, and returns the client session, automatically cleaning up all resources. Panics if any setup fails. ```Go package main import ( testing "testing" wish "charm.land/wish/v2" testsession "charm.land/wish/v2/testsession" gossh "golang.org/x/crypto/ssh" ) func TestSSHServer(t *testing.T) { srv, err := wish.NewServer( wish.WithMiddleware(myMiddleware), ) if err != nil { t.Fatal(err) } sess := testsession.New(t, srv, nil) // Use the session output, err := sess.Output("echo hello") if err != nil { t.Fatal(err) } if string(output) != "hello\n" { t.Fatalf("unexpected output: %s", output) } } ``` -------------------------------- ### Start Test SSH Server with Listen Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Use `testsession.Listen` to start a test SSH server on a random local port and get its address. The server runs in a goroutine and cleanup is registered with `tb.Cleanup`. Panics if the server fails to start. ```Go srv, _ := wish.NewServer() addr := testsession.Listen(t, srv) // addr is "127.0.0.1:54321" or similar ``` -------------------------------- ### Full Command Execution Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/02-command-execution.md A complete example showing how to handle a connection, execute a command like 'cat /etc/hostname', and log success or failure. ```go package main import ( "log" "net" "charm.land/wish/v2" ) func handleConnection(sess ssh.Session) { cmd := wish.Command(sess, "cat", "/etc/hostname") if err := cmd.Run(); err != nil { wish.Fatalf(sess, "Error: %v\n", err) return } wish.Println(sess, "Command completed successfully") } ``` -------------------------------- ### Wish Git Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/08-git.md An example demonstrating how to set up a Wish server with the Git middleware. It includes custom authorization and notification hooks. ```go package main import ( "net" "charm.land/wish/v2" "charm.land/wish/v2/git" "github.com/charmbracelet/ssh" ) type MyHooks struct { adminKey []byte } func (h *MyHooks) AuthRepo(repo string, key ssh.PublicKey) git.AccessLevel { if bytes.Equal(key.Marshal(), h.adminKey) { return git.AdminAccess } return git.ReadOnlyAccess } func (h *MyHooks) Push(repo string, key ssh.PublicKey) { log.Printf("Push to %s by %x\n", repo, key.Marshal()) } func (h *MyHooks) Fetch(repo string, key ssh.PublicKey) { log.Printf("Fetch from %s by %x\n", repo, key.Marshal()) } func main() { hooks := &MyHooks{ adminKey: mustParseKey("ssh-ed25519 AAAA..."), } s := wish.NewServer( wish.WithAddress(net.JoinHostPort("localhost", "2222")), wish.WithMiddleware( git.Middleware("/var/git/repos", hooks), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### GetInfo Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md Example demonstrating how to use GetInfo to parse an SCP command and access its operation details. ```go info := scp.GetInfo([]string{"scp", "-r", "-f", "/etc"}) // info.Ok = true // info.Recursive = true // info.Op = OpCopyToClient // info.Path = "/etc" ``` -------------------------------- ### Create New Wish Server Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/README.md Example of creating a new Wish server with a specified address. This is a common starting point for setting up a Wish server instance. ```go s, err := wish.NewServer( wish.WithAddress("localhost:2222"), ) ``` -------------------------------- ### FileSystemHandler Usage Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md Example of creating a FileSystemHandler for SCP operations and integrating it with a Wish server. ```go handler := scp.NewFileSystemHandler("/var/scp-root") s, err := wish.NewServer( wish.WithMiddleware( scp.Middleware(handler, handler), ), ) ``` -------------------------------- ### SCP Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md Example of setting up a Wish server with SCP middleware for file transfers. It uses NewFileSystemHandler to create a handler for the local filesystem. ```go package main import ( "net" "charm.land/wish/v2" "charm.land/wish/v2/scp" "charm.land/wish/v2/logging" ) func main() { handler := scp.NewFileSystemHandler("/home/user/uploads") s := wish.NewServer( wish.WithAddress(net.JoinHostPort("localhost", "2222")), wish.WithMiddleware( logging.Middleware(), scp.Middleware(handler, handler), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### Bubble Tea Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/07-bubbletea.md An example demonstrating how to set up a Wish server with the bubbletea.Middleware. It defines a simple Bubble Tea model and a handler to serve it over SSH on port 2222. ```go package main import ( "net" tea "charm.land/bubbletea/v2" "charm.land/wish/v2" "charm.land/wish/v2/bubbletea" "charm.land/wish/v2/activeterm" "github.com/charmbracelet/ssh" ) type Model struct { counter int } func (m Model) Init() tea.Cmd { return nil } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case tea.KeyMsg: return m, tea.Quit } return m, nil } func (m Model) View() string { return "Press any key to exit\n" } func teaHandler(sess ssh.Session) (tea.Model, []tea.ProgramOption) { return &Model{}, []tea.ProgramOption{} } func main() { s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("localhost", "2222")), wish.WithMiddleware( bubbletea.Middleware(teaHandler), activeterm.Middleware(), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### Git Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Sets up a complete Git server with custom access control hooks. Requires SSH keys for authentication and defines access levels for repositories. ```go package main import ( "bytes" "net" "charm.land/wish/v2" "charm.land/wish/v2/git" "charm.land/wish/v2/logging" "github.com/charmbracelet/ssh" ) type MyGitHooks struct { adminKey []byte } func (h *MyGitHooks) AuthRepo(repo string, key ssh.PublicKey) git.AccessLevel { if key == nil { return git.NoAccess } if bytes.Equal(key.Marshal(), h.adminKey) { return git.AdminAccess } return git.ReadOnlyAccess } func (h *MyGitHooks) Push(repo string, key ssh.PublicKey) { // Trigger CI/CD, webhooks, etc. log.Printf("Push to %s", repo) } func (h *MyGitHooks) Fetch(repo string, key ssh.PublicKey) { log.Printf("Fetch from %s", repo) } func main() { hooks := &MyGitHooks{ // Set adminKey from parsed SSH key } s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("0.0.0.0", "2222")), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithMiddleware( logging.Middleware(), git.Middleware("/var/git/repos", hooks), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### Testing Wish Servers Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/README.md Example of setting up a test server and session for Wish functionality. ```go srv, _ := wish.NewServer(wish.WithMiddleware(myMiddleware)) sess := testsession.New(t, srv, nil) ``` -------------------------------- ### Middleware Stack Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/05-accesscontrol.md This example shows how to integrate the accesscontrol middleware with other middleware like logging, git, and scp to create a layered security and functionality stack. ```go wish.WithMiddleware( logging.Middleware(), accesscontrol.Middleware("git-receive-pack", "git-upload-pack", "scp"), gitMiddleware(), scpMiddleware(), ) ``` -------------------------------- ### Basic SSH Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Sets up a simple Wish SSH server that listens on a specified address and accepts all incoming connections. It includes graceful shutdown handling. ```go package main import ( "errors" "log" "net" "os" "os/signal" "syscall" "charm.land/wish/v2" "github.com/charmbracelet/ssh" ) func main() { s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("localhost", "2222")), ) if err != nil { log.Fatal(err) } done := make(chan os.Signal, 1) signal.Notify(done, os.Interrupt, syscall.SIGTERM) go func() { log.Println("SSH server listening on", s.Addr) if err := s.ListenAndServe(); err != nil && !errors.Is(err, ssh.ErrServerClosed) { log.Fatalf("Server error: %v", err) } }() <-done log.Println("Shutting down...") s.Close() } ``` -------------------------------- ### Quick Start SSH Server Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/00-table-of-contents.md A basic template for starting an SSH server using Wish v2. It configures the server address, host key path, and includes logging middleware. ```go package main import ( "log" "net" "charm.land/wish/v2" "charm.land/wish/v2/logging" ) func main() { s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("0.0.0.0", "2222")), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithMiddleware( logging.Middleware(), ), ) if err != nil { log.Fatal(err) } log.Println("Starting SSH server on", s.Addr) log.Fatal(s.ListenAndServe()) } ``` -------------------------------- ### Rate-Limited Server Setup Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Configure a Wish server with rate limiting to protect against connection floods. This example sets up a limiter for 10 connections per second per IP with a burst of 5, tracking up to 10,000 IPs. ```go package main import ( "net" time "time" "charm.land/wish/v2" "charm.land/wish/v2/logging" "charm.land/wish/v2/ratelimiter" "golang.org/x/time/rate" ) func main() { limiter := ratelimiter.NewRateLimiter( rate.Every(100*time.Millisecond), // 10 conn/sec per IP 5, // burst 5 10000, // track 10k IPs ) s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("0.0.0.0", "2222")), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithMiddleware( logging.Middleware(), ratelimiter.Middleware(limiter), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### New Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Creates a complete test environment by starting a local SSH server, connecting a client, and returning the client session. It automatically cleans up all resources upon completion. ```APIDOC ## New ### Description Creates a complete test environment: starts a local SSH server, connects a client, and returns the client session. Automatically cleans up all resources. ### Function Signature ```go func New(tb testing.TB, srv *ssh.Server, cfg *gossh.ClientConfig) *gossh.Session ``` ### Parameters #### Path Parameters - **tb** (testing.TB) - Required - Test context (from *testing.T or *testing.B) - **srv** (*ssh.Server) - Required - Configured SSH server to run - **cfg** (*gossh.ClientConfig) - Optional - Client configuration (nil uses defaults) ### Returns - **(*gossh.Session)** - A connected SSH client session. ### Behavior - Starts a local SSH server listening on a random port - Connects an SSH client to it - Registers cleanup with `tb.Cleanup` to close everything - Panics if any setup fails ### Example ```go package main import ( t "testing" "charm.land/wish/v2" "charm.land/wish/v2/testsession" gossh "golang.org/x/crypto/ssh" ) func TestSSHServer(t *testing.T) { srv, err := wish.NewServer( wish.WithMiddleware(myMiddleware), ) if err != nil { t.Fatal(err) } sess := testsession.New(t, srv, nil) // Use the session output, err := sess.Output("echo hello") if err != nil { t.Fatal(err) } if string(output) != "hello\n" { t.Fatalf("unexpected output: %s", output) } } ``` ``` -------------------------------- ### Public Key Authentication Setup Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Configure a Wish server to use public key authentication. Ensure the authorized_keys file is correctly specified. ```go s, err := wish.NewServer( wish.WithAddress(":2222"), wish.WithAuthorizedKeys("/home/user/.ssh/authorized_keys"), ) ``` -------------------------------- ### Unstructured Log Output Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/06-logging.md Provides an example of unstructured log output for a user connecting and disconnecting. ```text alice connect 192.168.1.10:54321 true [git-upload-pack /repo.git] xterm 120 40 SSH-2.0-Git/2.30 192.168.1.10:54321 disconnect 5s231ms ``` -------------------------------- ### Basic Test Structure with Wish Server and Client Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Demonstrates a typical test setup for a Wish application. It shows how to create a server with middleware, establish a client session, and test command execution and output. ```go func TestMyApp(t *testing.T) { // Create server with middleware srv, err := wish.NewServer( wish.WithMiddleware(myMiddleware()), ) if err != nil { t.Fatal(err) } // Create client session sess := testsession.New(t, srv, nil) // Test command execution output, err := sess.Output("mycommand arg1") if err != nil { t.Fatal(err) } if !strings.Contains(string(output), "expected") { t.Errorf("output mismatch: %s", output) } } ``` -------------------------------- ### Listen Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Starts a test SSH server on a random local port and returns its address. Resources are automatically cleaned up. ```APIDOC ## Listen ### Description Starts a test SSH server on a random local port and returns its address. ### Function Signature ```go func Listen(tb testing.TB, srv *ssh.Server) string ``` ### Parameters #### Path Parameters - **tb** (testing.TB) - Required - Test context - **srv** (*ssh.Server) - Required - Server to run ### Returns - **string** - Address in format "127.0.0.1:12345". ### Behavior - Listens on IPv4 or IPv6 - Runs the server in a goroutine - Registers cleanup with `tb.Cleanup` - Panics if server fails ### Example ```go srv, _ := wish.NewServer() addr := testsession.Listen(t, srv) // addr is "127.0.0.1:54321" or similar ``` ``` -------------------------------- ### Certificate Authentication Setup Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Configure a Wish server to use certificate authentication. Specify the path to the trusted user CA public key. ```go s, err := wish.NewServer( wish.WithAddress(":2222"), wish.WithTrustedUserCAKeys("/etc/ssh/ca.pub"), ) ``` -------------------------------- ### Example Usage of EnsureRepo Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/08-git.md Demonstrates how to use the EnsureRepo function to ensure a Git repository exists, creating it if necessary. The parent directory will be created if it does not exist. ```go err := git.EnsureRepo("/var/git/repos", "myproject.git") if err != nil { log.Fatal(err) } ``` -------------------------------- ### Example: Implement AuthRepo Hook Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/08-git.md Custom implementation of the AuthRepo hook to determine user access based on public key. This example checks for admin, write, and read permissions. ```go func (h *MyHooks) AuthRepo(repo string, key ssh.PublicKey) git.AccessLevel { // Check if user is admin if isAdmin(key.Marshal()) { return git.AdminAccess } // Check if user can write if canWrite(repo, key.Marshal()) { return git.ReadWriteAccess } // Check if user can read if canRead(repo, key.Marshal()) { return git.ReadOnlyAccess } return git.NoAccess } ``` -------------------------------- ### SCP File Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Sets up a server that supports secure file transfers using SCP. It uses a file system handler to manage file operations on a specified directory. ```go package main import ( "net" "charm.land/wish/v2" "charm.land/wish/v2/logging" "charm.land/wish/v2/scp" ) func main() { handler := scp.NewFileSystemHandler("/home/user/uploads") s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("0.0.0.0", "2222")), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithMiddleware( logging.Middleware(), scp.Middleware(handler, handler), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### Custom SCP Handler Implementation Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md Provides an example of implementing the `Handler` interface for custom storage solutions, such as a database. ```go type MyHandler struct { db *Database } func (h *MyHandler) Glob(sess ssh.Session, pattern string) ([]string, error) { return h.db.Find(pattern) } func (h *MyHandler) WalkDir(sess ssh.Session, path string, fn fs.WalkDirFunc) error { return h.db.Walk(path, fn) } func (h *MyHandler) NewDirEntry(sess ssh.Session, name string) (*scp.DirEntry, error) { entry := h.db.GetDir(name) return &scp.DirEntry{ Name: entry.Name, Filepath: entry.Path, Mode: 0755, Mtime: entry.Modified, Atime: entry.Modified, }, } func (h *MyHandler) NewFileEntry(sess ssh.Session, name string) (*scp.FileEntry, func() error, error) { file := h.db.GetFile(name) reader := file.OpenReader() return &scp.FileEntry{ Name: file.Name, Filepath: file.Path, Mode: 0644, Size: file.Size, Reader: reader, Mtime: file.Modified, Atime: file.Modified, }, reader.Close, nil } // ... implement Mkdir and Write ``` -------------------------------- ### Listen and Serve Wish Server Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md This snippet shows how to start the Wish server and handle potential errors during the listening process. Ensure proper error handling for production environments. ```go import "log" // Listen and serve if err := s.ListenAndServe(); err != nil { log.Fatal(err) } ``` -------------------------------- ### Start Wish App with SystemD Source: https://github.com/charmbracelet/wish/blob/main/README.md Command to start, restart, or stop a Wish application managed by SystemD. Replace 'myapp' with your service name. ```bash sudo systemctl start myapp ``` -------------------------------- ### Bubble Tea TUI Application Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Serves a Bubble Tea TUI application over SSH. This example demonstrates integrating a TUI application with Wish using specific middleware. ```go package main import ( "net" tea "charm.land/bubbletea/v2" "charm.land/wish/v2" "charm.land/wish/v2/activeterm" "charm.land/wish/v2/bubbletea" "charm.land/wish/v2/logging" "github.com/charmbracelet/ssh" ) type AppModel struct { counter int } func (m AppModel) Init() tea.Cmd { return nil } func (m AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case tea.KeyMsg: return m, tea.Quit } return m, nil } func (m AppModel) View() string { return "Counter: " + string(rune(m.counter)) + "\nPress q to quit\n" } func appHandler(sess ssh.Session) (tea.Model, []tea.ProgramOption) { return &AppModel{}, []tea.ProgramOption{} } func main() { s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("localhost", "2222")), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithMiddleware( logging.Middleware(), bubbletea.Middleware(appHandler), activeterm.Middleware(), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### SCP Middleware Integration Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md Demonstrates how to integrate the SCP middleware with other Wish middleware components like logging, rate limiting, and access control. ```go wish.WithMiddleware( logging.Middleware(), ratelifimiter.Middleware(limiter), accesscontrol.Middleware("scp"), scp.Middleware(handler, handler), ) ``` -------------------------------- ### Structured Log Output Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/06-logging.md Shows the structured log output for connection and disconnection events, including user and command details. ```text [INFO] connect user=alice remote-addr=192.168.1.10:54321 public-key=true command=[git-upload-pack /repo.git] term=xterm width=120 height=40 client-version=SSH-2.0-Git/2.30 [INFO] disconnect user=alice remote-addr=192.168.1.10:54321 duration=5s231ms ``` -------------------------------- ### Example Git Middleware Stack Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/08-git.md Demonstrates how to integrate the Git middleware with other common middleware components like logging, rate limiting, and access control in a Wish application. ```go wish.WithMiddleware( logging.Middleware(), ratellimiter.Middleware(limiter), accesscontrol.Middleware("git-upload-pack", "git-receive-pack"), git.Middleware("/var/git", hooks), ) ``` -------------------------------- ### Structured Log Format Example (JSON) Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/06-logging.md Illustrates the machine-readable JSON format for detailed logging, including connection parameters. ```json { "level": "info", "msg": "connect", "user": "alice", "remote-addr": "127.0.0.1:12345", "public-key": true, "command": ["git-upload-pack", "/repo.git"], "term": "xterm-256color", "width": 80, "height": 24, "client-version": "SSH-2.0-OpenSSH_8.2p1" } ``` -------------------------------- ### Unstructured Log Format Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/06-logging.md Shows the basic, human-readable log format for connection events. ```text user connect 127.0.0.1:12345 true [git-upload-pack /repo.git] xterm-256color 80 24 SSH-2.0-OpenSSH_8.2p1 127.0.0.1:12345 disconnect 2s450ms ``` -------------------------------- ### Custom Program Handler Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/07-bubbletea.md An example of a custom ProgramHandler that creates and configures a tea.Program instance. This handler provides direct control over the program's input and output streams, connected to the SSH session. ```go func customProgramHandler(sess ssh.Session) *tea.Program { model := &Model{} program := tea.NewProgram( model, tea.WithInput(sess), tea.WithOutput(sess), // ... other options ) return program } s, err := wish.NewServer( wish.WithMiddleware( bubbletea.MiddlewareWithProgramHandler(customProgramHandler), ), ) ``` -------------------------------- ### Password Authentication Setup Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Configure a Wish server to use password authentication. A custom function validates the provided password. ```go s, err := wish.NewServer( wish.WithAddress(":2222"), wish.WithPasswordAuth(func(ctx ssh.Context, password string) bool { return password == "secret" }), ) ``` -------------------------------- ### Configure Access Control Middleware Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/05-accesscontrol.md This example demonstrates how to set up a Wish server with the accesscontrol middleware to allow only specific Git commands. It also includes logging middleware. ```go package main import ( "net" "charm.land/wish/v2" "charm.land/wish/v2/accesscontrol" "charm.land/wish/v2/logging" ) func main() { s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("localhost", "2222")), wish.WithMiddleware( accesscontrol.Middleware("git-upload-pack", "git-receive-pack"), logging.Middleware(), ), ) if err != nil { panic(err) } s.ListenAndServe() } ``` -------------------------------- ### Testing a Wish Server Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Use Wish's test utilities to create a test session and run commands against a server. This example verifies an 'echo' command. ```go package main import ( t "testing" "charm.land/wish/v2" "charm.land/wish/v2/testsession" ) func TestServer(t *testing.T) { srv, err := wish.NewServer( wish.WithMiddleware(echoMiddleware()), ) if err != nil { t.Fatal(err) } sess := testsession.New(t, srv, nil) output, err := sess.Output("echo hello") if err != nil { t.Fatal(err) } expected := "hello\n" if string(output) != expected { t.Fatalf("expected %q, got %q", expected, output) } } func echoMiddleware() wish.Middleware { return func(next ssh.Handler) ssh.Handler { return func(s ssh.Session) { cmd := wish.Command(s, "echo", strings.Join(s.Command(), " ")) cmd.Run() } } } ``` -------------------------------- ### Interactive Shell Server Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/12-examples.md Configures a Wish SSH server to provide interactive shell access. It uses middleware for logging and a custom shell handler that executes 'bash'. ```go package main import ( "net" "charm.land/wish/v2" "charm.land/wish/v2/logging" ) func main() { s, err := wish.NewServer( wish.WithAddress(net.JoinHostPort("0.0.0.0", "2222")), wish.WithHostKeyPath(".ssh/id_ed25519"), wish.WithMiddleware( logging.Middleware(), shellMiddleware(), ), ) if err != nil { panic(err) } s.ListenAndServe() } func shellMiddleware() wish.Middleware { return func(next ssh.Handler) ssh.Handler { return func(s ssh.Session) { cmd := wish.Command(s, "bash") _ = cmd.Run() } } } ``` -------------------------------- ### Middleware Ordering Recommendation Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/10-additional-middleware.md This example demonstrates a typical ordering of multiple middleware components within a Wish server configuration. Middleware is applied from outermost to innermost, but executed in reverse order. This ordering ensures proper execution flow for logging, security, and session management. ```go wish.WithMiddleware( // Outermost (runs last) elapsed.Middleware(), // Measure total time recover.Middleware(), // Catch panics logging.Middleware(), // Log all access ratellimiter.Middleware(limiter), // Check limits accesscontrol.Middleware(...), // Check permissions comment.Middleware(...), // Print message at end appMiddleware(), // Innermost (runs first) // Innermost ) ``` -------------------------------- ### Glob Method Implementation Example Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md A minimal Glob implementation that returns the pattern itself, effectively disabling server-side globbing. This is useful when custom globbing logic is not required. ```go func (h *MyHandler) Glob(sess ssh.Session, pattern string) ([]string, error) { return []string{pattern}, nil } ``` -------------------------------- ### Bubble Tea Handler with MakeOptions Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/07-bubbletea.md Example of a teaHandler function that uses bubbletea.MakeOptions to configure program options for an SSH session. ```go func teaHandler(sess ssh.Session) (tea.Model, []tea.ProgramOption) { opts := bubbletea.MakeOptions(sess) opts = append(opts, tea.WithAltScreen()) return &MyModel{}, opts } ``` -------------------------------- ### Test Custom Middleware Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Example test for custom middleware. It verifies that the logging middleware correctly logs session information. ```go func TestLoggingMiddleware(t *testing.T) { var logs []string loggingMw := func(next ssh.Handler) ssh.Handler { return func(s ssh.Session) { logs = append(logs, "logged: "+s.User()) next(s) } } srv, _ := wish.NewServer( wish.WithMiddleware(loggingMw), ) sess := testsession.New(t, srv, nil) sess.Close() if len(logs) == 0 { t.Fatal("expected logs") } } ``` -------------------------------- ### Implement Echo Command Middleware Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Create a custom middleware that echoes the connected user's name back to them. This is a simple example of how to interact with the SSH session within a middleware. ```go import "charm.land/wish/v2" import "github.com/gliderlabs/ssh" func echoMiddleware() wish.Middleware { return func(next ssh.Handler) ssh.Handler { return func(s ssh.Session) { wish.Println(s, "Hello, "+s.User()) } } } ``` -------------------------------- ### Set Environment Variables for Commands Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Get the current session environment variables and set custom environment variables for a specific command. ```go // Get session environment env := sess.Environ() // Set in command cmd := wish.Command(s, "sh", "-c", "echo $CUSTOM") cmd.SetEnv(append(sess.Environ(), "CUSTOM=value")) cmd.Run() ``` -------------------------------- ### Wish Application Before v2 Source: https://github.com/charmbracelet/wish/blob/main/UPGRADE_GUIDE_V2.md This Go code demonstrates a typical Wish application structure using Bubble Tea v1, including server setup, session handling, and a basic model for UI interaction. ```go package main import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/bubbletea" "github.com/charmbracelet/wish/logging" ) func main() { s, _ := wish.NewServer( wish.WithAddress(":2222"), wish.WithMiddleware( bubbletea.Middleware(teaHandler), logging.Middleware(), ), ) s.ListenAndServe() } func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { renderer := bubbletea.MakeRenderer(s) style := renderer.NewStyle().Foreground(lipgloss.Color("10")) m := model{style: style} return m, []tea.ProgramOption{tea.WithAltScreen{}} } type model struct { style lipgloss.Style } func (m model) Init() tea.Cmd { return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: if msg.String() == "q" { return m, tea.Quit } } return m, nil } func (m model) View() string { return m.style.Render("Hello, SSH!\n\nPress 'q' to quit") } ``` -------------------------------- ### Core API - Server Creation and Configuration Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/DOCUMENTATION_SUMMARY.txt This section details the core API for creating and configuring Wish SSH servers. It covers essential functions for server setup, host key management, authentication, and adding basic middleware. ```APIDOC ## Core API ### Description Provides functions for creating and configuring Wish SSH servers, including host key management, authentication, and middleware integration. ### Functions/Methods - **NewServer(config Config)** (Server) - Creates a new Wish SSH server instance. - **Server.ListenAndServe(addr string)** - Starts the SSH server on the specified address. - **Server.Use(middleware Middleware)** - Adds middleware to the server pipeline. ### Type Definitions #### Config - **HostKeyPath** (string) - Path to the SSH host key file. - **PasswordAuth** (func(user, password string) bool) - Callback for password authentication. - **PublicKeyAuth** (func(user, key []byte) bool) - Callback for public key authentication. ### Examples ```go package main import ( "crypto/ed25519" "io/ioutil" "log" "github.com/charmbracelet/wish" ) func main() { // Generate a new host key if one doesn't exist key, err := ed25519.GenerateKey(nil) if err != nil { log.Fatal(err) } // Create a Wish server with basic configuration sserver, err := wish.NewServer(wish.WithHostKey(key)) if err != nil { log.Fatal(err) } // Start the server on port 2222 log.Println("Starting SSH server on :2222") err = server.ListenAndServe(":2222") if err != nil { log.Fatal(err) } } ``` ``` -------------------------------- ### Password Authentication for Client Session Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Illustrates how to set up password authentication for a client session. Provide the correct username and password in the client configuration. ```go // Password auth cfg := &gossh.ClientConfig{ User: "testuser", Auth: []gossh.AuthMethod{ gossh.Password("mypassword"), }, HostKeyCallback: gossh.InsecureIgnoreHostKey(), } sess, _ := testsession.NewClientSession(t, addr, cfg) ``` -------------------------------- ### Wish Server with Middleware Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/README.md Demonstrates setting up a Wish server with custom middleware and a handler. ```go s, _ := wish.NewServer( wish.WithAddress(":2222"), wish.WithMiddleware( logging.Middleware(), myHandler(), ), ) s.ListenAndServe() ``` -------------------------------- ### Create and Run a Basic Command Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/02-command-execution.md Use `wish.Command` to create a command wrapper and `Run` to execute it. Errors are handled using `wish.Fatal`. ```go cmd := wish.Command(sess, "ls", "-la", "/tmp") if err := cmd.Run(); err != nil { wish.Fatal(sess, err) } ``` -------------------------------- ### SCP Get Info Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Retrieve information about an SCP command. ```go scp.GetInfo(cmd) ``` -------------------------------- ### Comment Middleware Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Apply middleware that sends a predefined comment message to the client at the start of the session. ```go comment.Middleware("Goodbye!") ``` -------------------------------- ### Public Key Authentication for Client Session Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/11-test-utilities.md Shows how to configure a client to use public key authentication for establishing a session. Ensure the private key is correctly parsed and provided. ```go // Public key auth key, _ := parsePrivateKey(keyBytes) cfg := &gossh.ClientConfig{ User: "testuser", Auth: []gossh.AuthMethod{ gossh.PublicKeys(key), }, HostKeyCallback: gossh.InsecureIgnoreHostKey(), } sess, _ := testsession.NewClientSession(t, addr, cfg) ``` -------------------------------- ### Mkdir Method Note Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/09-scp.md When implementing Mkdir, it's recommended to create only the single specified directory and avoid using os.MkdirAll to prevent unintended directory creation. ```go Note: Usually should not use os.MkdirAll; just create the single directory. ``` -------------------------------- ### Testing a Wish Server Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Demonstrates how to create and use a test session for interacting with a Wish server. This includes setting up the server, creating a session, and executing commands. ```go import "charm.land/wish/v2/testsession" func TestServer(t *testing.T) { srv, _ := wish.NewServer( wish.WithMiddleware(myMiddleware()), ) // Create session sess := testsession.New(t, srv, nil) // Or individual steps addr := testsession.Listen(t, srv) sess, _ := testsession.NewClientSession(t, addr, nil) // Use session output, _ := sess.Output("command") } ``` -------------------------------- ### Create Wish Server with Authentication Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Configure server authentication using authorized keys and password authentication. The password authentication function should return true for valid credentials. ```go import "charm.land/wish/v2" import "github.com/gliderlabs/ssh" // With authentication s, err := wish.NewServer( wish.WithAddress("0.0.0.0:2222"), wish.WithAuthorizedKeys("/home/user/.ssh/authorized_keys"), wish.WithPasswordAuth(func(ctx ssh.Context, pwd string) bool { return pwd == "secret" }), ) ``` -------------------------------- ### Set Environment Variables for Command Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/02-command-execution.md Use `SetEnv` to add or modify environment variables for a command. This example appends custom variables to the existing environment. ```go cmd := wish.Command(sess, "printenv", "USER") cmd.SetEnv(append(os.Environ(), "CUSTOM_VAR=value")) cmd.Run() ``` -------------------------------- ### Execute Command with Custom Environment and Directory Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Set custom environment variables and the working directory before executing a command. ```go // Set environment cmd := wish.Command(s, "printenv") cmd.SetEnv(append(os.Environ(), "CUSTOM=value")) cmd.SetDir("/tmp") cmd.Run() ``` -------------------------------- ### Configure Wish Server with Existing Host Key Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Initializes a new Wish server, specifying the path to an existing host key. ```go s, _ := wish.NewServer( wish.WithHostKeyPath("/path/to/key"), ) ``` -------------------------------- ### Get Background Color from Messages in Bubble Tea v2 Source: https://github.com/charmbracelet/wish/blob/main/UPGRADE_GUIDE_V2.md Instead of querying the terminal at initialization, listen for `BackgroundColorMsg` in the `Update` function to determine the background color. ```go // Before func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { renderer := bubbletea.MakeRenderer(s) bg := "light" if renderer.HasDarkBackground() { bg = "dark" } m := model{bg: bg} return m, nil } // After func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { m := model{bg: "light"} // default return m, nil } func (m model) Init() tea.Cmd { return tea.RequestBackgroundColor } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.BackgroundColorMsg: if msg.IsDark() { m.bg = "dark" } else { m.bg = "light" } } return m, nil } ``` -------------------------------- ### Set up Git Server Middleware Source: https://github.com/charmbracelet/wish/blob/main/_autodocs/14-quick-reference.md Enable Git repository access over SSH by using the `git.Middleware`. Provide the path to your Git repositories and an implementation of `MyHooks` for custom Git operations. ```go import "charm.land/wish/v2" import "charm.land/wish/v2/middleware/git" hooks := &MyHooks{} s, _ := wish.NewServer( wish.WithMiddleware( git.Middleware("/var/git/repos", hooks), ), ) ``` -------------------------------- ### Get Color Profile from Messages in Bubble Tea v2 Source: https://github.com/charmbracelet/wish/blob/main/UPGRADE_GUIDE_V2.md Color profiles are now received as a `tea.ColorProfileMsg` in the `Update` function, allowing dynamic handling of terminal color capabilities. ```go func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.ColorProfileMsg: m.profile = msg.String() // "TrueColor", "ANSI256", "ANSI", etc. } return m, nil } ```