aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: a808082c33eea4ef8bc1d04b6e6131758d897309 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# jupyter_safe_port

```
usage: jupyter_safe_port [-hcdf] {host} [port]

Discovers the next TCP port available for your notebook server and returns
execution instructions. If the argument '-c' is present and the requested
port is already bound on the remote host, return the SSH connection string

Positional arguments:
    host    host name or IP of remote system
    port    notebook server port to poll (default: 1024)

Arguments:
    -h    show this usage statement
    -c    only generate the SSH connection string
    -d    dump ports (useful in scripts)
              format: local remote
    -f    apply SSH argument to daemonize session (i.e. ssh -f)
```

## Install

```
./install.sh --prefix=/usr/local
```

## Examples

_Oh no! I need to run two notebook servers on a remote system but which ports should I use?_

```
$ jupyter_safe_port example.lan
Execute on example.lan:
jupyter notebook --no-browser --port=1024

Connect via:
ssh -N -L1024:localhost:1024 user@example.lan
```

You start the first notebook server. Now run `jupyter_safe_port` again...

```
$ jupyter_safe_port example.lan
Execute on example.lan:
jupyter notebook --no-browser --port=1025

Connect via:
ssh -N -L1025:localhost:1025 user@example.lan
```

The local port 1024 is already bound to the first server so it gives you 1025. On the remote system, `example.lan`, port 1024 is bound too so it returns 1025 as well. What if you want to use a higher port number on `example.lan`? Let's see...

```
$ jupyter_safe_port example.lan 8080
Execute on example.lan:
jupyter notebook --no-browser --port=8081

Connect via:
ssh -N -L1026:localhost:8081 user@example.lan
```

Oops, you forgot about that web server test. 8080 is already bound so you're given 8081 instead. Locally 1024 and 1025 are already bound so `jupyter_safe_port` returns 1026.

Let's say you have closed your laptop and lost all of your connections. If you can remember the remote port you used then `-c` will get you up and running in no time...
 
```
$ jupyter_safe_port example.lan -c 8081
Connect via:
ssh -N -L1024:localhost:8081 user@example.lan
```

## Scripting

You can use `jupyter_safe_port` in your scripts. `-d` returns the local port followed by the remote port.

```
$ jupyter_safe_port example.lan -d 8081
1024 8081
```

Here are a few ways to use it...

```shell
#!/usr/bin/env bash
interrupt() {
    echo "Interrupted" >&2
    exit 0
}
trap interrupt SIGINT SIGTERM

# System to spawn the notebook server on
server=example.lan

# Where is conda located? (Use single-quotes to avoid expanding ~ or variables)
conda_root='~/miniconda3'

# Conda environment to activate
environ=XYZ

# Configure port range (optional)
range_low=
range_high=

if ! [[ -x $(command -v "jupyter_safe_port") ]]; then
    echo "jupyter_safe_port is not installed" >&2
    exit 1
fi

result="$(jupyter_safe_port -d $server)"
if (( $? )) || [ -z "$result" ]; then
    # jupyter_safe_port failed
    # case 1: not enough arguments
    # case 2: invalid argument
    # case 3: invalid port range
    # case 4: ssh failed to connect to $server
    exit 1
fi

# Extract ports from result
read port_local port_remote <<< "$result"

if [[ -z "$port_local" ]] || [[ -z "$port_remote" ]]; then
    # case 1: unhandled exception
    exit 1
fi

if (( port_local < 0 )); then
    # case 1: no local ports are available in range
    exit 1
fi

if (( port_remote < 0 )); then
    # case 1: no remote ports are available in range
    # case 2: if using '-c', no service is present on the requested port
    exit 1
fi

echo "Starting remote jupyter session on $server:$port_remote"
session="jupyter_${port_remote}"
ssh $server "bash -c 'tmux new-session -d -s $session \
    \"source ~/.bash_profile \
    && source $conda_root/etc/profile.d/conda.sh \
    && conda activate $environ \
    && jupyter notebook --no-browser --port=$port_remote\"'"
if (( $? )); then
    echo "Failed to connect to $server" >&2
    exit 1
fi

echo "Remote tmux session name: $session"
echo
echo "To kill the tmux session and the jupyter notebook server:"
echo "ssh $server 'tmux kill-session -t $session'"
echo

# Forward the ports to the local system
echo "Forwarding $server:$port_remote to localhost:$port_local"
echo "To interrupt the session press: ctrl-c"

ssh -N -L$port_local:localhost:$port_remote $server
if (( $? )); then
    echo "Failed to forward port." >&2
    exit 1
fi
```