2 # kickstart.py : Apply kickstart configuration to a system
4 # Copyright 2007, Red Hat Inc.
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; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 import system_config_keyboard
.keyboard
as keyboard
31 import rhpl
.keyboard
as keyboard
33 import pykickstart
.commands
as kscommands
34 import pykickstart
.constants
as ksconstants
35 import pykickstart
.errors
as kserrors
36 import pykickstart
.parser
as ksparser
37 import pykickstart
.version
as ksversion
39 import imgcreate
.errors
as errors
40 import imgcreate
.fs
as fs
42 def read_kickstart(path
):
43 """Parse a kickstart file and return a KickstartParser instance.
45 This is a simple utility function which takes a path to a kickstart file,
46 parses it and returns a pykickstart KickstartParser instance which can
47 be then passed to an ImageCreator constructor.
49 If an error occurs, a CreatorError exception is thrown.
52 version
= ksversion
.makeVersion()
53 ks
= ksparser
.KickstartParser(version
)
55 ksfile
= urlgrabber
.urlgrab(path
)
56 ks
.readKickstart(ksfile
)
57 # Fallback to e.args[0] is a workaround for bugs in urlgragger and pykickstart.
59 raise errors
.KickstartError("Failed to read kickstart file "
60 "'%s' : %s" % (path
, e
.strerror
or
62 except kserrors
.KickstartError
, e
:
63 raise errors
.KickstartError("Failed to parse kickstart file "
64 "'%s' : %s" % (path
, e
))
67 def build_name(kscfg
, prefix
= None, suffix
= None, maxlen
= None):
68 """Construct and return an image name string.
70 This is a utility function to help create sensible name and fslabel
71 strings. The name is constructed using the sans-prefix-and-extension
72 kickstart filename and the supplied prefix and suffix.
74 If the name exceeds the maxlen length supplied, the prefix is first dropped
75 and then the kickstart filename portion is reduced until it fits. In other
76 words, the suffix takes precedence over the kickstart portion and the
77 kickstart portion takes precedence over the prefix.
79 kscfg -- a path to a kickstart file
80 prefix -- a prefix to prepend to the name; defaults to None, which causes
82 suffix -- a suffix to append to the name; defaults to None, which causes
83 a YYYYMMDDHHMM suffix to be used
84 maxlen -- the maximum length for the returned string; defaults to None,
85 which means there is no restriction on the name length
87 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
90 name
= os
.path
.basename(kscfg
)
98 suffix
= time
.strftime("%Y%m%d%H%M")
100 if name
.startswith(prefix
):
101 name
= name
[len(prefix
):]
103 ret
= prefix
+ name
+ "-" + suffix
104 if not maxlen
is None and len(ret
) > maxlen
:
105 ret
= name
[:maxlen
- len(suffix
) - 1] + "-" + suffix
109 class KickstartConfig(object):
110 """A base class for applying kickstart configurations to a system."""
111 def __init__(self
, instroot
):
112 self
.instroot
= instroot
114 def path(self
, subpath
):
115 return self
.instroot
+ subpath
118 os
.chroot(self
.instroot
)
121 def call(self
, args
):
122 if not os
.path
.exists("%s/%s" %(self
.instroot
, args
[0])):
123 raise errors
.KickstartError("Unable to run %s!" %(args))
124 subprocess
.call(args
, preexec_fn
= self
.chroot
)
129 class LanguageConfig(KickstartConfig
):
130 """A class to apply a kickstart language configuration to a system."""
131 def apply(self
, kslang
):
132 lang
= kslang
.lang
or "en_US.UTF-8"
134 f
= open(self
.path("/etc/locale.conf"), "w+")
135 f
.write("LANG=\"" + lang
+ "\"\n")
138 class KeyboardConfig(KickstartConfig
):
139 """A class to apply a kickstart keyboard configuration to a system."""
140 def apply(self
, kskeyboard
):
141 k
= keyboard
.Keyboard()
142 if kskeyboard
.keyboard
:
143 k
.set(kskeyboard
.keyboard
)
144 k
.write(self
.instroot
)
146 class TimezoneConfig(KickstartConfig
):
147 """A class to apply a kickstart timezone configuration to a system."""
148 def apply(self
, kstimezone
):
149 tz
= kstimezone
.timezone
or "America/New_York"
150 utc
= str(kstimezone
.isUtc
)
152 # /etc/localtime is a symlink with glibc > 2.15-41
153 if os
.path
.islink(self
.path("/etc/localtime")):
154 os
.unlink(self
.path("/etc/localtime"))
155 os
.symlink("/usr/share/zoneinfo/%s" %(tz
,),
156 self
.path("/etc/localtime"))
159 shutil
.copy2(self
.path("/usr/share/zoneinfo/%s" %(tz
,)),
160 self
.path("/etc/localtime"))
161 except (OSError, shutil
.Error
) as e
:
162 log
.error("Error copying timezone: %s" %(e
.strerror
,))
165 class AuthConfig(KickstartConfig
):
166 """A class to apply a kickstart authconfig configuration to a system."""
167 def apply(self
, ksauthconfig
):
168 if not os
.path
.exists(self
.path("/usr/sbin/authconfig")):
171 auth
= ksauthconfig
.authconfig
or "--useshadow --enablemd5"
172 args
= ["/usr/sbin/authconfig", "--update", "--nostart"]
173 self
.call(args
+ auth
.split())
175 class FirewallConfig(KickstartConfig
):
176 """A class to apply a kickstart firewall configuration to a system."""
177 def apply(self
, ksfirewall
):
178 if not os
.path
.exists(self
.path("/usr/sbin/lokkit")):
180 args
= ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
181 if ksfirewall
.enabled
:
182 args
.append("--enabled")
184 for port
in ksfirewall
.ports
:
185 args
.append("--port=%s" %(port
,))
186 for svc
in ksfirewall
.services
:
187 args
.append("--service=%s" %(svc
,))
188 for dev
in ksfirewall
.trusts
:
189 args
.append("--trust=%s" %(dev
,))
191 args
.append("--disabled")
195 class RootPasswordConfig(KickstartConfig
):
196 """A class to apply a kickstart root password configuration to a system."""
198 self
.call(["/usr/bin/passwd", "-d", "root"])
200 def set_encrypted(self
, password
):
201 self
.call(["/usr/sbin/usermod", "-p", password
, "root"])
203 def set_unencrypted(self
, password
):
204 for p
in ("/bin/echo", "/usr/bin/passwd"):
205 if not os
.path
.exists("%s/%s" %(self
.instroot
, p
)):
206 raise errors
.KickstartError("Unable to set unencrypted password due to lack of %s" % p
)
208 p1
= subprocess
.Popen(["/bin/echo", password
],
209 stdout
= subprocess
.PIPE
,
210 preexec_fn
= self
.chroot
)
211 p2
= subprocess
.Popen(["/usr/bin/passwd", "--stdin", "root"],
213 stdout
= subprocess
.PIPE
,
214 preexec_fn
= self
.chroot
)
217 def apply(self
, ksrootpw
):
218 if ksrootpw
.isCrypted
:
219 self
.set_encrypted(ksrootpw
.password
)
220 elif ksrootpw
.password
!= "":
221 self
.set_unencrypted(ksrootpw
.password
)
225 class ServicesConfig(KickstartConfig
):
226 """A class to apply a kickstart services configuration to a system."""
227 def apply(self
, ksservices
):
228 if not os
.path
.exists(self
.path("/sbin/chkconfig")):
230 for s
in ksservices
.enabled
:
231 self
.call(["/sbin/chkconfig", s
, "on"])
232 for s
in ksservices
.disabled
:
233 self
.call(["/sbin/chkconfig", s
, "off"])
235 class XConfig(KickstartConfig
):
236 """A class to apply a kickstart X configuration to a system."""
237 def apply(self
, ksxconfig
):
239 f
= open(self
.path("/etc/inittab"), "rw+")
241 buf
= buf
.replace("id:3:initdefault", "id:5:initdefault")
245 if ksxconfig
.defaultdesktop
:
246 f
= open(self
.path("/etc/sysconfig/desktop"), "w")
247 f
.write("DESKTOP="+ksxconfig
.defaultdesktop
+"\n")
250 class RPMMacroConfig(KickstartConfig
):
251 """A class to apply the specified rpm macros to the filesystem"""
255 f
= open(self
.path("/etc/rpm/macros.imgcreate"), "w+")
257 f
.write("%_excludedocs 1\n")
258 if not selinux_enabled(ks
):
259 f
.write("%__file_context_path %{nil}\n")
260 if inst_langs(ks
) != None:
261 f
.write("%_install_langs ")
262 f
.write(inst_langs(ks
))
266 class NetworkConfig(KickstartConfig
):
267 """A class to apply a kickstart network configuration to a system."""
268 def write_ifcfg(self
, network
):
269 p
= self
.path("/etc/sysconfig/network-scripts/ifcfg-" + network
.device
)
274 f
.write("DEVICE=%s\n" % network
.device
)
275 f
.write("BOOTPROTO=%s\n" % network
.bootProto
)
277 if network
.bootProto
.lower() == "static":
279 f
.write("IPADDR=%s\n" % network
.ip
)
281 f
.write("NETMASK=%s\n" % network
.netmask
)
284 f
.write("ONBOOT=on\n")
286 f
.write("ONBOOT=off\n")
289 f
.write("ESSID=%s\n" % network
.essid
)
292 if network
.ethtool
.find("autoneg") == -1:
293 network
.ethtool
= "autoneg off " + network
.ethtool
294 f
.write("ETHTOOL_OPTS=%s\n" % network
.ethtool
)
296 if network
.bootProto
.lower() == "dhcp":
298 f
.write("DHCP_HOSTNAME=%s\n" % network
.hostname
)
299 if network
.dhcpclass
:
300 f
.write("DHCP_CLASSID=%s\n" % network
.dhcpclass
)
303 f
.write("MTU=%s\n" % network
.mtu
)
307 def write_wepkey(self
, network
):
308 if not network
.wepkey
:
311 p
= self
.path("/etc/sysconfig/network-scripts/keys-" + network
.device
)
314 f
.write("KEY=%s\n" % network
.wepkey
)
317 def write_sysconfig(self
, useipv6
, hostname
, gateway
):
318 path
= self
.path("/etc/sysconfig/network")
322 f
.write("NETWORKING=yes\n")
325 f
.write("NETWORKING_IPV6=yes\n")
327 f
.write("NETWORKING_IPV6=no\n")
330 f
.write("HOSTNAME=%s\n" % hostname
)
332 f
.write("HOSTNAME=localhost.localdomain\n")
335 f
.write("GATEWAY=%s\n" % gateway
)
339 def write_hosts(self
, hostname
):
341 if hostname
and hostname
!= "localhost.localdomain":
342 localline
+= hostname
+ " "
343 l
= hostname
.split(".")
345 localline
+= l
[0] + " "
346 localline
+= "localhost.localdomain localhost"
348 path
= self
.path("/etc/hosts")
351 f
.write("127.0.0.1\t\t%s\n" % localline
)
352 f
.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
355 def write_resolv(self
, nodns
, nameservers
):
356 if nodns
or not nameservers
:
359 path
= self
.path("/etc/resolv.conf")
363 for ns
in (nameservers
):
365 f
.write("nameserver %s\n" % ns
)
369 def apply(self
, ksnet
):
370 fs
.makedirs(self
.path("/etc/sysconfig/network-scripts"))
378 for network
in ksnet
.network
:
379 if not network
.device
:
380 raise errors
.KickstartError("No --device specified with "
381 "network kickstart command")
383 if (network
.onboot
and network
.bootProto
.lower() != "dhcp" and
384 not (network
.ip
and network
.netmask
)):
385 raise errors
.KickstartError("No IP address and/or netmask "
386 "specified with static "
387 "configuration for '%s'" %
390 self
.write_ifcfg(network
)
391 self
.write_wepkey(network
)
399 hostname
= network
.hostname
401 gateway
= network
.gateway
403 if network
.nameserver
:
404 nameservers
= network
.nameserver
.split(",")
406 self
.write_sysconfig(useipv6
, hostname
, gateway
)
407 self
.write_hosts(hostname
)
408 self
.write_resolv(nodns
, nameservers
)
410 class SelinuxConfig(KickstartConfig
):
411 """A class to apply a kickstart selinux configuration to a system."""
412 def relabel(self
, ksselinux
):
413 # touch some files which get unhappy if they're not labeled correctly
414 for fn
in ("/etc/resolv.conf",):
420 if ksselinux
.selinux
== ksconstants
.SELINUX_DISABLED
:
423 if not os
.path
.exists(self
.path("/sbin/setfiles")):
426 self
.call(["/sbin/setfiles", "-p", "-e", "/proc", "-e", "/sys", "-e", "/dev", selinux
.selinux_file_context_path(), "/"])
428 def apply(self
, ksselinux
):
429 if os
.path
.exists(self
.path("/usr/sbin/lokkit")):
430 args
= ["/usr/sbin/lokkit", "--quiet", "--nostart"]
432 if ksselinux
.selinux
== ksconstants
.SELINUX_ENFORCING
:
433 args
.append("--selinux=enforcing")
434 if ksselinux
.selinux
== ksconstants
.SELINUX_PERMISSIVE
:
435 args
.append("--selinux=permissive")
436 if ksselinux
.selinux
== ksconstants
.SELINUX_DISABLED
:
437 args
.append("--selinux=disabled")
441 self
.relabel(ksselinux
)
443 def get_image_size(ks
, default
= None):
445 for p
in ks
.handler
.partition
.partitions
:
446 if p
.mountpoint
== "/" and p
.size
:
449 return int(__size
) * 1024L * 1024L
453 def get_image_fstype(ks
, default
= None):
454 for p
in ks
.handler
.partition
.partitions
:
455 if p
.mountpoint
== "/" and p
.fstype
:
461 if not hasattr(ks
.handler
.device
, "deviceList"):
462 devices
.append(ks
.handler
.device
)
464 devices
.extend(ks
.handler
.device
.deviceList
)
467 for device
in devices
:
468 if not device
.moduleName
:
470 modules
.extend(device
.moduleName
.split(":"))
474 def get_timeout(ks
, default
= None):
475 if not hasattr(ks
.handler
.bootloader
, "timeout"):
477 if ks
.handler
.bootloader
.timeout
is None:
479 return int(ks
.handler
.bootloader
.timeout
)
481 def get_kernel_args(ks
, default
= "ro rd.live.image quiet"):
482 if not hasattr(ks
.handler
.bootloader
, "appendLine"):
484 if ks
.handler
.bootloader
.appendLine
is None:
486 return "%s %s" %(default
, ks
.handler
.bootloader
.appendLine
)
488 def get_default_kernel(ks
, default
= None):
489 if not hasattr(ks
.handler
.bootloader
, "default"):
491 if not ks
.handler
.bootloader
.default
:
493 return ks
.handler
.bootloader
.default
495 def get_repos(ks
, repo_urls
= {}):
497 for repo
in ks
.handler
.repo
.repoList
:
499 if hasattr(repo
, "includepkgs"):
500 inc
.extend(repo
.includepkgs
)
503 if hasattr(repo
, "excludepkgs"):
504 exc
.extend(repo
.excludepkgs
)
506 baseurl
= repo
.baseurl
507 mirrorlist
= repo
.mirrorlist
510 if repo
.name
in repo_urls
:
511 baseurl
= repo_urls
[repo
.name
]
514 if repos
.has_key(repo
.name
):
515 logging
.warn("Overriding already specified repo %s" %(repo
.name
,))
516 repos
[repo
.name
] = (repo
.name
, baseurl
, mirrorlist
, proxy
, inc
, exc
, repo
.cost
)
518 return repos
.values()
520 def convert_method_to_repo(ks
):
522 ks
.handler
.repo
.methodToRepo()
523 except (AttributeError, kserrors
.KickstartError
):
526 def get_packages(ks
, required
= []):
527 return ks
.handler
.packages
.packageList
+ required
529 def get_groups(ks
, required
= []):
530 return ks
.handler
.packages
.groupList
+ required
532 def get_excluded(ks
, required
= []):
533 return ks
.handler
.packages
.excludedList
+ required
535 def get_partitions(ks
, required
= []):
536 return ks
.handler
.partition
.partitions
538 def ignore_missing(ks
):
539 return ks
.handler
.packages
.handleMissing
== ksconstants
.KS_MISSING_IGNORE
541 def exclude_docs(ks
):
542 return ks
.handler
.packages
.excludeDocs
545 if hasattr(ks
.handler
.packages
, "instLange"):
546 return ks
.handler
.packages
.instLange
547 elif hasattr(ks
.handler
.packages
, "instLangs"):
548 return ks
.handler
.packages
.instLangs
551 def get_pre_scripts(ks
):
553 for s
in ks
.handler
.scripts
:
554 if s
.type != ksparser
.KS_SCRIPT_PRE
:
559 def get_post_scripts(ks
):
561 for s
in ks
.handler
.scripts
:
562 if s
.type != ksparser
.KS_SCRIPT_POST
:
567 def selinux_enabled(ks
):
568 return ks
.handler
.selinux
.selinux
== ksconstants
.SELINUX_ENFORCING