11package main
22
33import (
4- "bufio"
5- "encoding/json"
64 "flag"
75 "fmt"
8- "io"
9- "log"
10- "net"
116 "os"
12- "os/exec"
13- "os/signal"
147 "path"
15- "strconv"
16- "strings"
17- "syscall"
188
19- "github.com/creack/pty"
20- "golang.org/x/term"
9+ "github.com/distrobox/hrun/cmd"
2110)
2211
23- type Command struct {
24- Command []string
25- Width uint16
26- Height uint16
27- }
28-
2912func main () {
3013 helpFlag := flag .Bool ("h" , false , "Display help" )
3114 helpFlagLong := flag .Bool ("help" , false , "Display help" )
@@ -56,13 +39,13 @@ If no command is provided, it starts a shell on the host.
5639
5740 // Help message
5841 if * helpFlag || * helpFlagLong {
59- flag . Usage ()
42+ cmd . ShowHelp ()
6043 return
6144 }
6245
6346 // Server mode
6447 if * startFlag {
65- startServer (allowedCmds , socketFlag )
48+ cmd . StartServer (allowedCmds , * socketFlag )
6649 return
6750 }
6851
@@ -74,285 +57,5 @@ If no command is provided, it starts a shell on the host.
7457 command = flag .Args ()
7558 }
7659
77- startClient (command , socketFlag )
78- }
79-
80- func startServer (allowedCmds []string , socketFlag * string ) {
81- // Create a listener for the server
82- listener , err := net .Listen ("unix" , * socketFlag )
83- if err != nil {
84- panic (err )
85- }
86- defer listener .Close ()
87- log .Printf ("Server is running on %s\n " , listener .Addr ())
88-
89- // Set up a signal handler to shut down the server
90- doneCh := make (chan struct {})
91- go func () {
92- sigCh := make (chan os.Signal , 1 )
93- signal .Notify (sigCh , os .Interrupt , syscall .SIGTERM , syscall .SIGQUIT )
94-
95- <- sigCh
96- log .Println ("Shutdown signal received, closing server..." )
97- close (doneCh )
98- }()
99-
100- // Accept connections and handle them
101- for {
102- select {
103- case <- doneCh :
104- log .Println ("Shutting down server..." )
105- return
106- case conn , ok := <- acceptConn (listener ):
107- if ! ok {
108- log .Println ("Listener closed, shutting down server..." )
109- return
110- }
111- go handleConnection (conn , allowedCmds )
112- }
113- }
114- }
115-
116- func acceptConn (listener net.Listener ) <- chan net.Conn {
117- ch := make (chan net.Conn )
118- go func () {
119- defer close (ch )
120- conn , err := listener .Accept ()
121- if err != nil {
122- log .Println ("Error accepting connection:" , err )
123- return
124- }
125- ch <- conn
126- }()
127- return ch
128- }
129-
130- func handleConnection (conn net.Conn , allowedCmds []string ) {
131- defer conn .Close ()
132-
133- // Read the command from the client
134- reader := bufio .NewReader (conn )
135- rawCommand , err := reader .ReadString ('\n' )
136- if err != nil {
137- log .Println ("Failed to read command: " , err )
138- return
139- }
140- log .Printf ("Received command: %s" , rawCommand )
141-
142- // Decode the command into the Command struct
143- var cmdStruct Command
144- if err := json .Unmarshal ([]byte (rawCommand ), & cmdStruct ); err != nil {
145- log .Printf ("Error decoding command: %v" , err )
146- conn .Close ()
147- return
148- }
149- if len (cmdStruct .Command ) == 0 {
150- log .Println ("No command provided" )
151- return
152- }
153-
154- // Check if the command is allowed
155- if len (allowedCmds ) > 0 {
156- allowed := false
157- for _ , allowedCmd := range allowedCmds {
158- if cmdStruct .Command [0 ] == allowedCmd {
159- allowed = true
160- break
161- }
162- }
163- if ! allowed {
164- log .Printf ("Command %s is not allowed" , cmdStruct .Command [0 ])
165- conn .Close ()
166- return
167- }
168- }
169-
170- // Prepare a pty
171- var ptyMaster , ptySlave * os.File
172- ptyMaster , ptySlave , err = pty .Open ()
173- if err != nil {
174- log .Println ("Error creating PTY:" , err )
175- conn .Close ()
176- return
177- }
178- defer ptySlave .Close ()
179- log .Println ("PTY created" )
180-
181- // Set initial terminal size
182- ws := & pty.Winsize {
183- Cols : cmdStruct .Width ,
184- Rows : cmdStruct .Height ,
185- }
186- if err := pty .Setsize (ptyMaster , ws ); err != nil {
187- log .Printf ("Error setting initial terminal size: %v" , err )
188- } else {
189- log .Printf ("Terminal initialized to %dx%d" , cmdStruct .Width , cmdStruct .Height )
190- }
191-
192- // Set up the channels to communicate with the host
193- go func () {
194- io .Copy (conn , ptyMaster )
195- ptyMaster .Close ()
196- conn .Close ()
197- }()
198- go func () {
199- io .Copy (ptyMaster , conn )
200- ptyMaster .Close ()
201- conn .Close ()
202- }()
203-
204- // Set the terminal size on resize request
205- go func () {
206- for {
207- message , err := reader .ReadString ('\n' )
208- if err != nil {
209- break
210- }
211-
212- if strings .HasPrefix (message , "resize:" ) {
213- log .Println ("Resize request received" )
214- trimmedMessage := strings .TrimSpace (message )
215- parts := strings .Split (trimmedMessage , ":" )
216- if len (parts ) == 3 {
217- width , errWidth := strconv .Atoi (parts [1 ])
218- height , errHeight := strconv .Atoi (parts [2 ])
219- if errWidth != nil || errHeight != nil {
220- log .Printf ("Error converting dimensions to integers: width error %v, height error %v" , errWidth , errHeight )
221- continue
222- }
223- ws := & pty.Winsize {
224- Cols : uint16 (width ),
225- Rows : uint16 (height ),
226- }
227- if err := pty .Setsize (ptyMaster , ws ); err != nil {
228- log .Printf ("Error resizing PTY: %v" , err )
229- } else {
230- log .Printf ("Terminal resized to %dx%d" , width , height )
231- }
232- } else {
233- log .Println ("Invalid resize message format" )
234- }
235- }
236- }
237- }()
238-
239- // Execute the command
240- cmd := exec .Command (cmdStruct .Command [0 ], cmdStruct .Command [1 :]... )
241- cmd .Stdin = ptySlave
242- cmd .Stdout = ptySlave
243- cmd .Stderr = ptySlave
244-
245- // Set the process attributes
246- cmd .SysProcAttr = & syscall.SysProcAttr {
247- Setctty : true ,
248- Setsid : true ,
249- Pdeathsig : syscall .SIGTERM ,
250- }
251-
252- // Start the shell process
253- if err = cmd .Start (); err != nil {
254- log .Println ("Error starting shell:" , err )
255- return
256- }
257- log .Println ("Shell started" )
258-
259- sigCh := make (chan os.Signal , 1 )
260- signal .Notify (sigCh , os .Interrupt , syscall .SIGTERM , syscall .SIGQUIT )
261-
262- // Handle the termination signal
263- go func () {
264- <- sigCh
265- conn .Close ()
266- syscall .Kill (- cmd .Process .Pid , syscall .SIGKILL )
267- }()
268-
269- // Wait for the shell process to exit
270- cmd .Wait ()
271- log .Println ("Shell process exited" )
272- log .Printf ("Connection closed\n \n " )
273- }
274-
275- func startClient (command []string , socketFlag * string ) {
276- // Connect to the server
277- conn , err := net .Dial ("unix" , * socketFlag )
278- if err != nil {
279- log .Println ("Error connecting to the host:" , err )
280- return
281- }
282- defer conn .Close ()
283-
284- // Get the initial terminal size
285- initialWidth , initialHeight , err := term .GetSize (int (os .Stdin .Fd ()))
286- if err != nil {
287- log .Println ("Error getting initial terminal size:" , err )
288- return
289- }
290-
291- // Send the command to the server
292- cmd := Command {
293- Command : command ,
294- Width : uint16 (initialWidth ),
295- Height : uint16 (initialHeight ),
296- }
297- cmdBytes , err := json .Marshal (cmd )
298- if err != nil {
299- log .Println ("Error encoding command:" , err )
300- return
301- }
302-
303- _ , err = conn .Write (append (cmdBytes , '\n' ))
304- if err != nil {
305- log .Println ("Error sending command to the server:" , err )
306- return
307- }
308-
309- // Set up handling for SIGWINCH (window change) signal to detect terminal resize events
310- sendTerminalSize := func () {
311- width , height , err := term .GetSize (int (os .Stdin .Fd ()))
312- if err != nil {
313- log .Println ("Error getting terminal size:" , err )
314- return
315- }
316-
317- resizeCommand := fmt .Sprintf ("resize:%d:%d\n " , width , height )
318- _ , err = conn .Write ([]byte (resizeCommand ))
319- if err != nil {
320- log .Println ("Error sending terminal size to the server:" , err )
321- }
322- }
323-
324- sigwinchChan := make (chan os.Signal , 1 )
325- signal .Notify (sigwinchChan , syscall .SIGWINCH )
326- go func () {
327- for range sigwinchChan {
328- sendTerminalSize ()
329- }
330- }()
331-
332- // Set the terminal to raw mode
333- oldState , err := term .MakeRaw (int (os .Stdin .Fd ()))
334- if err != nil {
335- log .Println ("Error setting terminal to raw mode:" , err )
336- return
337- }
338- defer func () { _ = term .Restore (int (os .Stdin .Fd ()), oldState ) }()
339-
340- // Create a channel to communicate with the pty
341- doneCh := make (chan struct {})
342- go func () {
343- _ , err := io .Copy (conn , os .Stdin )
344- if err != nil {
345- log .Println ("Error copying data to the server:" , err )
346- }
347- close (doneCh )
348- }()
349- go func () {
350- _ , err := io .Copy (os .Stdout , conn )
351- if err != nil {
352- log .Println ("Error copying data from the server:" , err )
353- }
354- close (doneCh )
355- }()
356-
357- <- doneCh
60+ cmd .StartClient (command , * socketFlag )
35861}
0 commit comments