cf4ad969b08edf8c52b26dab2bfeaa0be180b7a4
[libguestfs.git] / clone / virt-sysprep.in
1 #!/bin/bash -
2 # @configure_input@
3 # libguestfs virt-sysprep tool
4 # Copyright (C) 2011 Red Hat Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 unset CDPATH
21 program="virt-sysprep"
22 version="@PACKAGE_VERSION@"
23
24 TEMP=`getopt \
25         -o a:c:d:vVx \
26         --long help,add:,connect:,domain:,enable:,format::,hostname:,list-operations,verbose,version \
27         -n $program -- "$@"`
28 if [ $? != 0 ]; then
29     echo "$program: problem parsing the command line arguments"
30     exit 1
31 fi
32 eval set -- "$TEMP"
33
34 # This array accumulates the arguments we pass through to guestfish.
35 declare -a guestfish
36 guestfish[0]="guestfish"
37 guestfish[1]="--rw"
38 guestfish[2]="--listen"
39 guestfish[3]="-i"
40 i=4
41
42 verbose=
43 add_params=0
44 enable=
45 hostname_param=localhost.localdomain
46
47 usage ()
48 {
49     echo "Usage:"
50     echo "  $program [--options] -d domname"
51     echo "  $program [--options] -a disk.img [-a disk.img ...]"
52     echo
53     echo "Read $program(1) man page for more information."
54     echo
55     echo "NOTE: $program modifies the guest or disk image *in place*."
56     exit $1
57 }
58
59 while true; do
60     case "$1" in
61         -a|--add)
62             guestfish[i++]="-a"
63             guestfish[i++]="$2"
64             ((add_params++))
65             shift 2;;
66         -c|--connect)
67             guestfish[i++]="-c"
68             guestfish[i++]="$2"
69             shift 2;;
70         -d|--domain)
71             guestfish[i++]="-d"
72             guestfish[i++]="$2"
73             ((add_params++))
74             shift 2;;
75         --enable)
76             if [ -n "$enable" ]; then
77                 echo "error: --enable option can only be given once"
78                 exit 1
79             fi
80             enable="$2"
81             shift 2;;
82         --format)
83             if [ -n "$2" ]; then
84                 guestfish[i++]="--format=$2"
85             else
86                 guestfish[i++]="--format"
87             fi
88             shift 2;;
89         --help)
90             usage 0;;
91         --hostname)
92             hostname_param="$2"
93             shift 2;;
94         --list-operations)
95             enable=list
96             shift;;
97         -v|--verbose)
98             guestfish[i++]="-v"
99             verbose=yes
100             shift;;
101         -V|--version)
102             echo "$program $version"
103             exit 0;;
104         -x)
105             guestfish[i++]="-x"
106             shift;;
107         --)
108             shift
109             break;;
110         *)
111             echo "Internal error!"
112             exit 1;;
113     esac
114 done
115
116 # Different sysprep operations that can be enabled.  Default is to
117 # enable all of these, although some of them are only done on certain
118 # guest types (see details below).
119 if [ -z "$enable" ]; then
120     hostname=yes
121     net_hwaddr=yes
122     ssh_hostkeys=yes
123     udev_persistent_net=yes
124 elif [ "$enable" = "list" ]; then
125     echo "hostname"
126     echo "net-hwaddr"
127     echo "ssh-hostkeys"
128     echo "udev-persistent-net"
129     exit 0
130 else
131     for opt in $(echo "$enable" | sed 's/,/ /g'); do
132         case "$opt" in
133             hostname)              hostname=yes ;;
134             net-hwaddr)            net_hwaddr=yes ;;
135             ssh-hostkeys)          ssh_hostkeys=yes ;;
136             udev-persistent-net)   udev_persistent_net=yes ;;
137             *)
138                 echo "error: unknown --enable feature: $opt"
139                 exit 1
140         esac
141     done
142 fi
143
144 # Make sure there were no extra parameters on the command line.
145 if [ $# -gt 0 ]; then
146     echo "error: $program: extra parameters on the command line"
147     echo
148     usage 1
149 fi
150
151 # Did the user specify at least one -a or -d option?
152 if [ $add_params -eq 0 ]; then
153     echo "error: $program: you need at least one -a or -d option"
154     echo
155     usage 1
156 fi
157
158 # end of command line parsing
159 #----------------------------------------------------------------------
160
161 set -e
162
163 if [ "$verbose" = "yes" ]; then
164     echo command: "${guestfish[@]}"
165 fi
166
167 # Create a temporary directory for general purpose use during operations.
168 tmpdir="$(mktemp -d)"
169
170 # Increase the amount of memory allocated to the appliance because
171 # we're using augeas.  The user can override this by setting
172 # $LIBGUESTFS_MEMSIZE before running the script.
173 export LIBGUESTFS_MEMSIZE=${LIBGUESTFS_MEMSIZE:-2048}
174
175 # Call guestfish.
176 GUESTFISH_PID=
177 eval $("${guestfish[@]}")
178 if [ -z "$GUESTFISH_PID" ]; then
179     echo "$program: guestfish didn't start up, see error messages above"
180     exit 1
181 fi
182
183 # Helper.
184 gf="guestfish --remote --"
185
186 cleanup ()
187 {
188     $gf exit >/dev/null 2>&1 ||:
189     rm -rf "$tmpdir" ||:
190 }
191 trap cleanup EXIT ERR
192
193 # Launch back-end, inspect for operating systems, and get the guest
194 # root disk.
195 root=$($gf inspect-get-roots)
196
197 if [ "$root" = "" ]; then
198     echo "$program: no operating system was found on this disk"
199     exit 1
200 fi
201
202 if [ "$verbose" = "yes" ]; then
203     echo root: "$root"
204 fi
205
206 # Get the guest type.
207 type="$($gf -inspect-get-type $root)"
208
209 if [ "$type" = "linux" ]; then
210     distro="$($gf -inspect-get-distro $root)"
211 fi
212
213 if [ "$type" = "windows" ]; then
214     systemroot="$($gf -inspect-get-windows-systemroot $root)"
215 fi
216
217 # Start Augeas if it's a Linux guest.
218 if [ "$type" = "linux" ]; then
219     $gf aug-init / 0
220     using_augeas=yes
221 fi
222
223 #----------------------------------------------------------------------
224 # Useful functions.
225
226 # erase_line filename regex
227 #
228 # Erase line(s) in a file that match the given regex.
229 erase_line ()
230 {
231     $gf download "$1" "$tmpdir/file"
232     sed "/$2/d" < "$tmpdir/file" > "$tmpdir/file.1"
233     $gf upload "$tmpdir/file.1" "$1"
234 }
235
236 # prepend_line filename line
237 #
238 # Prepend a line to a file (this is better than appending, because it
239 # works even when the original file isn't terminated with a newline).
240 prepend_line ()
241 {
242     $gf download "$1" "$tmpdir/file"
243     echo "$2" > "$tmpdir/file.1"
244     cat "$tmpdir/file.1" "$tmpdir/file" >> "$tmpdir/file.2"
245     $gf upload "$tmpdir/file.2" "$1"
246 }
247
248 # rm_files wildcard
249 #
250 # Remove files.  Doesn't fail if no files exist.  Note the wildcard
251 # parameter cannot contain spaces or characters that need special
252 # quoting.
253 rm_files ()
254 {
255     files=$($gf glob-expand "$1")
256     for f in $files; do
257         $gf rm "$f"
258     done
259 }
260
261 # rm_file filename
262 #
263 # Remove a single file.  No error if the file doesn't exist or is not
264 # a file.
265 rm_file ()
266 {
267     t=$($gf is-file "$1")
268     if [ "$t" = "true" ]; then
269         $gf rm "$1"
270     fi
271 }
272
273 #----------------------------------------------------------------------
274 # The sysprep operations.
275
276 if [ "$hostname" = "yes" ]; then
277     case "$type/$distro" in
278         linux/fedora)
279             $gf aug-set /files/etc/sysconfig/network/HOSTNAME "$hostname_param"
280             augeas_save_needed=yes
281             ;;
282         linux/debian|linux/ubuntu)
283             $gf write /etc/hostname "$hostname_param"
284             ;;
285     esac
286 fi
287
288 if [ "$net_hwaddr" = "yes" ]; then
289     case "$type/$distro" in
290         linux/fedora)
291             # XXX these filenames can have spaces and untrusted chars in them!
292             nodes=$( $gf aug-ls /files/etc/sysconfig/network-scripts |
293                      grep /files/etc/sysconfig/network-scripts/ifcfg- )
294             for node in $nodes; do
295                 $gf -aug-rm "$node/HWADDR" >/dev/null
296                 augeas_save_needed=yes
297             done
298             ;;
299     esac
300 fi
301
302 if [ "$ssh_hostkeys" = "yes" -a "$type" != "windows" ]; then
303     rm_files "/etc/ssh/*_host_*"
304 fi
305
306 if [ "$udev_persistent_net" = "yes" -a "$type" = "linux" ]; then
307     rm_file /etc/udev/rules.d/70-persistent-net.rules
308 fi
309
310 #----------------------------------------------------------------------
311 # Clean up and close down.
312
313 if [ "$using_augeas" = "yes" -a "$augeas_save_needed" = "yes" ]; then
314     $gf aug-save
315     $gf aug-close
316 fi
317
318 $gf umount-all
319 $gf sync
320 $gf exit
321
322 exit 0