/
wemux
executable file
·582 lines (544 loc) · 20.6 KB
/
wemux
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
#!/bin/bash
# wemux by Matt Furden @zolrath
# version 2.2.1
#
# wemux allows you to start a shared tmux session using the command 'wemux'.
# Clients have the option of mirroring, which will give them read-only access,
# or pairing, which will allow them to edit your file (shared cursor) or work
# in another window (separate cursors) in the hosts tmux session.
#
# To set a user as host add their username to the host_list in /etc/wemux.conf
# Other configuations options are also located in /etc/wemux.conf
#
# For environments with multiple hosts running their own independent sessions
# on the same machine wemux can join different sessions with the wemux join
# command, if enabled. This can be enabled in the configuration file.
#
# WEMUX HOST COMMANDS:
# wemux start : Start the wemux session/join an existing wemux session.
# wemux attach: Join an existing wemux session.
# wemux stop : Stop the wemux session, delete the socket.
# wemux users : List the currently connected wemux users.
# wemux kick : Disconnect an SSH user, remove their wemux session.
# wemux config: Open the wemux configuration file in your $EDITOR.
# wemux help : Display the help screen.
#
# WEMUX CLIENT COMMANDS:
# wemux mirror: Attach to Host in read-only mode.
# wemux pair : Attach to Host in pair mode, which allows editing.
# wemux logout: Log out of the wemux pairing session.
# wemux users : List the currently connected wemux users.
# wemux help : Display the help screen.
#
# To enable multi-host commands, set allow_session_change="true" in wemux.conf
# WEMUX SESSION COMMANDS: can be run by either host or client.
# wemux join : Join wemux session with supplied name.
# wemux reset : Join default wemux session: wemux
# wemux list : List all currently active wemux sessions.
###############################################################################
# Current wemux version.
version="2.2.0"
# Setup and Configuration Files.
# Default settings, modify them in the /etc/wemux.conf file:
host_list=(root)
socket_prefix="/tmp/wemux"
options="-u"
allow_pair_mode="true"
default_client_mode="mirror"
allow_kick_user="true"
allow_session_change="false"
allow_session_list="true"
allow_user_list="true"
announce_attach="true"
announce_session_change="true"
# Prevent users from changing their $USER env variable to become host.
username=`whoami`
# Set $EDITOR default to vi if not configured on host machine.
editor=${EDITOR:="vi"}
# Load configuration options from /etc/wemux.conf
[ -f /etc/wemux.conf ] && . /etc/wemux.conf
# Sanitize session name, replace spaces and underscores with dashes.
# Remove all non alpha-numeric characters, convert to lowercase.
sanitize_session_name() {
local new_session=$@
local new_session=${new_session// /\-}
local new_session=${new_session//_/\-}
local new_session=${new_session//[^a-zA-Z0-9\-]/}
local new_session=`echo "$new_session" | tr '[A-Z]' '[a-z]'`
echo "$new_session"
}
# Load the session name of last wemux session. If empty, set to 'wemux'.
# Ensure session name is 'wemux' if allow_session_change is disabled.
load_session_name() {
if [ "$allow_session_change" == "true" ]; then
if [ -f ~/.wemux_last_session ]; then
unclean_session=`cat ~/.wemux_last_session`
# Sanitize loaded session name.
session=`sanitize_session_name $unclean_session`
if ! [[ $unclean_session == $session ]]; then
echo "$session" > ~/.wemux_last_session
fi
else
# If ~/.wemux_last_session doesn't exist set to 'wemux'
session="wemux"
fi
else
# If allow_session_change is disabled, set to 'wemux'
session="wemux"
fi
}
# Build $wemux variable to call proper tmux session.
build_wemux_prefix() {
load_session_name
# Set client's pair mode session name.
client_session="$session-$username"
# Set socket to include session name.
socket="${socket_prefix}-$session"
# Set $wemux to wemux session file.
wemux="tmux -S $socket $options"
}
# List all currently running wemux sessions.
list_active_sessions() {
if [ "$allow_session_change" == "true" ]; then
if [ "$allow_session_list" == "true" ]; then
wemux_session_sockets=$socket_prefix*
for wemux_session in $wemux_session_sockets; do
redirect=`tmux -S $wemux_session server-info 2>&1`; session_running=$?
# Number each active session, give it its own line.
if [ "$session_running" == 0 ]; then
num=$[num+1]
session_name=`echo "$wemux_session" | sed -e "s,$socket_prefix-,,"`
# Add indicator for current wemux session.
if [ "$session_name" == "$session" ]; then
session_name="$session_name <- current session"
fi
session_list="$session_list $num. $session_name\n"
fi
done
if [ -z "$session_list" ]; then
echo "No wemux sessions currently active."
else
echo "Currently active wemux sessions:"
# Remove last newline
echo -e "${session_list%??}"
fi
# allow_session_list is disabled:
else
echo "Session listing has been disabled."
return 126
fi
# allow_session_change is disabled:
else
echo "Session related commands have been disabled."
return 126
fi
}
# Tmux status line uses global variables, until something is changed this won't
# work for filtering users from the user list.
# set_current_user() {
# `$wemux set-environment -t $client_session CURRENT_WEMUX_USER $(whoami)`
# }
# List users currently attached to wemux session. Username[m] for mirror mode.
# Only contains names. Formatted for being included as part of status bar.
status_users() {
if [ "$allow_user_list" == "true" ]; then
while IFS= read line; do
read tty mode <<<$(echo $line)
# Get user associated with tty
name=`stat -f%Su $tty 2>/dev/null` || name=`stat -c%U $tty 2>/dev/null`
# If user is attached in read-only mode, set mode to [m]
[[ $mode == 0 ]] && mode="" || mode="[m]"
# If user/mode is already is userlist, do not add them again.
if ! [[ "$users" =~ "$name$mode," ]]; then
users="$users$name$mode, "
fi
done < <(wemux list-clients -F "#{client_tty},#{client_readonly}" | tr ',' ' ')
# Strip last two characters ', '
echo "${users%??}"
else
echo "User list disabled."
return 126
fi
}
# List users currently attached to wemux session with informative string.
# More verbose, intended for use in terminal.
list_users() {
if [ "$allow_user_list" == "true" ]; then
redirect=`wemux server-info 2>&1`; session_running=$?
if [ "$session_running" == 0 ]; then
# Get list of users with status_users function.
users="$(wemux status_users)"
# Number each user, give it its own line.
for user in $users; do
num=$[num+1]
user=`echo "$user" | sed -e "s/,//"`
user_list="$user_list $num. $user\n"
done
if [ -z "$user_list" ]; then
echo "No wemux users connected to '$session'."
else
echo "Users connected to '$session': "
# Remove last newline.
echo -e "${user_list%??}"
fi
else
echo "No wemux session running on '$session'. No session, no users!"
fi
else
echo "User listing has been disabled."
fi
}
# Display the currently attached users verbosely in a tmux message.
display_users() {
redirect=`$wemux display-message "$(wemux users)" 2>&1`
}
# The ugly group of redirects below solve the issue where tmux/epoll causes tmux
# to hang when stderr is redirected to /dev/null in a backwards compatible way.
# Returns true if host currently has a running wemux session.
session_exists() {
redirect=`$wemux has-session -t $session 2>&1`; does_exist=$?
[ "$does_exist" == 0 ] && return 0 || return 1;
}
# Returns true if pair session with current host already exists.
has_pair_session() {
redirect=`$wemux has-session -t $client_session 2>&1`; does_exist=$?
[ "$does_exist" == 0 ] && return 0 || return 1;
}
# Returns true if session is successfully killed.
kill_session_successful() {
redirect=`tmux -S $socket kill-server 2>&1`; killed_successfully=$?
[ "$killed_successfully" == 0 ] && return 0 || return 1;
}
# Announce when user attaches/detaches from session.
# Can be disabled by changing announce_attach to false in /etc/wemux.conf
# The first argument specifies the mode the user is attaching in for the message
# All additional arguments get wrapped in the attach/detach messages.
announce_connection() {
connection_type=$1; shift; attach_commands="$@"
[ "$announce_attach" == "true" ] && redirect=`$wemux display-message \
"$username has attached in $connection_type mode." 2>&1`
$attach_commands
[ "$announce_attach" == "true" ] && redirect=`$wemux display-message \
"$username has detached." 2>&1`
return 0
}
# Announces when a user joins/changes their session.
# Can be disabled by changing announce_session_change to false in /etc/wemux.conf
# Change session name for session, or display session name if no argument is given.
change_session() {
if [ "$allow_session_change" == "true" ]; then
# Sanitize input.
new_session=`sanitize_session_name $@`
old_session=$session
# Get list of currently running sessions
session_names=(`echo $socket_prefix* | sed -e "s,$socket_prefix-,,g"`)
# If user joins a number, go to the session with that number in `wemux list`
[[ "$new_session" =~ ^[0-9]+$ ]] && new_session=${session_names[$new_session-1]}
if [ -z "$new_session" ]; then
echo "Current wemux session: $session"
elif [ "$new_session" == "$old_session" ]; then
echo "Your wemux session is already set to '$session'"
else
# Announce that the user has changed sessions to current session.
[ "$announce_session_change" == "true" ] && redirect=`$wemux display-message \
"$username has switched to session: $new_session" 2>&1`
echo "Changed wemux session from '$old_session' to '$new_session'"
echo $new_session > ~/.wemux_last_session
# Rebuild wemux prefix for new session name.
build_wemux_prefix
# Announce that the user has joined to new session.
[ "$announce_session_change" == "true" ] && redirect=`$wemux display-message \
"$username has joined this session" 2>&1`
fi
else
echo "Changing wemux sessions has been disabled."
return 126
fi
return 0
}
# Display version of wemux installed on system. Show URL for wemux.
display_version() {
echo "wemux $version"
echo "To check for a newer version visit: http://www.github.com/zolrath/wemux"
}
# Host mode, used when user is listed in the host_list array in /etc/wemux.conf
host_mode() {
# Start the session if it doesn't exist, otherwise reattach.
start_session() {
if ! session_exists; then
$wemux new-session -d -s $session
# Open tmux socket to all users to allow clients to connect.
chmod 1777 $socket
echo "wemux session started on '$session'."
fi
reattach
}
# Reattach to the wemux session.
reattach() {
if session_exists; then
$wemux attach -t $session
else
echo "No wemux session to attach to on '$session'."
fi
}
# Kick an SSH user off the server and remove their pair session if it exists.
kick_user() {
if [ "$allow_kick_user" == "true" ]; then
kicked_user=$1
# Get sshd process with users name and get its PID.
user_pid=`ps aux | grep "$kicked_user.*sshd" | grep -v grep | tr -s ' ' | cut -d ' ' -f 2`
echo "Kicking $kicked_user from the server. Sudo required."
# Kill the sshd process of the supplied user.
redirect=`sudo kill -9 $user_pid 2>&1`; kicked_successfully=$?
# Remove any tmux sessions ending in "-kicked_user"
`wemux kill-session -t "*-$kicked_user" > /dev/null 2>&1`
if [ "$kicked_successfully" == 0 ]; then
echo "$kicked_user kicked successfully!"
else
echo "$kicked_user was not kicked. Are you sure they're connected?"
fi
else
echo "Kicking users has been disabled."
fi
}
# Stop the wemux session and remove the socket file.
stop_session() {
session_sockets=(`echo $socket_prefix*`)
# If a specific session is supplied:
if [ $1 ]; then
# If user enters a number, stop the session with that number in `wemux list`
if [[ $@ =~ ^[0-9]+$ ]]; then
socket=${session_sockets[$1-1]}
session=`echo $socket | sed -e "s,$socket_prefix-,,g"`
# Otherwise, stop the session with the supplied name.
else
session=`sanitize_session_name $@`
socket="$socket_prefix-$session"
fi
fi
# If the user doesn't pass an argument, stop current session.
if kill_session_successful; then
echo "wemux session on '$session' stopped."
else
echo "No wemux session running on '$session'"
fi
# If the socket file exists:
if [ -e "$socket" ]; then
if ! rm $socket; then
echo "Could not remove '$socket'. Please check file ownership."
fi
fi
}
# Display the commands available in host mode.
display_host_commands() {
echo "wemux version $version"
if [ "$allow_session_change" == "true" ]; then
echo "Current wemux session: $session"
fi
echo ""
echo "Usage: wemux [command]"
echo "To host a wemux session please use one of the following:"
echo ""
echo " [s] start: Start the wemux session/attach to an existing wemux session."
echo " [a] attach: Attach to an existing wemux session."
echo " [k] stop: Kill the wemux session '$session', delete its socket."
echo ""
if [ "$allow_session_change" == "true" ]; then
echo " [j] join [name]: Join the specified wemux session."
echo " [r] reset: Join default wemux session: wemux"
if [ "$allow_session_list" == "true" ]; then
echo " [l] list: List all currently active wemux sessions."
fi
fi
if [ "$allow_user_list" == "true" ]; then
echo " [u] users: List all users currently connected to '$session'"
fi
if [ "$allow_kick_user" == "true" ]; then
echo " kick: Disconnect an SSH user, remove their wemux session."
fi
echo ""
echo " [c] config: Open the wemux configuration file in $EDITOR."
echo " [h] help: Display this screen."
echo " no args: Start the wemux session/attach to an existing wemux session."
}
# Host mode command handling:
# If no command given, call start session.
if [ -z "$1" ]; then
announce_connection "host" start_session
else
case "$1" in
start|s) announce_connection "host" start_session;;
attach|a) announce_connection "host" reattach;;
stop|st) shift; stop_session $@;;
kill|k) shift; stop_session $@;;
help|h) display_host_commands;;
join|j) shift; change_session $@;;
name|n) shift; change_session $@;;
reset|r) change_session "wemux";;
list|l) list_active_sessions;;
users|u) list_users;;
kick) kick_user $2;;
status_users) status_users;;
display_users) display_users;;
version|v) display_version;;
conf*|c) $editor /etc/wemux.conf;;
*) if ! $wemux $@; then
display_host_commands
exit 127
fi;;
esac
fi
}
# Client Mode, used when user is NOT listed in the host_list in /etc/wemux.conf
client_mode() {
# Mirror mode, allows the user to view wemux session in read only mode.
mirror_mode() {
if session_exists; then
$wemux attach -t $session -r
else
echo "No wemux session to mirror on '$session'."
fi
}
# Pair mode, allows user to interact with wemux session.
# Will connect to existing pair session or create one if necessary.
pair_mode() {
if [ "$allow_pair_mode" == "true" ]; then
if has_pair_session; then
$wemux attach -t $client_session
elif session_exists; then
$wemux new-session -d -t $session -s $client_session
$wemux new-window -n $username
$wemux attach -t $client_session
else
echo "No wemux session to pair with on '$session'."
fi
else
echo "Pair mode is disabled."
return 126
fi
}
# Log user out of pair mode, removing their session..
logout_pair() {
if [ "$allow_pair_mode" == "true" ]; then
if has_pair_session; then
[ "$announce_attach" == "true" ] && $wemux display-message \
"$username has logged out of pair mode."
$wemux kill-session -t $client_session
echo "Logged out of pair mode on '$session'."
else
echo "No wemux session to log out of on '$session'."
fi
else
echo "Pair mode is disabled."
return 126
fi
}
# Reattach based upon presence of existing pair mode session.
# If pair mode session exists, reattach to it.
# If no pair mode session exists, mirror the host.
smart_reattach() {
# If default_client_mode has been set to "pair" in wemux.conf:
if [ "$default_client_mode" == "pair" ] && [ "$allow_pair_mode" == "true" ]; then
announce_connection "pair" pair_mode
else
if has_pair_session && [ "$allow_pair_mode" == "true" ]; then
announce_connection "pair" $wemux attach -t $client_session
elif session_exists; then
announce_connection "mirror" $wemux attach -t $session -r
else
echo "No wemux session to attach to on '$session'."
fi
fi
}
# Display the commands available in client mode.
display_client_commands() {
echo "wemux version $version"
if [ "$allow_session_change" == "true" ]; then
echo "Current wemux session: $session"
fi
echo ""
echo "Usage: wemux [command]"
echo "To connect to wemux please use one of the following:"
echo ""
echo " [m] mirror: Attach to '$session' in read-only mode."
if [ "$allow_pair_mode" == "true" ]; then
echo " [p] pair: Attach to '$session' in pair mode, which allows editing."
echo " [o] logout: Log out of the current wemux pairing session."
fi
echo ""
if [ "$allow_session_change" == "true" ]; then
echo " [j] join [name]: Join the specified wemux session."
echo " [r] reset: Join default wemux session: wemux"
if [ "$allow_session_list" == "true" ]; then
echo " [l] list: List all currently active wemux sessions."
fi
fi
if [ "$allow_user_list" == "true" ]; then
echo " [u] users: List all users currently connected to '$session'"
fi
echo ""
echo " [h] help: Display this screen."
if [ "$default_client_mode" == "pair" ] && [ "$allow_pair_mode" == "true" ]; then
echo " no args: Attach to '$session' in pair mode, which allows editing."
elif [ "$allow_pair_mode" == "true" ]; then
echo " no args: Attach to pair session on '$session' if it already exists, otherwise mirror."
else
echo " no args: Attach to '$session' in mirror mode."
fi
}
# Client mode command handling:
# If no command given, call smart_reattach
if [ -z "$1" ]; then
smart_reattach
else
case "$1" in
mirror|m) announce_connection "mirror" mirror_mode;;
pair|p) announce_connection "pair" pair_mode;;
edit|e) announce_connection "pair" pair_mode;;
logout|o) logout_pair;;
stop|s) logout_pair;;
help|h) display_client_commands;;
join|j) shift; change_session $@;;
name|n) shift; change_session $@;;
reset|r) change_session "wemux";;
list|l) list_active_sessions;;
users|u) list_users;;
status_users) status_users;;
display_users) display_users;;
version|v) display_version;;
*) if ! $wemux $@; then
display_client_commands
exit 127
fi;;
esac
fi
}
# Check if current user is listed in the host_list.
user_is_a_host() {
for user in "${host_list[@]}"; do
[[ "$user" == "$username" ]] && return 0
done
return 1
}
allowed_nested_command() {
commands=(users u list l display_users status_users list-clients
kill-sesssion kick version v)
for command in "${commands[@]}"; do
[[ "$command" == "$1" ]] && return 0
done
return 1
}
# Create $wemux variable.
build_wemux_prefix
# Don't allow wemux to be run directly within a wemux session.
if [[ "$TMUX" != *$socket* ]] || allowed_nested_command "$1" ; then
# If user is in host list, use host mode. If not, use client mode.
if user_is_a_host; then
host_mode "$@"
else
client_mode "$@"
fi
else
echo "You're already attached to the wemux session on '$session'"
echo "wemux does not allow nesting wemux sessions directly."
fi