@@ -3,7 +3,9 @@ package main
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "net"
6
7
"os"
8
+ "os/exec"
7
9
"os/signal"
8
10
"runtime/debug"
9
11
"time"
@@ -28,6 +30,17 @@ func realMain(cfg *config) error {
28
30
cancel (fmt .Errorf ("received %s signal" , signal ))
29
31
}()
30
32
33
+ errs := make (chan error )
34
+ if cfg .SSH .Host != "" {
35
+ zerolog .Ctx (ctx ).Info ().
36
+ Msgf ("setting up SSH tunnel: %d:localhost:%d -> %s" ,
37
+ cfg .SSH .LocalPort , cfg .SSH .RemotePort , cfg .SSH .Host ,
38
+ )
39
+ if err := setupSSHTunnel (ctx , cfg .SSH , errs ); err != nil {
40
+ return fmt .Errorf ("setup SSH tunnel: %w" , err )
41
+ }
42
+ }
43
+
31
44
clientCtx , clientCancel := context .WithTimeout (ctx , time .Second * 10 )
32
45
defer clientCancel ()
33
46
@@ -38,8 +51,6 @@ func realMain(cfg *config) error {
38
51
return fmt .Errorf ("new server: %w" , err )
39
52
}
40
53
41
- errs := make (chan error )
42
-
43
54
go func () {
44
55
if err := bitcoind .Listen (ctx , cfg .Listen ); err != nil {
45
56
errs <- err
@@ -90,3 +101,61 @@ func findSetting(key string, settings []debug.BuildSetting) string {
90
101
91
102
return "unknown"
92
103
}
104
+
105
+ // setupSSHTunnel creates an SSH tunnel by running the ssh command
106
+ func setupSSHTunnel (ctx context.Context , conf sshConfig , out chan <- error ) error {
107
+ args := []string {
108
+ "-N" ,
109
+ "-L" , fmt .Sprintf ("%d:localhost:%d" , conf .LocalPort , conf .RemotePort ),
110
+ conf .Host ,
111
+ }
112
+ if conf .KnownHosts != nil {
113
+ tempFile , err := os .CreateTemp ("" , "" )
114
+ if err != nil {
115
+ return fmt .Errorf ("create temp file: %w" , err )
116
+ }
117
+
118
+ for _ , host := range conf .KnownHosts {
119
+ fmt .Fprintln (tempFile , host )
120
+ }
121
+ if err := tempFile .Close (); err != nil {
122
+ return fmt .Errorf ("close temp file: %w" , err )
123
+ }
124
+
125
+ args = append (args , "-o" , "UserKnownHostsFile=" + tempFile .Name ())
126
+ }
127
+ // Build SSH command with port forwarding
128
+ // -N: Don't execute remote command (forward only)
129
+ // -L: Local port forwarding
130
+ cmd := exec .CommandContext (ctx , "ssh" , args ... )
131
+
132
+ // Start the SSH tunnel
133
+ if err := cmd .Start (); err != nil {
134
+ return fmt .Errorf ("starting SSH tunnel: %w" , err )
135
+ }
136
+
137
+ // Monitor the tunnel process in background
138
+ go func () {
139
+ if err := cmd .Wait (); err != nil {
140
+ zerolog .Ctx (ctx ).Error ().
141
+ Err (err ).
142
+ Msg ("SSH tunnel exited unexpectedly" )
143
+ out <- fmt .Errorf ("SSH tunnel exited unexpectedly: %w" , err )
144
+ }
145
+ }()
146
+
147
+ // Wait for the tunnel to be established
148
+ for i := 0 ; i < 10 ; i ++ {
149
+ if conn , err := net .Dial ("tcp" , fmt .Sprintf ("localhost:%d" , conf .LocalPort )); err == nil {
150
+ conn .Close ()
151
+ return nil
152
+ }
153
+ select {
154
+ case <- ctx .Done ():
155
+ return fmt .Errorf ("wait for SSH tunnel: %w" , ctx .Err ())
156
+ case <- time .After (time .Second ):
157
+ }
158
+ }
159
+
160
+ return fmt .Errorf ("timeout waiting for SSH tunnel" )
161
+ }
0 commit comments