Added the -r option to specify where to download result files.
[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 [--help] [-h <remote_host>] [-d <remote_dir>] [-r <local_result_dir>] [-i] <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 -d option is provided, the provided directory is used and
66   kept, instead of a temporary one
67
68   If the -i option is provided, all the files are installed and
69   scripts run in the specified directory on the remote host, but the
70   main executable and post-run commands are ignored
71
72   If the -r option is provided, the result files specified with
73   @XREMOTE_GET will be downloaded there.
74
75   If no argument is provided to @XREMOTE_HOST, and the -h option is
76   not specified, the environment variable \$XREMOTE_HOST is used
77   instead
78
79   Contact <francois@fleuret.org> for comments.
80
81 EOF
82     return 0
83 }
84
85 function cleanup_remote_tmp () {
86     if [[ "${REMOTE_HOST}" ]] && [[ "${REMOTE_DIR}" ]]
87     then
88         if [[ "${ARG_DIR}" ]]
89         then
90             echo "xremote: Keeping remote workdir."
91         else
92             echo "xremote: Cleaning up temporary remote workdir."
93             ssh "${REMOTE_HOST}" rm -rf "${REMOTE_DIR}"
94         fi
95     fi
96 }
97
98 ######################################################################
99
100 while [[ "$1" =~ ^- ]]
101 do
102     case "$1"
103     in
104         -h)
105             shift
106             ARG_HOST="$1"
107             [[ ${ARG_HOST} ]] || (echo "xremote: Hostname missing." && exit 1)
108             echo "xremote: remote forced to ${ARG_HOST}"
109             ;;
110
111         -d)
112             shift
113             ARG_DIR="$1"
114             [[ ${ARG_DIR} ]] || (echo "xremote: Directory missing." && exit 1)
115             echo "xremote: remote dir set to ${ARG_DIR}"
116             ;;
117
118         -i)
119             NO_RUN=1
120             echo "xremote: no run"
121             ;;
122
123         -r)
124             shift
125             ARG_RESULT_DIR="$1"
126             [[ ${ARG_RESULT_DIR} ]] || (echo "xremote: Directory missing." && exit 1)
127             echo "xremote: result dir set to ${ARG_RESULT_DIR}"
128             ;;
129
130         --help)
131             help
132             exit 0
133             ;;
134
135         *)
136             echo "xremote: Unknown option $1"
137             exit 1
138             ;;
139     esac
140     shift
141 done
142
143 ######################################################################
144
145 [[ "$1" ]] || (echo "xremote: Script name missing" && exit 1)
146
147 [[ -a "$1" ]] || (help && echo >&2 "xremote: Cannot find script \`$1'" && exit 1)
148
149 cd "$(dirname "$1")"
150
151 main="$(basename "$1")"
152 main_config="${main}.xremote"
153
154 if [[ -f "${main_config}" ]]
155 then
156     echo "xremote: found ${main_config}"
157 else
158     main_config="${main}"
159 fi
160
161 shift
162
163 trap cleanup_remote_tmp EXIT
164
165 ######################################################################
166
167 while read line
168 do
169
170     if [[ "${line}" =~ '@XREMOTE' ]]
171     then
172
173         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
174         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
175
176         case "${label}" in
177
178             EXEC)
179                 check_remote_is_defined
180                 [[ "${REMOTE_EXEC}" ]] && (exit "Remote executable already defined!" >&2 && exit 1)
181                 REMOTE_EXEC="${value}"
182                 ;;
183
184             PRE)
185                 check_remote_is_defined
186                 echo "xremote: ${value}"
187                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
188                 ;;
189
190             SEND)
191                 check_remote_is_defined
192                 echo "xremote: -- sending files --------------------------------------------"
193                 tar ch ${value} | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mxv"
194                 ;;
195
196             HOST)
197                 [[ "${REMOTE_DIR}" ]] && (exit "Remote host already defined!" >&2 && exit 1)
198                 REMOTE_HOST="${ARG_HOST}" # Host given in argument has priority
199                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${value}"
200                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${XREMOTE_HOST}"
201                 [[ "${REMOTE_HOST}" ]] || (echo "xremote: No remote host specified." >&2 && exit 1)
202                 if [[ "${ARG_DIR}" ]]
203                 then
204                     ssh </dev/null "${REMOTE_HOST}" "mkdir -p \"${ARG_DIR}\""
205                     REMOTE_DIR="${ARG_DIR}"
206                 else
207                     REMOTE_DIR="$(ssh </dev/null "${REMOTE_HOST}" mktemp -d /tmp/xremote_\$\(whoami\)_from_"$(hostname)_$(date +%Y%m%d_%H%M%S)".XXXXXX)"
208                 fi
209                 echo "xremote: target is ${REMOTE_HOST}"
210                 ;;
211         esac
212     fi
213
214 done < "${main_config}"
215
216 ######################################################################
217
218 check_remote_is_defined
219
220 tar c "${main}" | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mx"
221
222 if [[ "${NO_RUN}" ]]
223 then
224     echo "xremote: everything has been set up in ${REMOTE_HOST}:${ARG_DIR}"
225     exit 0
226 fi
227
228 echo "xremote: -- running the executable -----------------------------------"
229
230 if [[ "${REMOTE_EXEC}" ]]
231 then
232     REMOTE_COMMAND="${REMOTE_EXEC} ${main}"
233 else
234     REMOTE_COMMAND="./${main}"
235 fi
236
237 ######################################################################
238
239 # I find this slightly ugly ...
240
241 for s in "$@"
242 do
243   quoted_args="${quoted_args} \"${s}\""
244 done
245
246 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${REMOTE_COMMAND} ${quoted_args}"
247
248 ######################################################################
249
250 # Disable globbing to keep wildcards for the remote side
251
252 echo "xremote: -- retrieving files -----------------------------------------"
253
254 set -f
255
256 if [[ "${ARG_RESULT_DIR}" ]]
257 then
258     RESULT_DIR="${ARG_RESULT_DIR}"
259 else
260     RESULT_DIR="."
261 fi
262
263 while read line
264 do
265     if [[ "${line}" =~ '@XREMOTE' ]]
266     then
267         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
268         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
269         case "${label}" in
270             POST)
271                 check_remote_is_defined
272                 echo "xremote: ${value}"
273                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
274                 ;;
275
276             GET)
277                 check_remote_is_defined
278                 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar 2>/dev/null c ${value}" | tar mxv -C "${RESULT_DIR}"
279                 ;;
280         esac
281     fi
282
283 done < "${main_config}"
284
285 set +f
286
287 echo "xremote: -- finished -------------------------------------------------"
288
289 ######################################################################