virt-sysprep: Switch to using guestmount, add more features.
[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 # Uncomment this to see every shell command that is executed.
25 #set -x
26
27 TEMP=`getopt \
28         -o a:c:d:vVx \
29         --long help,add:,connect:,domain:,enable:,format::,hostname:,list-operations,selinux-relabel,no-selinux-relabel,verbose,version \
30         -n $program -- "$@"`
31 if [ $? != 0 ]; then
32     echo "$program: problem parsing the command line arguments"
33     exit 1
34 fi
35 eval set -- "$TEMP"
36
37 # This array accumulates the arguments we pass through to guestmount.
38 declare -a params
39 i=0
40
41 verbose=
42 add_params=0
43 enable=
44 hostname_param=localhost.localdomain
45 selinux_relabel=auto
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             params[i++]="-a"
63             params[i++]="$2"
64             ((add_params++))
65             shift 2;;
66         -c|--connect)
67             params[i++]="-c"
68             params[i++]="$2"
69             shift 2;;
70         -d|--domain)
71             params[i++]="-d"
72             params[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                 params[i++]="--format=$2"
85             else
86                 params[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         --selinux-relabel)
98             selinux_relabel=yes
99             shift;;
100         --no-selinux-relabel)
101             selinux_relabel=no
102             shift;;
103         -v|--verbose)
104             params[i++]="-v"
105             verbose=yes
106             shift;;
107         -V|--version)
108             echo "$program $version"
109             exit 0;;
110         -x)
111             # Can't pass the -x option directly to guestmount because
112             # that stops guestmount from forking, which means we can't
113             # coordinate with guestmount when it has finished
114             # initializing.  So instead set just the underlying option
115             # in libguestfs by exporting LIBGUESTFS_TRACE.
116             # Unfortunately (a) this omits FUSE calls, but don't worry
117             # about that for now, and more importantly (b) trace
118             # messages disappear into never-never land after the fork.
119             export LIBGUESTFS_TRACE=1
120             shift;;
121         --)
122             shift
123             break;;
124         *)
125             echo "Internal error!"
126             exit 1;;
127     esac
128 done
129
130 # Different sysprep operations that can be enabled.  Default is to
131 # enable all of these, although some of them are only done on certain
132 # guest types (see details below).
133 if [ -z "$enable" ]; then
134     dhcp_client_state=yes
135     dhcp_server_state=yes
136     hostname=yes
137     logfiles=yes
138     net_hwaddr=yes
139     random_seed=yes
140     smolt_uuid=yes
141     ssh_hostkeys=yes
142     udev_persistent_net=yes
143     yum_uuid=yes
144 elif [ "$enable" = "list" ]; then
145     echo "dhcp-client-state"
146     echo "dhcp-server-state"
147     echo "hostname"
148     echo "logfiles"
149     echo "net-hwaddr"
150     echo "random-seed"
151     echo "smolt-uuid"
152     echo "ssh-hostkeys"
153     echo "udev-persistent-net"
154     echo "yum-uuid"
155     exit 0
156 else
157     for opt in $(echo "$enable" | sed 's/,/ /g'); do
158         case "$opt" in
159             dhcp-client-state)     dhcp_client_state=yes ;;
160             dhcp-server-state)     dhcp_server_state=yes ;;
161             hostname)              hostname=yes ;;
162             logfiles)              logfiles=yes ;;
163             net-hwaddr)            net_hwaddr=yes ;;
164             random-seed)           random_seed=yes ;;
165             smolt-uuid)            smolt_uuid=yes ;;
166             ssh-hostkeys)          ssh_hostkeys=yes ;;
167             udev-persistent-net)   udev_persistent_net=yes ;;
168             yum-uuid)              yum_uuid=yes ;;
169             *)
170                 echo "error: unknown --enable feature: $opt"
171                 exit 1
172         esac
173     done
174 fi
175
176 # Make sure there were no extra parameters on the command line.
177 if [ $# -gt 0 ]; then
178     echo "error: $program: extra parameters on the command line"
179     echo
180     usage 1
181 fi
182
183 # Did the user specify at least one -a or -d option?
184 if [ $add_params -eq 0 ]; then
185     echo "error: $program: you need at least one -a or -d option"
186     echo
187     usage 1
188 fi
189
190 # end of command line parsing
191 #----------------------------------------------------------------------
192
193 set -e
194
195 if [ "$verbose" = "yes" ]; then
196     echo params: "${params[@]}"
197 fi
198
199 # Create a temporary directory for general purpose use during operations.
200 tmpdir="$(mktemp -d)"
201
202 cleanup ()
203 {
204     if [ -d $tmpdir/mnt ]; then
205         fusermount -u $tmpdir/mnt >/dev/null 2>&1 ||:
206     fi
207     rm -rf $tmpdir ||:
208 }
209 trap cleanup EXIT ERR
210
211 # Run virt-inspector and grab inspection information about this guest.
212 virt-inspector "${params[@]}" > $tmpdir/xml
213 xmlstarlet sel -t -c \
214     "string(/operatingsystems/operatingsystem[position()=1]/name)" \
215     $tmpdir/xml > $tmpdir/type
216 xmlstarlet sel -t -c \
217     "string(/operatingsystems/operatingsystem[position()=1]/distro)" \
218     $tmpdir/xml > $tmpdir/distro ||:
219 xmlstarlet sel -t -c \
220     "string(/operatingsystems/operatingsystem[position()=1]/package_format)" \
221     $tmpdir/xml > $tmpdir/package_format ||:
222 xmlstarlet sel -t -c \
223     "string(/operatingsystems/operatingsystem[position()=1]/package_management)" \
224     $tmpdir/xml > $tmpdir/package_management ||:
225
226 type="$(cat $tmpdir/type)"
227 distro="$(cat $tmpdir/distro)"
228 package_format="$(cat $tmpdir/package_format)"
229 package_management="$(cat $tmpdir/package_management)"
230
231 # Mount the disk.
232 mkdir $tmpdir/mnt
233 guestmount --rw -i "${params[@]}" $tmpdir/mnt
234
235 mnt="$tmpdir/mnt"
236
237 #----------------------------------------------------------------------
238 # The sysprep operations.
239
240 if [ "$dhcp_client_state" = "yes" ]; then
241     case "$type" in
242         linux)
243             rm -rf $mnt/var/lib/dhclient/*
244             # RHEL 3:
245             rm -rf $mnt/var/lib/dhcp/*
246             ;;
247     esac
248 fi
249
250 if [ "$dhcp_server_state" = "yes" ]; then
251     case "$type" in
252         linux)
253             rm -rf $mnt/var/lib/dhcpd/*
254             ;;
255     esac
256 fi
257
258 if [ "$hostname" = "yes" ]; then
259     case "$type/$distro" in
260         linux/fedora)
261             echo "HOSTNAME=$hostname_param" > $mnt/etc/sysconfig/network.new
262             sed '/^HOSTNAME=/d' < $mnt/etc/sysconfig/network >> $mnt/etc/sysconfig/network.new
263             mv -f $mnt/etc/sysconfig/network.new $mnt/etc/sysconfig/network
264             created_files=yes
265             ;;
266         linux/debian|linux/ubuntu)
267             echo "$hostname_param" > $mnt/etc/hostname
268             created_files=yes
269             ;;
270     esac
271 fi
272
273 if [ "$logfiles" = "yes" ]; then
274     case "$type" in
275         linux)
276             rm -rf $mnt/var/log/*.log*
277             rm -rf $mnt/var/log/audit/*
278             rm -rf $mnt/var/log/btmp*
279             rm -rf $mnt/var/log/cron*
280             rm -rf $mnt/var/log/dmesg*
281             rm -rf $mnt/var/log/lastlog*
282             rm -rf $mnt/var/log/maillog*
283             rm -rf $mnt/var/log/mail/*
284             rm -rf $mnt/var/log/messages*
285             rm -rf $mnt/var/log/secure*
286             rm -rf $mnt/var/log/spooler*
287             rm -rf $mnt/var/log/tallylog*
288             rm -rf $mnt/var/log/wtmp*
289             ;;
290     esac
291 fi
292
293 if [ "$net_hwaddr" = "yes" ]; then
294     case "$type/$distro" in
295         linux/fedora)
296             if [ -d $mnt/etc/sysconfig/network-scripts ]; then
297                 rm_hwaddr ()
298                 {
299                     sed '/^HWADDR=/d' < "$1" > "$1.new"
300                     mv -f "$1.new" "$1"
301                 }
302                 export -f rm_hwaddr
303                 find $mnt/etc/sysconfig/network-scripts \
304                     -name 'ifcfg-*' -type f \
305                     -exec bash -c 'rm_hwaddr "$0"' {} \;
306                 created_files=yes
307             fi
308             ;;
309     esac
310 fi
311
312 if [ "$random_seed" = "yes" -a "$type" = "linux" ]; then
313     f=
314     if [ -f $mnt/var/lib/random-seed ]; then
315         # Fedora
316         f=$mnt/var/lib/random-seed
317     elif [ -f $mnt/var/lib/urandom/random-seed ]; then
318         # Debian
319         f=$mnt/var/lib/urandom/random-seed
320     fi
321     if [ -n "$f" ]; then
322         dd if=/dev/random of="$f" bs=8 count=1 conv=nocreat,notrunc 2>/dev/null
323     fi
324 fi
325
326 if [ "$smolt_uuid" = "yes" -a "$type" = "linux" ]; then
327     rm -f $mnt/etc/sysconfig/hw-uuid
328     rm -f $mnt/etc/smolt/uuid
329     rm -f $mnt/etc/smolt/hw-uuid
330 fi
331
332 if [ "$ssh_hostkeys" = "yes" -a "$type" != "windows" ]; then
333     rm -rf $mnt/etc/ssh/*_host_*
334 fi
335
336 if [ "$udev_persistent_net" = "yes" -a "$type" = "linux" ]; then
337     rm -f $mnt/etc/udev/rules.d/70-persistent-net.rules
338 fi
339
340 if [ "$yum_uuid" = "yes" -a "$package_management" = "yum" ]; then
341     rm -f $mnt/var/lib/yum/uuid
342 fi
343
344 #----------------------------------------------------------------------
345 # Clean up and close down.
346
347 # If we created any new files and the guest uses SELinux, then we have
348 # to relabel the filesystem on boot.  Could do with a better way to
349 # test "guest uses SELinux" (XXX).
350 case "$selinux_relabel/$created_files" in
351     yes/*)
352         touch $mnt/.autorelabel;;
353     auto/yes)
354         case "$type/$distro" in
355             linux/fedora|linux/rhel|linux/centos|linux/scientificlinux|linux/redhat-based)
356                 touch $mnt/.autorelabel
357                 ;;
358         esac
359         ;;
360 esac
361
362 sync
363
364 fusermount -u $tmpdir/mnt
365 rm -rf $tmpdir
366
367 trap - EXIT ERR
368
369 exit 0