Files
mgmt/lang/core/embedded/provisioner/main.mcl
James Shubin 483cc22c32 lang: core: embedded: provisioner: Add IPXE support
This lets you boot from ipxe. You can run the ipxe shell from their
stock image or the netboot.xyz one. For the latter, press "m", then type
"dhcp" (machine is now pingable!) then type "route" to check the ip.

To boot type:

chain http://192.168.42.1:4280/menu.ipxe

and you're off!

Thanks to frebib for finding the workaround to the VFS bug. The answer
is you need to run the imgfree command to unblock the initrd.
2025-01-18 01:07:19 -05:00

891 lines
29 KiB
Plaintext

# Mgmt
# Copyright (C) 2013-2024+ James Shubin and the project contributors
# Written by James Shubin <james@shubin.ca> and the project contributors
#
# 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 3 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, see <https://www.gnu.org/licenses/>.
#
# Additional permission under GNU GPL version 3 section 7
#
# If you modify this program, or any covered work, by linking or combining it
# with embedded mcl code and modules (and that the embedded mcl code and
# modules which link with this program, contain a copy of their source code in
# the authoritative form) containing parts covered by the terms of any other
# license, the licensors of this program grant you additional permission to
# convey the resulting work. Furthermore, the licensors of this program grant
# the original author, James Shubin, additional permission to update this
# additional permission if he deems it necessary to achieve the goals of this
# additional permission.
# Run `sudo setcap CAP_NET_BIND_SERVICE=+eip mgmt` first to avoid running as root.
# based on: https://docs.fedoraproject.org/en-US/fedora/f36/install-guide/advanced/Network_based_Installations/
import "convert"
import "deploy"
import "fmt"
import "golang"
import "golang/path/filepath"
import "golang/strings"
import "list"
import "local"
import "net"
import "os"
import "value"
import "world"
$http_suffix = "http/"
$tftp_suffix = "tftp/"
$uefi_suffix = "uefi/"
$kickstart_suffix = "kickstart/"
# The base class is the core provisioner which can also spawn child classes.
class base($config) {
#
# variables
#
$interface = $config->interface || "eth0" # XXX: what if no interface exists?
#$interface = _struct_lookup_optional($config, "interface", "eth0")
$http_port = $config->http_port || 4280 # using :4280 avoids needing root and isn't in /etc/services
$http_port_str = fmt.printf("%d", $http_port)
$network = $config->network || "192.168.42.0/24"
$router = $config->router || "192.168.42.1/24"
$router_ip = net.cidr_to_ip($router) # removes cidr suffix
$dns = $config->dns || ["8.8.8.8", "1.1.1.1",] # maybe google/cloudflare will sponsor!
$prefix = $config->prefix || ""
panic($prefix == "") # panic if prefix is empty
panic(not strings.has_suffix($prefix, "/"))
file "${prefix}" { # dir
state => $const.res.file.state.exists,
}
$tftp_prefix = "${prefix}${tftp_suffix}"
$http_prefix = "${prefix}${http_suffix}"
$uefi_prefix = "${prefix}${uefi_suffix}"
$firewalld = $config->firewalld || true
# eg: equivalent of: https://download.fedoraproject.org/pub/fedora/linux/
$inst_repo_base = "http://${router_ip}:${http_port_str}/fedora/" # private lan online, no https!
$server_base = "http://${router_ip}:${http_port_str}/" # private lan online, no https!
$syslinux_root = "/usr/share/syslinux/"
$nbp_bios = "tftp://${router_ip}/pxelinux.0" # for bios clients
$nbp_uefi = "tftp://${router_ip}/uefi/shim.efi" # for uefi clients
#
# network
#
net "${interface}" {
state => $const.res.net.state.up,
addrs => [$router,], # has cidr suffix
#gateway => "192.168.42.1", # TODO: get upstream public gateway with new function
ip_forward => true, # XXX: does this work?
Meta:reverse => true, # XXX: ^C doesn't reverse atm. Fix that!
Before => Dhcp:Server[":67"], # TODO: add autoedges
}
#
# packages
#
# TODO: do we need "syslinux-nonlinux" ?
$pkgs_bios = ["syslinux",]
$pkgs_uefi = ["shim-x64", "grub2-efi-x64",]
pkg $pkgs_bios {
state => "installed",
}
#pkg $pkgs_uefi {
# state => "installed",
#}
# TODO: not in F39+ it seems
#$pkgs_kickstart = ["fedora-kickstarts", "spin-kickstarts",]
#pkg $pkgs_kickstart {
# state => "installed",
#}
#
# firewalld
#
if $firewalld {
firewalld "provisioner" { # name is irrelevant
services => [
"tftp",
"dhcp",
],
ports => ["${http_port_str}/tcp",],
state => $const.res.firewalld.state.exists,
}
}
file "${tftp_prefix}" { # dir
state => $const.res.file.state.exists,
}
file "${uefi_prefix}" { # dir
state => $const.res.file.state.exists,
}
#
# tftp
#
tftp:server ":69" {
timeout => 60, # increase the timeout
#root => $root, # we're running in memory without needing a root!
#debug => true, # XXX: print out a `tree` representation in tmp prefix for the user
Depend => Pkg[$pkgs_bios], # hosted by tftp
#Depend => Pkg[$pkgs_uefi],
}
#
# bios bootloader images
#
# XXX: should this also be part of repo too?
class tftp_root_file($f) {
#tftp:file "${f}" { # without root slash
tftp:file "/${f}" { # with root slash
path => $syslinux_root + $f, # TODO: add autoedges
Depend => Pkg[$pkgs_bios],
}
}
include tftp_root_file("pxelinux.0")
include tftp_root_file("vesamenu.c32")
include tftp_root_file("ldlinux.c32")
include tftp_root_file("libcom32.c32")
include tftp_root_file("libutil.c32")
#
# dhcp
#
dhcp:server ":67" {
interface => $interface, # required for now
leasetime => "10m", # seems some clients refresh half way at 5m
dns => $dns, # pick your own better ones!
routers => [$router_ip,],
serverid => $router_ip, # XXX: test automatic mode
#Depend => Net[$interface], # TODO: add autoedges
}
#
# http
#
file "${http_prefix}" { # dir
state => $const.res.file.state.exists,
}
http:server ":${http_port_str}" {
#address => ":${http_port_str}", # you can override the name like this
#timeout => 60, # add a timeout (seconds)
}
$kickstart_http_prefix = "${http_prefix}${kickstart_suffix}"
file "${kickstart_http_prefix}" {
state => $const.res.file.state.exists,
#source => "", # this default means empty directory
recurse => true,
purge => true, # remove unmanaged files in here
}
$vardir = local.vardir("provisioner/")
$binary_path = deploy.binary_path()
http:file "/mgmt/binary" { # TODO: support different architectures
path => $binary_path, # TODO: As long as binary doesn't contain private data!
Before => Print["ready"],
}
# XXX: don't put anything private in your code this isn't authenticated!
$abs_tar = "${vardir}deploys/deploy.tar"
$abs_gz = "${abs_tar}.gz"
file "${vardir}deploys/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,g=rx,o=", # dir
}
# Tag this so that the folder purge doesn't remove it. (XXX: bug!)
file "${abs_tar}" {
owner => "root",
group => "root",
mode => "u=rw,g=rw,o=", # file
Meta:retry => -1, # changing the mode on this file can be racy
}
file "${abs_gz}" {
owner => "root",
group => "root",
mode => "u=rw,g=rw,o=", # file
Meta:retry => -1, # changing the mode on this file can be racy
}
deploy:tar "${abs_tar}" {
Before => Gzip["${abs_gz}"],
Depend => File["${vardir}deploys/"], # make the dir first!
}
gzip "${abs_gz}" {
input => "${abs_tar}",
}
http:file "/mgmt/deploy.tar.gz" {
path => "${abs_gz}",
}
print "ready" {
msg => "ready to provision!",
Meta:autogroup => false,
Depend => Tftp:Server[":69"],
Depend => Dhcp:Server[":67"],
Depend => Http:Server[":${http_port_str}"],
}
# we're effectively returning a new class definition...
}
# The repo class which is a child of base, defines the distro repo to use.
class base:repo($config) {
$distro = $config->distro || "fedora"
$version = $config->version || "39" # not an int!
$arch = $config->arch || "x86_64"
#$flavour = $config->flavour || "" # is flavour needed for repo sync?
# export this value to parent scope for base:host to consume
$uid = "${distro}${version}-${arch}" # eg: fedora39-x86_64
# TODO: We need a way to pick a good default because if a lot of people
# use this, then most won't change it to one in their country...
$mirror = $config->mirror || "" # TODO: how do we pick a default?
$rsync = $config->rsync || ""
$is_fedora = $distro == "fedora"
$distroarch_tftp_prefix = "${tftp_prefix}${uid}/"
$distroarch_uefi_prefix = "${uefi_prefix}${uid}/"
$distroarch_http_prefix = "${http_prefix}${uid}/"
$distroarch_release_http_prefix = "${distroarch_http_prefix}release/"
$distroarch_updates_http_prefix = "${distroarch_http_prefix}updates/"
file "${distroarch_tftp_prefix}" { # dir
state => $const.res.file.state.exists,
#Meta:quiet => true, # TODO
}
file "${distroarch_uefi_prefix}" { # dir
state => $const.res.file.state.exists,
}
file "${distroarch_http_prefix}" { # root http dir
state => $const.res.file.state.exists,
}
file "${distroarch_release_http_prefix}" {
state => $const.res.file.state.exists,
}
file "${distroarch_updates_http_prefix}" {
state => $const.res.file.state.exists,
}
#
# uefi bootloader images
#
$uefi_download_dir = "${distroarch_uefi_prefix}download/"
$uefi_extract_dir = "${distroarch_uefi_prefix}extract/"
file "${uefi_extract_dir}" { # mkdir
state => $const.res.file.state.exists,
Depend => Exec["uefi-download-${uid}"],
Before => Exec["uefi-extract-${uid}"],
}
# Download the shim and grub2-efi packages. If your server is a BIOS
# system, you must download the packages to a temporary install root.
# Installing them directly on a BIOS machine will attempt to configure
# the system for UEFI booting and cause problems.
$pkgs_uefi_string = strings.join($pkgs_uefi, " ")
$repoidname = "local"
# eg: https://mirror.csclub.uwaterloo.ca/fedora/linux/releases/39/Everything/x86_64/os/
$repo_url = "http://${router_ip}:${http_port_str}/fedora/releases/${version}/Everything/${arch}/os/"
exec "uefi-download-${uid}" {
# no inner quotes because it's not bash handling this!
# the dnf download command makes the download destination dir
# With DNF5 (provisioning OS Fedora 41+) it's --destdir.
# Previously it's --downloaddir and no idea why the API break.
cmd => "/usr/bin/dnf download ${pkgs_uefi_string} --assumeyes --disablerepo=* --repofrompath ${repoidname},${repo_url} --destdir=${uefi_download_dir} --releasever ${version}",
# TODO: add an optional expiry mtime check that deletes these old files with an || rm * && false
ifcmd => "! test -s '${uefi_download_dir}shim-x64'*",
ifshell => "/usr/bin/bash",
Depend => Http:Server[":${http_port_str}"],
}
exec "uefi-extract-${uid}" {
# we use rpm2archive instead of cpio since the latter is deprecated for big files
# we do this in a loop for all the rpm files
cmd => "for i in ${uefi_download_dir}*.rpm; do /usr/bin/rpm2archive \$i | /usr/bin/tar -xvz --directory ${uefi_extract_dir} --exclude ./etc; done",
shell => "/usr/bin/bash",
# TODO: add an optional expiry mtime check that deletes these old files with an || rm * && false
creates => $uefi_shim,
Depend => Exec["uefi-download-${uid}"],
Before => Tftp:Server[":69"],
}
$uefi_root = "${uefi_extract_dir}/boot/efi/EFI/fedora/"
$uefi_shim = "${uefi_root}shim.efi"
$uefi_shimx64 = "${uefi_root}shimx64.efi"
tftp:file "/uefi/shim.efi" { # needs leading slash
path => $uefi_shim, # TODO: add autoedges
Depend => Exec["uefi-extract-${uid}"],
}
tftp:file "/uefi/shimx64.efi" { # needs leading slash
path => $uefi_shimx64, # TODO: add autoedges
Depend => Exec["uefi-extract-${uid}"],
}
tftp:file "/uefi/grubx64.efi" { # sometimes used?
path => "${uefi_root}grubx64.efi", # TODO: add autoedges
Depend => Exec["uefi-extract-${uid}"],
}
tftp:file "grubx64.efi" { # no leading slash
path => "${uefi_root}grubx64.efi", # TODO: add autoedges
Depend => Exec["uefi-extract-${uid}"],
}
# XXX: replace with a download resource
# XXX: allow send->recv to pass this file to tftp:file->data to keep it in mem!
$vmlinuz_file = "${distroarch_tftp_prefix}vmlinuz"
exec "vmlinuz-${uid}" {
cmd => "/usr/bin/wget",
args => [
"--no-verbose",
"${repo_url}images/pxeboot/vmlinuz",
"-O",
$vmlinuz_file,
],
creates => $vmlinuz_file,
Depend => File[$distroarch_tftp_prefix],
Depend => Http:Server[":${http_port_str}"],
Before => Print["ready"],
}
tftp:file "/${uid}/vmlinuz" {
path => $vmlinuz_file, # TODO: add autoedges
#Depend => Pkg[$pkgs],
}
http:file "/${uid}/vmlinuz" { # when using ipxe
path => $vmlinuz_file, # TODO: add autoedges
#Depend => Pkg[$pkgs],
}
$initrd_file = "${distroarch_tftp_prefix}initrd.img"
exec "initrd-${uid}" {
cmd => "/usr/bin/wget",
args => [
"--no-verbose",
"${repo_url}images/pxeboot/initrd.img",
"-O",
$initrd_file,
],
creates => $initrd_file,
Depend => File[$distroarch_tftp_prefix],
Depend => Http:Server[":${http_port_str}"],
Before => Print["ready"],
}
tftp:file "/${uid}/initrd.img" {
path => $initrd_file, # TODO: add autoedges
#Depend => Pkg[$pkgs],
}
http:file "/${uid}/initrd.img" { # when using ipxe
path => $initrd_file, # TODO: add autoedges
#Depend => Pkg[$pkgs],
}
# this file resource serves the entire rsync directory over http
if $mirror == "" { # and $rsync != ""
http:file "/fedora/releases/${version}/Everything/${arch}/os/" {
path => $distroarch_release_http_prefix,
}
http:file "/fedora/updates/${version}/Everything/${arch}/" {
path => $distroarch_updates_http_prefix,
}
} else {
# same as the above http:file path would have been
http:proxy "/fedora/releases/${version}/Everything/${arch}/os/" {
sub => "/fedora/", # we remove this from the name!
head => $mirror,
cache => $distroarch_release_http_prefix, # $prefix/http/fedora39-x86_64/release/
}
# XXX: if we had both of these in the same http_prefix, we could overlap them with an rsync :/ hmm...
http:proxy "/fedora/updates/${version}/Everything/${arch}/" { # no os/ dir at the end
sub => "/fedora/", # we remove this from the name!
head => $mirror,
cache => $distroarch_updates_http_prefix, # $prefix/http/fedora39-x86_64/updates/
}
}
# .repo files
#$fedora_repo_template = struct{
# name => "fedora",
# distro => $distro,
# version => $version, # 39 for fedora 39
# arch => $arch, # could also be aarch64
# #flavour => "Everything", # The install repo uses "Everything" even for "Workstation" or "Server"
# extra => "releases",
#
# baseurl => "http://${router_ip}:${http_port_str}/fedora/releases/${version}/Everything/${arch}/os/",
#}
#$updates_repo_template = struct{
# name => "updates",
# distro => $distro,
# version => $version, # 39 for fedora 39
# arch => $arch, # could also be aarch64
# #flavour => "Everything", # The install repo uses "Everything" even for "Workstation" or "Server"
# extra => "updates",
#
# baseurl => "http://${router_ip}:${http_port_str}/fedora/updates/${version}/Everything/${arch}/",
#}
#http:file "/fedora/${uid}/fedora.repo" {
# data => golang.template(deploy.readfile("/files/repo.tmpl"), $fedora_repo_template),
#}
#http:file "/fedora/${uid}/updates.repo" {
# data => golang.template(deploy.readfile("/files/repo.tmpl"), $updates_repo_template),
#}
#
# rsync
#
#$source_pattern = if $is_fedora {
# "${rsync}releases/${version}/Everything/${arch}/os/" # source
#} else {
# "" # XXX: not implemented
#}
#panic($source_pattern == "") # distro is not specified
# TODO: combine release and updates?
#$is_safe = $distroarch_release_http_prefix != "" and $distroarch_release_http_prefix != "/"
#if $rsync != "" and $source_pattern != "" and $is_safe {
#
# $mtime_file = "${http_prefix}rsync-${uid}.mtime"
# $delta = convert.int_to_str(60 * 60 * 24 * 7) # ~1 week in seconds: 604800
# exec "rsync-${uid}" {
# cmd => "/usr/bin/rsync",
# args => [
# "-avSH",
# "--progress",
# # This flavour must always be Everything to work.
# # The Workstation flavour doesn't have an os/ dir.
# $source_pattern, # source
# $distroarch_release_http_prefix, # dest
# ],
#
# # run this when cmd completes successfully
# donecmd => "/usr/bin/date --utc > ${mtime_file}",
# doneshell => "/usr/bin/bash",
#
# # Run if the difference between the current date and the
# # saved date (both converted to sec) is greater than the
# # delta! (Or if the mtime file does not even exist yet.)
# ifcmd => "! /usr/bin/test -e ${mtime_file} || /usr/bin/test \$((`/usr/bin/date +%s` - `/usr/bin/stat -c %Y '${mtime_file}'`)) -gt ${delta}",
#
# ifshell => "/usr/bin/bash",
#
# Before => Http:Server[":${http_port_str}"],
# Before => File[$distroarch_release_http_prefix],
# }
#}
}
# The host class is used for each physical host we want to provision.
class base:host($name, $config) {
#print $name {
# msg => "host: ${name}",
#
# Meta:autogroup => false,
#}
$repouid = $config->repo || ""
$uidst = os.parse_distro_uid($repouid)
$distro = $uidst->distro
$version = $uidst->version # not an int!
$arch = $uidst->arch
panic($distro == "")
panic($version == "")
panic($arch == "")
$flavour = $config->flavour || ""
$mac = $config->mac || ""
#panic($mac == "") # provision anyone by default
$ip = $config->ip || "" # XXX: auto-generate it inside of the above network somehow (see below)
panic($ip == "")
#$ns = if $config->ip == "" {
# ""
#} else {
# "" + get_value("network") # XXX: implement some sort of lookup function
#}
#$ip = $config->ip || magic.pool($ns, [1,2,3,4], $name) # XXX: if $ns is "", then don't allocate. Otherwise get from list. Re-use based on $name hash.
$bios = $config->bios || false
$password = $config->password || "" # empty means disabled
panic(len($password) != 0 and len($password) != 106) # length of salted password
$part = $config->part || "" # partitioning scheme
$empty_list_str []str = [] # need an explicit type on empty list definition
$packages = $config->packages || $empty_list_str
# should we provision this host by default?
$provision_default = $config->provision || false # false is safest!
$handoff_type = $config->handoff || ""
$handoff_code = $config->handoff_code || ""
$handoff_module_path = $config->handoff_module_path || ""
panic($handoff_code != "" and not strings.has_prefix($handoff_code, "/"))
$handoff_hostname = $config->handoff_hostname || ""
# This is a giant driver://user:password@host:port/whatever URL...
$bmc_uri = $config->bmc_uri || ""
# unique host key which is usually a mac address unless it's a default
$hkey = if $mac == "" {
"default"
} else {
$mac
}
$provision_key = $hkey # XXX: what unique id should we use for the host? mac? name? hkey?
#$ret = world.getval($provision_key) # has it previously been provisioned?
#$val = if $ret->value == "" { # avoid an invalid string killing the parse_bool function
# convert.format_bool(false) # "false"
#} else {
# $ret->value
#}
#$provision = if not $ret->exists {
# $provision_default
#} else {
# not convert.parse_bool($val) # XXX: should an invalid string return false or error here?
#}
$provision = true
$nbp_path = if $bios {
"/pxelinux.0" # for bios clients
} else {
"/uefi/shim.efi" # for uefi clients
}
if $mac != "" {
dhcp:host "${name}" { # the hostname
mac => $mac,
ip => $ip, # cidr notation is required
nbp => $provision ?: if $bios { # XXX: do we want this from the base class?
$nbp_bios # from base class
} else {
$nbp_uefi # from base class
},
nbp_path => $provision ?: $nbp_path, # with leading slash
Depend => Tftp:Server[":69"],
}
} else {
# Handle ANY mac address since we don't have one specified!
# TODO: Our dhcp:range could send/recv a map from ip => mac address!
dhcp:range "${name}" {
network => "${network}", # eg: 192.168.42.0/24
skip => [$router,], # eg: 192.168.42.1/24
nbp => $provision ?: if $bios { # XXX: do we want this from the base class?
$nbp_bios # from base class
} else {
$nbp_uefi # from base class
},
nbp_path => $provision ?: $nbp_path, # with leading slash
Depend => Tftp:Server[":69"],
}
}
$menu_template = struct{
distro => $distro,
version => $version, # 39 for fedora 39
arch => $arch, # could also be aarch64
flavour => "Everything", # The install repo uses "Everything" even for "Workstation" or "Server"
ks => "http://${router_ip}:${http_port_str}/fedora/kickstart/${hkey}.ks", # usually $mac or `default`
inst_repo_base => $inst_repo_base,
server_base => $server_base,
}
#
# default menus
#
$safe_mac = if $mac == "" {
"00:00:00:00:00:00"
} else {
$mac
}
$old_mac = net.oldmacfmt($safe_mac)
# no idea why these need a 01- prefix
$bios_menu = if $mac == "" {
"/pxelinux.cfg/default"
} else {
# /pxelinux.cfg/01-00-11-22-33-44-55-66
"/pxelinux.cfg/01-${old_mac}"
}
$uefi_menu = if $mac == "" {
# XXX: add the front slash!?
#"pxelinux/uefi" # TODO: Did some machines use this?
"/uefi/grub.cfg"
} else {
# /uefi/grub.cfg-01-00-11-22-33-44-55-66
"/uefi/grub.cfg-01-${old_mac}"
}
$ipxe_menu = if $mac == "" {
"menu.ipxe"
} else {
"${old_mac}.ipxe"
}
if $bios {
tftp:file "${bios_menu}" { # for bios
data => golang.template(deploy.readfile("/files/bios-menu.tmpl"), $menu_template),
}
} else {
tftp:file "${uefi_menu}" { # for uefi
# XXX: linuxefi & initrdefi VS. kernel & append ?
data => golang.template(deploy.readfile("/files/uefi-menu.tmpl"), $menu_template),
#Depend => Pkg[$pkgs_uefi],
#Depend => Exec["uefi-extract"],
}
}
http:file "/${ipxe_menu}" { # for ipxe
data => golang.template(deploy.readfile("/files/ipxe-menu.tmpl"), $menu_template),
}
# If it's a dir we don't need a suffix, otherwise return the last chunk.
$handoff_code_chunk = if strings.has_suffix($prefix, "/") {
""
} else {
filepath.base($handoff_code)
}
if $handoff_code != "" { # it's a file path or dir!
$abs_tar = "${vardir}deploys/deploy-${provision_key}.tar"
$abs_gz = "${abs_tar}.gz"
# Tag this so that the folder purge doesn't remove it. (XXX: bug!)
file "${abs_tar}" {
owner => "root",
group => "root",
mode => "u=rw,g=rw,o=", # file
Meta:retry => -1, # changing the mode on this file can be racy
}
file "${abs_gz}" {
owner => "root",
group => "root",
mode => "u=rw,g=rw,o=", # file
Meta:retry => -1, # changing the mode on this file can be racy
}
tar "${abs_tar}" {
inputs => [
$handoff_code, # code comes in here!
strings.trim_suffix($handoff_module_path, "/"), # remove trailing slash to include the dir name
],
Before => Gzip["${abs_gz}"],
Depend => File["${vardir}deploys/"], # make the dir first!
}
gzip "${abs_gz}" {
input => "${abs_tar}",
}
http:file "/mgmt/deploy-${provision_key}.tar.gz" {
path => "${abs_gz}",
}
}
$handoff_binary_path = "/usr/local/bin/mgmt" # we install it here
$firstboot_scripts_dir = "/var/lib/mgmt-firstboot/" # TODO: /usr/lib/ instead?
$firstboot_done_dir = "/var/lib/mgmt-firstboot/done/"
$deploy_dir = "/root/mgmt-deploy/" # deploy code dir
$modules_chunk = filepath.base(strings.trim_suffix($handoff_module_path, "/")) # "modules" typically
$deploy_dir_modules = "${deploy_dir}${modules_chunk}/"
# TODO: we can customize these more precisely based on $handoff_type
$handoff_packages = deploy.bootstrap_packages($distro) # TODO: catch errors here with || []
panic($handoff_type != "" and len($handoff_packages) == 0)
#$handoff_packages_string = strings.join($handoff_packages, " ")
$handoff_binary = if $handoff_type == "" {
""
} else {
# Copy over the actual mgmt binary. This enables a lot below...
"/usr/bin/wget -O '${handoff_binary_path}' 'http://${router_ip}:${http_port_str}/mgmt/binary' && /usr/bin/chmod u+x '${handoff_binary_path}'"
}
$handoff_cpcode = if $handoff_type == "" {
""
} else {
# Download a tar ball of our code.
# TODO: Alternate mechanisms of getting the code are possible.
if $handoff_code != "" {
"/usr/bin/wget -O /root/mgmt-deploy.tar.gz 'http://${router_ip}:${http_port_str}/mgmt/deploy-${provision_key}.tar.gz' && /usr/bin/mkdir '${deploy_dir}' && /usr/bin/tar -xf /root/mgmt-deploy.tar.gz --directory '${deploy_dir}'"
} else {
"/usr/bin/wget -O /root/mgmt-deploy.tar.gz 'http://${router_ip}:${http_port_str}/mgmt/deploy.tar.gz' && /usr/bin/mkdir '${deploy_dir}' && /usr/bin/tar -xf /root/mgmt-deploy.tar.gz --directory '${deploy_dir}'"
}
}
$handoff_service = if $handoff_type == "" {
""
} else {
# Setup the mgmt service, which starts on firstboot.
"${handoff_binary_path} setup svc --binary-path='${handoff_binary_path}' --install --enable"
}
$handoff_firstboot = if $handoff_type == "" {
""
} else {
# Setup the firstboot service itself.
"${handoff_binary_path} setup firstboot --binary-path='${handoff_binary_path}' --mkdir --install --enable --scripts-dir='${firstboot_scripts_dir}' --done-dir='${firstboot_done_dir}'"
}
$handoff_deploy = if $handoff_type == "" {
""
} else {
# Add a script that will run by our firstboot service on boot.
# It seems that the deploy will hang until mgmt is started...
# NOTE: We need to add the $handoff_code arg the same way it was
# passed into the provisioner. It's just now in a deploy subdir.
# If it's a dir, then this becomes the empty strings.
# XXX: The deploy could instead happen over the network to etcd.
"echo '#!/bin/bash' > ${firstboot_scripts_dir}mgmt-deploy.sh && echo '${handoff_binary_path} deploy lang --seeds=http://127.0.0.1:2379 --no-git --module-path=${deploy_dir_modules} ${deploy_dir}${handoff_code_chunk}' >> ${firstboot_scripts_dir}mgmt-deploy.sh && chmod u+x ${firstboot_scripts_dir}mgmt-deploy.sh"
}
# TODO: Do we want to signal an http:flag if we're a "default" host?
$provisioning_done = if $provision_key == "default" {
""
} else {
"/usr/bin/wget --post-data 'done=true&password=sha1TODO' -O - 'http://${router_ip}:${http_port_str}/action/done/mac=${provision_key}'"
}
$http_kickstart_template = struct{
comment => "hello!",
lang => [
"en_CA.UTF-8",
"fr_CA.UTF-8",
"en_US.UTF-8",
],
password => $password, # salted
bios => $bios,
part => $part,
flavour => $flavour,
url => "http://${router_ip}:${http_port_str}/fedora/releases/${version}/Everything/${arch}/os/",
repos => {
#"fedora" => "http://${router_ip}:${http_port_str}/fedora/releases/${version}/Everything/${arch}/os/", # TODO: this vs url ?
"updates" => "http://${router_ip}:${http_port_str}/fedora/updates/${version}/Everything/${arch}/",
},
#repos => { # needs internet or blocks at storage https://bugzilla.redhat.com/show_bug.cgi?id=2269752
# "fedora" => "https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-\$releasever&arch=\$basearch",
# "updates" => "https://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f\$releasever&arch=\$basearch",
#},
# We need $handoff_packages installed in the _KICKSTART_ environ
# so that we can actually run mgmt to do our work for us below!
packages => list.concat($packages, $handoff_packages),
# XXX: is wget segfaulting in the kickstart environment? curl is OK.
#pre => [
# "/usr/bin/mkdir /etc/yum.repos.d/",
# "/usr/bin/wget 'http://${router_ip}:${http_port_str}/fedora/${repouid}/fedora.repo' -O /etc/yum.repos.d/fedora.repo",
# "/usr/bin/wget 'http://${router_ip}:${http_port_str}/fedora/${repouid}/updates.repo' -O /etc/yum.repos.d/updates.repo",
# "/usr/bin/dnf --enablerepo=fedora,updates install --assumeyes ${handoff_packages_string}",
#],
hostname => $handoff_hostname,
post => [
$handoff_binary, # copy over the binary
$handoff_cpcode, # copy over a bundle of code
$handoff_service, # install a service for mgmt
$handoff_firstboot, # install firstboot service
$handoff_deploy, # install a firstboot script to deploy
$provisioning_done, # send a done signal back here
],
}
$kickstart_file = "${kickstart_http_prefix}${hkey}.ks"
file "${kickstart_file}" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/kickstart.ks.tmpl"), $http_kickstart_template),
}
http:file "/fedora/kickstart/${hkey}.ks" { # usually $mac or `default`
#data => golang.template(deploy.readfile("/files/kickstart.ks.tmpl"), $http_kickstart_template),
path => $kickstart_file,
Before => Print["ready"],
}
if "${bmc_uri}" != "" {
bmc:power "${bmc_uri}" { # TODO: Name() API is not yet stable
#password => "hunter2",
insecure_password => true, # XXX: get from uri for now
state => "on",
Meta:poll => 60, # required until BMC's support real events!
}
}
##$str_true = convert.format_bool(true)
##$str_false = convert.format_bool(false)
#http:flag "${name}" {
# key => "done",
# path => "/action/done/mac=${provision_key}",
# #mapped => {$str_true => $str_true, $str_false => $str_false,},
#}
#kv "${name}" {
# key => $provision_key,
#}
#value "${provision_key}" {
# #any => true, # bool
#}
#Http:Flag["${name}"].value -> Kv["${name}"].value
#Http:Flag["${name}"].value -> Value["${provision_key}"].any
##$st_provisioned = value.get_bool($provision_key)
#$st_provisioned = value.get_str($provision_key)
#$provisioned = $st_provisioned->ready and $st_provisioned->value == "true" # export this value to parent scope
}