OCD cosmetics.
[xremote.git] / xremote.sh
1 #!/bin/bash
2
3 #########################################################################
4 # This program is free software: you can redistribute it and/or modify  #
5 # it under the terms of the version 3 of the GNU General Public License #
6 # as published by the Free Software Foundation.                         #
7 #                                                                       #
8 # This program is distributed in the hope that it will be useful, but   #
9 # WITHOUT ANY WARRANTY; without even the implied warranty of            #
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      #
11 # General Public License for more details.                              #
12 #                                                                       #
13 # You should have received a copy of the GNU General Public License     #
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.  #
15 #                                                                       #
16 # Written by and Copyright (C) Francois Fleuret                         #
17 # Contact <francois@fleuret.org> for comments & bug reports             #
18 #########################################################################
19
20 set -e
21
22 # set -o pipefail
23
24 ######################################################################
25
26 function check_remote_is_defined () {
27     if [[ "${REMOTE_HOST}" ]] && [[ "${REMOTE_DIR}" ]]
28     then
29         return 0
30     else
31         echo "@XREMOTE_HOST should come first." >&2
32         exit 1
33     fi
34 }
35
36 function help () {
37     cat <<EOF
38 xremote.sh [-h <remote_host>] [-i <remote_dir>] <script> [script arguments]
39
40   This script takes a script as argument and executes it remotely in a
41   temporary directory on a ssh-accessible server.
42
43   It parses the script first to find embedded arguments which define
44   the hostname on which to run, the files to send, the files to get
45   back when the execution is over, and commands to execute before
46   running the executable remotely.
47
48   These arguments can appear multiple times, except the one that
49   specifies the remote host.
50
51   Example:
52
53     @XREMOTE_HOST: elk.fleuret.org
54     @XREMOTE_EXEC: python3
55     @XREMOTE_SEND: main.cf
56     @XREMOTE_GET: *.dat
57     @XREMOTE_PRE: ln -s /home/fleuret/data/pytorch ./data
58
59   If a file with the same name as the script with the .xremote
60   extension appended to it exists, arguments will be read from it by
61   default.
62
63   If the -h option is provided @XREMOTE_HOST is ignored.
64
65   If the -i option is provided, all the files are installed and
66   scripts run in the specified directory on the remote host, but the
67   main executable and post-run commands are ignored
68
69   If no argument is provided to @XREMOTE_HOST, and the -h option is
70   not specified, the environment variable \$XREMOTE_HOST is used
71   instead
72
73   Contact <francois@fleuret.org> for comments.
74
75 EOF
76     return 0
77 }
78
79 function cleanup_remote_tmp () {
80     if [[ "${REMOTE_HOST}" ]] && [[ "${REMOTE_DIR}" ]] && [[ ! "${ARG_DIR}" ]]
81     then
82         echo "xremote: Clean up remote workdir."
83     fi
84 }
85
86 ######################################################################
87
88 while [[ "$1" =~ ^- ]]
89 do
90     case "$1"
91     in
92         -h)
93             shift
94             ARG_HOST="$1"
95             echo "xremote: remote forced to ${ARG_HOST}"
96             ;;
97
98         -i)
99             shift
100             ARG_DIR="$1"
101             echo "xremote: remote dir set to ${ARG_DIR}"
102             ;;
103
104         *)
105             echo "Unknown option $1"
106             exit 1
107             ;;
108     esac
109     shift
110 done
111
112 ######################################################################
113
114 [[ -a "$1" ]] || (help && echo >&2 "Cannot find $1" && exit 1)
115
116 cd "$(dirname "$1")"
117
118 main="$(basename "$1")"
119 main_config="${main}.xremote"
120
121 if [[ -f "${main_config}" ]]
122 then
123     echo "xremote: found ${main_config}"
124 else
125     main_config="${main}"
126 fi
127
128 shift
129
130 trap cleanup_remote_tmp EXIT
131
132 ######################################################################
133
134 while read line
135 do
136
137     if [[ "${line}" =~ '@XREMOTE' ]]
138     then
139
140         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
141         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
142
143         case "${label}" in
144
145             EXEC)
146                 check_remote_is_defined
147                 [[ "${REMOTE_EXEC}" ]] && (exit "Remote executable already defined!" >&2 && exit 1)
148                 REMOTE_EXEC="${value}"
149                 ;;
150
151             PRE)
152                 check_remote_is_defined
153                 echo "xremote: ${value}"
154                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
155                 ;;
156
157             SEND)
158                 check_remote_is_defined
159                 echo "xremote: -- sending files --------------------------------------------"
160                 tar ch ${value} | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mxv"
161                 ;;
162
163             HOST)
164                 [[ "${REMOTE_DIR}" ]] && (exit "Remote host already defined!" >&2 && exit 1)
165                 REMOTE_HOST="${ARG_HOST}" # Host given in argument has priority
166                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${value}"
167                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${XREMOTE_HOST}"
168                 [[ "${REMOTE_HOST}" ]] || (echo "xremote: No remote host specified." >&2 && exit 1)
169                 if [[ "${ARG_DIR}" ]]
170                 then
171                     ssh </dev/null "${REMOTE_HOST}" "mkdir -p \"${ARG_DIR}\""
172                     REMOTE_DIR="${ARG_DIR}"
173                 else
174                     REMOTE_DIR="$(ssh </dev/null "${REMOTE_HOST}" mktemp -d /tmp/xremote_\$\(whoami\)_from_"$(hostname)_$(date +%Y%m%d_%H%M%S)".XXXXXX)"
175                 fi
176                 echo "xremote: target is ${REMOTE_HOST}"
177                 ;;
178         esac
179     fi
180
181 done < "${main_config}"
182
183 ######################################################################
184
185 check_remote_is_defined
186
187 tar c "${main}" | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mx"
188
189 if [[ "${ARG_DIR}" ]]
190 then
191     echo "xremote: everything has been set up in ${REMOTE_HOST}:${ARG_DIR}"
192     exit 0
193 fi
194
195 echo "xremote: -- running the executable -----------------------------------"
196
197 if [[ "${REMOTE_EXEC}" ]]
198 then
199     REMOTE_COMMAND="${REMOTE_EXEC} ${main}"
200 else
201     REMOTE_COMMAND="./${main}"
202 fi
203
204 ######################################################################
205
206 # I find this slightly ugly ...
207
208 for s in "$@"
209 do
210   quoted_args="${quoted_args} \"${s}\""
211 done
212
213 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${REMOTE_COMMAND} ${quoted_args}"
214
215 ######################################################################
216
217 # Disable globbing to keep wildcards for the remote side
218
219 echo "xremote: -- retrieving files -----------------------------------------"
220
221 set -f
222
223 while read line
224 do
225     if [[ "${line}" =~ '@XREMOTE' ]]
226     then
227         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
228         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
229         case "${label}" in
230             POST)
231                 check_remote_is_defined
232                 echo "xremote: ${value}"
233                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
234                 ;;
235
236             GET)
237                 check_remote_is_defined
238                 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar 2>/dev/null c ${value}" | tar mxv
239                 ;;
240         esac
241     fi
242
243 done < "${main_config}"
244
245 set +f
246
247 echo "xremote: -- finished -------------------------------------------------"
248
249 ######################################################################