Now read arguments from a .xremote file by default if it exists.
[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 && exit 1)
115
116 main="$(basename "$1")"
117 main_config="${main}.xremote"
118
119 if [[ ! -f "${main}.xremote" ]]
120 then
121     main_config="${main}"
122 fi
123
124 cd "$(dirname "$1")"
125
126 shift
127
128 trap cleanup_remote_tmp EXIT
129
130 ######################################################################
131
132 while read line
133 do
134
135     if [[ "${line}" =~ '@XREMOTE' ]]
136     then
137
138         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
139         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
140
141         case "${label}" in
142
143             EXEC)
144                 check_remote_is_defined
145                 [[ "${REMOTE_EXEC}" ]] && (exit "Remote executable already defined!" >&2 && exit 1)
146                 REMOTE_EXEC="${value}"
147                 ;;
148
149             PRE)
150                 check_remote_is_defined
151                 echo "xremote: ${value}"
152                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
153                 ;;
154
155             SEND)
156                 check_remote_is_defined
157                 echo "xremote: -- sending files --------------------------------------------"
158                 tar ch ${value} | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mxv"
159                 ;;
160
161             HOST)
162                 [[ "${REMOTE_DIR}" ]] && (exit "Remote host already defined!" >&2 && exit 1)
163                 REMOTE_HOST="${ARG_HOST}" # Host given in argument has priority
164                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${value}"
165                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${XREMOTE_HOST}"
166                 [[ "${REMOTE_HOST}" ]] || (echo "xremote: No remote host specified." >&2 && exit 1)
167                 if [[ "${ARG_DIR}" ]]
168                 then
169                     ssh </dev/null "${REMOTE_HOST}" "mkdir -p \"${ARG_DIR}\""
170                     REMOTE_DIR="${ARG_DIR}"
171                 else
172                     REMOTE_DIR="$(ssh </dev/null "${REMOTE_HOST}" mktemp -d /tmp/xremote_\$\(whoami\)_from_"$(hostname)_$(date +%Y%m%d_%H%M%S)".XXXXXX)"
173                 fi
174                 echo "xremote: target is ${REMOTE_HOST}"
175                 ;;
176         esac
177     fi
178
179 done < "${main_config}"
180
181 ######################################################################
182
183 check_remote_is_defined
184
185 tar c "${main}" | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mx"
186
187 if [[ "${ARG_DIR}" ]]
188 then
189     echo "xremote: everything has been set up in ${REMOTE_HOST}:${ARG_DIR}"
190     exit 0
191 fi
192
193 echo "xremote: -- running the executable -----------------------------------"
194
195 if [[ "${REMOTE_EXEC}" ]]
196 then
197     REMOTE_COMMAND="${REMOTE_EXEC} ${main}"
198 else
199     REMOTE_COMMAND="./${main}"
200 fi
201
202 ######################################################################
203
204 # I find this slightly ugly ...
205 for s in "$@"
206 do
207   quoted_args="${quoted_args} \"${s}\""
208 done
209
210 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${REMOTE_COMMAND} ${quoted_args}"
211
212 ######################################################################
213
214 # Disable globbing to keep wildcards for the remote side
215
216 echo "xremote: -- retrieving files -----------------------------------------"
217
218 set -f
219
220 while read line
221 do
222     if [[ "${line}" =~ '@XREMOTE' ]]
223     then
224         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
225         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
226         case "${label}" in
227             POST)
228                 check_remote_is_defined
229                 echo "xremote: ${value}"
230                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
231                 ;;
232
233             GET)
234                 check_remote_is_defined
235                 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar 2>/dev/null c ${value}" | tar mxv
236                 ;;
237         esac
238     fi
239 done < "${main_config}"
240
241 set +f
242
243 echo "xremote: -- finished -------------------------------------------------"
244
245 ######################################################################