From: "Richard W.M. Jones" <rjones(a)redhat.com>
This allows you to add scripts that run in the context of
the guest the first time it boots.
---
TODO | 2 -
po/POTFILES-ml | 2 +
sysprep/Makefile.am | 40 +++++++++++--
sysprep/firstboot.ml | 102 ++++++++++++++++++++++++++++++++
sysprep/firstboot.mli | 27 +++++++++
sysprep/sysprep_operation_firstboot.ml | 86 +++++++++++++++++++++++++++
sysprep/sysprep_operation_script.ml | 6 +-
sysprep/utils.ml | 12 ++++
sysprep/utils.mli | 3 +
sysprep/virt-sysprep.pod | 26 ++++++++
10 files changed, 297 insertions(+), 9 deletions(-)
create mode 100644 sysprep/firstboot.ml
create mode 100644 sysprep/firstboot.mli
create mode 100644 sysprep/sysprep_operation_firstboot.ml
diff --git a/TODO b/TODO
index 3832371..1232e4d 100644
--- a/TODO
+++ b/TODO
@@ -383,7 +383,6 @@ virt-sysprep ideas
- Windows sysprep
(see:
https://github.com/clalancette/oz/blob/e74ce83283d468fd987583d6837b441608...
)
- (librarian suggests ...)
- . install a firstboot script virt-sysprep --script=/tmp/foo.sh
. run external guestfish script virt-sysprep --fish=/tmp/foo.fish
- if drives are encrypted, then dm-crypt key should be changed
and drives all re-encrypted
@@ -421,7 +420,6 @@ customized with the organization logo etc. Some ideas:
- change the background image to some custom desktop
- change the sign-on messages (/etc/issue.net etc)
- - firstboot script (as suggested by librarian above)
- Windows login script/service
Launch remote sessions over ssh
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index 7f75dc8..76043a0 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -7,6 +7,7 @@ sparsify/progress.ml
sparsify/sparsify.ml
sparsify/sparsify_gettext.ml
sparsify/utils.ml
+sysprep/firstboot.ml
sysprep/main.ml
sysprep/sysprep_gettext.ml
sysprep/sysprep_operation.ml
@@ -18,6 +19,7 @@ sysprep/sysprep_operation_cron_spool.ml
sysprep/sysprep_operation_dhcp_client_state.ml
sysprep/sysprep_operation_dhcp_server_state.ml
sysprep/sysprep_operation_dovecot_data.ml
+sysprep/sysprep_operation_firstboot.ml
sysprep/sysprep_operation_flag_reconfiguration.ml
sysprep/sysprep_operation_hostname.ml
sysprep/sysprep_operation_kerberos_data.ml
diff --git a/sysprep/Makefile.am b/sysprep/Makefile.am
index 50c6e11..5551c2d 100644
--- a/sysprep/Makefile.am
+++ b/sysprep/Makefile.am
@@ -39,16 +39,43 @@ CLEANFILES = \
# Filenames sysprep_operation_<name>.ml in alphabetical order.
operations = \
- abrt_data bash_history blkid_tab ca_certificates cron_spool \
- dhcp_client_state dhcp_server_state dovecot_data flag_reconfiguration \
- hostname kerberos_data lvm_uuids logfiles machine_id mail_spool \
- net_hwaddr pacct_log package_manager_cache pam_data puppet_data_log \
- random_seed rhn_systemid samba_db_log script smolt_uuid ssh_hostkeys \
- ssh_userdir sssd_db_log udev_persistent_net user_account \
+ abrt_data \
+ bash_history \
+ blkid_tab \
+ ca_certificates \
+ cron_spool \
+ dhcp_client_state \
+ dhcp_server_state \
+ dovecot_data \
+ flag_reconfiguration \
+ firstboot \
+ hostname \
+ kerberos_data \
+ lvm_uuids \
+ logfiles \
+ machine_id \
+ mail_spool \
+ net_hwaddr \
+ pacct_log \
+ package_manager_cache \
+ pam_data \
+ puppet_data_log \
+ random_seed \
+ rhn_systemid \
+ samba_db_log \
+ script \
+ smolt_uuid \
+ ssh_hostkeys \
+ ssh_userdir \
+ sssd_db_log \
+ udev_persistent_net \
+ user_account \
utmp yum_uuid
# Alphabetical order.
SOURCES = \
+ firstboot.ml \
+ firstboot.mli \
main.ml \
sysprep_gettext.ml \
sysprep_operation.ml \
@@ -63,6 +90,7 @@ if HAVE_OCAML
OBJECTS = \
sysprep_gettext.cmx \
utils.cmx \
+ firstboot.cmx \
sysprep_operation.cmx \
$(patsubst %,sysprep_operation_%.cmx,$(operations)) \
main.cmx
diff --git a/sysprep/firstboot.ml b/sysprep/firstboot.ml
new file mode 100644
index 0000000..c551bd5
--- /dev/null
+++ b/sysprep/firstboot.ml
@@ -0,0 +1,102 @@
+(* virt-sysprep
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+open Utils
+open Sysprep_operation
+open Sysprep_gettext.Gettext
+
+(* For Linux guests. *)
+let firstboot_dir = "/usr/lib/virt-sysprep"
+
+let firstboot_sh = sprintf "\
+#!/bin/sh -
+
+d=%s/scripts
+logfile=~root/virt-sysprep-firstboot.log
+
+for f in $d/* ; do
+ echo '=== Running' $f '===' >>$logfile
+ $f >>$logfile 2>&1
+ rm $f
+done
+" firstboot_dir
+
+let firstboot_service = sprintf "\
+[Unit]
+Description=virt-sysprep firstboot service
+After=syslog.target network.target
+Before=prefdm.service
+
+[Service]
+Type=oneshot
+ExecStart=%s/firstboot.sh
+RemainAfterExit=yes
+
+[Install]
+WantedBy=default.target
+" firstboot_dir
+
+let failed fs =
+ ksprintf (fun msg -> failwith (s_"firstboot: failed: " ^ msg)) fs
+
+let rec install_service g root =
+ g#mkdir_p firstboot_dir;
+ g#mkdir_p (sprintf "%s/scripts" firstboot_dir);
+ g#write (sprintf "%s/firstboot.sh" firstboot_dir) firstboot_sh;
+ g#chmod 0o755 (sprintf "%s/firstboot.sh" firstboot_dir);
+
+ (* systemd, else assume sysvinit *)
+ if g#is_dir "/etc/systemd" then
+ install_systemd_service g root
+ else
+ install_sysvinit_service g root
+
+(* Install the systemd firstboot service, if not installed already. *)
+and install_systemd_service g root =
+ g#write (sprintf "%s/firstboot.service" firstboot_dir) firstboot_service;
+ g#mkdir_p "/etc/systemd/system/default.target.wants";
+ g#ln_sf (sprintf "%s/firstboot.service" firstboot_dir)
+ "/etc/systemd/system/default.target.wants"
+
+and install_sysvinit_service g root =
+ g#mkdir_p "/etc/rc.d/rc2.d";
+ g#mkdir_p "/etc/rc.d/rc3.d";
+ g#mkdir_p "/etc/rc.d/rc5.d";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/rc.d/rc2.d/99virt-sysprep-firstboot";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/rc.d/rc3.d/99virt-sysprep-firstboot";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/rc.d/rc5.d/99virt-sysprep-firstboot"
+
+let add_firstboot_script g root id content =
+ let typ = g#inspect_get_type root in
+ let distro = g#inspect_get_distro root in
+ match typ, distro with
+ | "linux", _ ->
+ install_service g root;
+ let filename =
+ sprintf "%s/scripts/%g-%s-%s"
+ firstboot_dir (Unix.time ()) (string_random8 ()) id in
+ g#write filename content;
+ g#chmod 0o755 filename
+
+ | _ ->
+ failed "guest type %s/%s is not supported" typ distro
diff --git a/sysprep/firstboot.mli b/sysprep/firstboot.mli
new file mode 100644
index 0000000..910dd75
--- /dev/null
+++ b/sysprep/firstboot.mli
@@ -0,0 +1,27 @@
+(* virt-sysprep
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+val add_firstboot_script : Guestfs.guestfs -> string -> string -> string ->
unit
+ (** [add_firstboot_script g root id content] adds a firstboot
+ script called [shortname] containing [content].
+
+ NB. [content] is the contents of the script, {b not} a filename.
+
+ [id] should be a short name containing only 7 bit ASCII [-a-z0-9].
+
+ You should make sure the filesystem is relabelled after calling this. *)
diff --git a/sysprep/sysprep_operation_firstboot.ml
b/sysprep/sysprep_operation_firstboot.ml
new file mode 100644
index 0000000..d0f3293
--- /dev/null
+++ b/sysprep/sysprep_operation_firstboot.ml
@@ -0,0 +1,86 @@
+(* virt-sysprep
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+open Utils
+open Sysprep_operation
+open Sysprep_gettext.Gettext
+
+module G = Guestfs
+
+let files = ref []
+
+let make_id_from_filename filename =
+ let ret = String.copy filename in
+ for i = 0 to String.length ret - 1 do
+ let c = String.unsafe_get ret i in
+ if not ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9')) then
+ String.unsafe_set ret i '-'
+ done;
+ ret
+
+let firstboot_perform g root =
+ (* Read the files and add them using the {!Firstboot} module. *)
+ List.iter (
+ fun filename ->
+ let content = read_whole_file filename in
+ let basename = Filename.basename filename in
+ let id = make_id_from_filename basename in
+ Firstboot.add_firstboot_script g root id content
+ ) !files;
+ [ `Created_files ]
+
+let firstboot_op = {
+ name = "firstboot";
+
+ (* enabled_by_default because we only do anything if the
+ * --firstboot parameter is used.
+ *)
+ enabled_by_default = true;
+
+ heading = s_"Add scripts to run once at next boot";
+ pod_description = Some (s_"\
+Supply one of more shell scripts (using the I<--firstboot> option).
+
+These are run the first time the guest boots, and then are
+deleted. So these are useful for performing last minute
+configuration that must run in the context of the guest
+operating system, for example C<yum update>.
+
+Output or errors from the scripts are written to
+C<~root/virt-sysprep-firstboot.log> (in the guest).
+
+Currently this is only implemented for Linux guests using
+either System V init, or systemd.");
+
+ extra_args = [
+ ("--firstboot", Arg.String (fun s -> files := s :: !files),
+ s_"script" ^ " " ^ s_"run script once next time guest
boots"),
+ s_"\
+Run script(s) once next time the guest boots. You can supply
+the I<--firstboot> option as many times as needed."
+ ];
+
+ perform_on_filesystems = Some firstboot_perform;
+ perform_on_devices = None;
+}
+
+let () = register_operation firstboot_op
diff --git a/sysprep/sysprep_operation_script.ml b/sysprep/sysprep_operation_script.ml
index 9337701..a49bc3c 100644
--- a/sysprep/sysprep_operation_script.ml
+++ b/sysprep/sysprep_operation_script.ml
@@ -134,7 +134,11 @@ guest's DNS configuration file, but C<rm /etc/resolv.conf>
would
(try to) remove the host's file.
Normally a temporary mount point for the guest is used, but you
-can choose a specific one by using the I<--scriptdir> parameter.");
+can choose a specific one by using the I<--scriptdir> parameter.
+
+B<Note:> This is different from I<--firstboot> scripts (which run
+in the context of the guest when it is booting first time).
+I<--script> scripts run on the host, not in the guest.");
extra_args = [
("--scriptdir", Arg.String set_scriptdir, s_"dir" ^ " "
^ s_"Mount point on host"),
s_"\
diff --git a/sysprep/utils.ml b/sysprep/utils.ml
index 3b3ad8a..6f76713 100644
--- a/sysprep/utils.ml
+++ b/sysprep/utils.ml
@@ -86,3 +86,15 @@ let skip_dashes str =
let compare_command_line_args a b =
compare (String.lowercase (skip_dashes a)) (String.lowercase (skip_dashes b))
+
+let read_whole_file path =
+ let buf = Buffer.create 1024 in
+ let chan = open_in path in
+ let rec loop () =
+ let line = input_line chan in
+ Buffer.add_string buf line;
+ loop ()
+ in
+ (try loop () with End_of_file -> ());
+ close_in chan;
+ Buffer.contents buf
diff --git a/sysprep/utils.mli b/sysprep/utils.mli
index 0ecb8da..351b936 100644
--- a/sysprep/utils.mli
+++ b/sysprep/utils.mli
@@ -52,3 +52,6 @@ val compare_command_line_args : string -> string -> int
(** Compare two command line arguments (eg. ["-a"] and ["--V"]),
ignoring leading dashes and case. Note this assumes the
strings are 7 bit ASCII. *)
+
+val read_whole_file : string -> string
+(** Read whole file into memory. *)
diff --git a/sysprep/virt-sysprep.pod b/sysprep/virt-sysprep.pod
index 66bc710..71900ca 100755
--- a/sysprep/virt-sysprep.pod
+++ b/sysprep/virt-sysprep.pod
@@ -383,6 +383,32 @@ to pay for disk space), then instead of copying the template, you
can
run L<virt-resize(1)>. Virt-resize performs a copy and resize, and
thus is ideal for cloning guests from a template.
+=head1 FIRSTBOOT VS SCRIPT
+
+The two options I<--firstboot> and I<--script> both supply shell
+scripts that are run against the guest. However these two options are
+significantly different.
+
+I<--firstboot script> uploads the file C<script> into the guest
+and arranges that it will run, in the guest, when the guest is
+next booted. (The script will only run once, at the "first boot").
+
+I<--script script> runs the shell C<script> I<on the host>, with its
+current directory inside the guest filesystem.
+
+If you needed, for example, to C<yum install> new packages, then you
+I<must not> use I<--script> for this, since that would (a) run the
+C<yum> command on the host and (b) wouldn't have access to the same
+resources (repositories, keys, etc.) as the guest. Any command that
+needs to run on the guest I<must> be run via I<--firstboot>.
+
+On the other hand if you need to make adjustments to the guest
+filesystem (eg. copying in files), then I<--script> is ideal since (a)
+it has access to the host filesystem and (b) you will get immediate
+feedback on errors.
+
+Either or both options can be used multiple times on the command line.
+
=head1 SECURITY
Although virt-sysprep removes some sensitive information from the
--
1.7.10.4