lang: core: embedded: provisioner: Support exec handoff

Could be used for any tool, but mgmt is an obvious possibility.

I should check this code more, but it's roughly right and I'm sure it
will get refactored more when I build opt-in provisioning and so on.
This commit is contained in:
James Shubin
2025-06-08 21:16:54 -04:00
parent 582cea31b0
commit 777ea6115b
3 changed files with 79 additions and 36 deletions

View File

@@ -576,6 +576,7 @@ class base:host($name, $config) {
$provision_default = $config->provision || false # false is safest!
$handoff_type = $config->handoff || ""
$handoff_exec = $config->handoff_exec || ""
$handoff_code = $config->handoff_code || ""
$handoff_module_path = $config->handoff_module_path || ""
panic($handoff_code != "" and not golang_strings.has_prefix($handoff_code, "/"))
@@ -756,28 +757,35 @@ class base:host($name, $config) {
panic($handoff_type != "" and len($handoff_packages) == 0)
#$handoff_packages_string = golang_strings.join($handoff_packages, " ")
$sshkey_flag = "sshkey"
$sshkey_type = "ed25519" # TODO: support other options
$sshkey_path = "/root/.ssh/id_${sshkey_type}"
$setup_sshkey = "mkdir -p '/root/.ssh/' -m 700 && ssh-keygen -N '' -t '${sshkey_type}' -f '${sshkey_path}'" # non-interactive
# Setting up the known_hosts file isn't needed since that can be done on
# the first run of mgmt, and the initial key can come in from --ssh-url.
$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 {
$handoff_cpcode = if $handoff_type == "code" {
# Download a tar ball of our code.
# TODO: Alternate mechanisms of getting the code are possible.
if $handoff_code != "" {
if $mac != "" {
"/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 {
""
}
$handoff_service = if $handoff_type == "code" { # TODO: maybe add it in other scenarios
# Setup the mgmt service, which starts on firstboot.
"${handoff_binary_path} setup svc --binary-path='${handoff_binary_path}' --install --enable"
} else {
""
}
$handoff_firstboot = if $handoff_type == "" {
""
@@ -785,24 +793,28 @@ class base:host($name, $config) {
# 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 == "" {
""
$handoff_firstboot_exec = if $handoff_type == "exec" and $handoff_exec != "" {
# Add a script that will run by our firstboot service on boot.
# This usually just runs mgmt over the network to etcd.
"echo '#!/usr/bin/env bash' > ${firstboot_scripts_dir}mgmt-exec.sh && echo 'ulimit -n 16384' >> ${firstboot_scripts_dir}mgmt-exec.sh && echo '${handoff_exec}' >> ${firstboot_scripts_dir}mgmt-exec.sh && chmod u+x ${firstboot_scripts_dir}mgmt-exec.sh"
} else {
""
}
$handoff_firstboot_code = if $handoff_type == "code" {
# 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 '#!/usr/bin/env 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"
"echo '#!/usr/bin/env bash' > ${firstboot_scripts_dir}mgmt-code.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-code.sh && chmod u+x ${firstboot_scripts_dir}mgmt-code.sh"
} else {
""
}
# TODO: Do we want to signal an http:server: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}'"
}
$provisioning_done = "/usr/bin/curl --data-urlencode 'done=true' --data-urlencode 'password=sha1TODO' --data-urlencode 'sshtype=ssh-${sshkey_type}' --data-urlencode \"${sshkey_flag}=\$(cut -d ' ' -f 2 '${sshkey_path}.pub')\" --data-urlencode 'sshcomment=root@${handoff_hostname}' -o - 'http://${router_ip}:${http_port_str}/action/done/mac=${provision_key}'"
$http_kickstart_template = struct{
comment => "hello!",
@@ -836,11 +848,13 @@ class base:host($name, $config) {
#],
hostname => $handoff_hostname,
post => [
$setup_sshkey, # setup the ssh key
$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
$handoff_firstboot_exec, # install a firstboot script to exec
$handoff_firstboot_code, # install a firstboot script to deploy
$provisioning_done, # send a done signal back here
],
}
@@ -871,20 +885,40 @@ class base:host($name, $config) {
##$str_true = convert.format_bool(true)
##$str_false = convert.format_bool(false)
#http:server: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)
http:server:flag "${name}-done" {
key => "done",
path => "/action/done/mac=${provision_key}",
#mapped => {$str_true => $str_true, $str_false => $str_false,},
}
http:server:flag "${name}-sshkey" {
key => "${sshkey_flag}",
path => "/action/done/mac=${provision_key}",
}
# TODO: rename the names of kv and value here?
kv "${name}-done" {
key => $provision_key,
}
value "${name}-done" {
#any => true, # bool
}
kv "${name}-sshkey" {
key => $provision_key,
}
value "${name}-sshkey" {
#any => "",
}
Http:Server:Flag["${name}-done"].value -> Kv["${name}-done"].value
Http:Server:Flag["${name}-done"].value -> Value["${name}-done"].any
Http:Server:Flag["${name}-sshkey"].value -> Kv["${name}-sshkey"].value
Http:Server:Flag["${name}-sshkey"].value -> Value["${name}-sshkey"].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
$st_done = value.get_str("${name}-done")
$provisioned = $st_done->ready and $st_done->value == "true" # export this value to parent scope
$st_sshkey = value.get_str("${name}-sshkey")
$sshkey = $st_sshkey->value # export this value to parent scope
}

View File

@@ -167,6 +167,10 @@ type localArgs struct {
// other or the base installation packages.
Packages []string `arg:"--packages,separate" help:"list of additional distro packages to install" func:"cli_packages"`
// HandoffExec specifies that we want to handoff to this machine by
// running a single exec on firstboot. Usually an `mgmt run` command.
HandoffExec string `arg:"--handoff-exec" help:"exec command to run on firstboot" func:"cli_handoff_exec"` // eg: mgmt run ...
// HandoffCode specifies that we want to handoff to this machine with a
// static code deploy bolus. This is useful for isolated, one-time runs.
HandoffCode string `arg:"--handoff-code" help:"code dir to handoff to host" func:"cli_handoff_code"` // eg: /etc/mgmt/

View File

@@ -59,10 +59,14 @@ $distro = provisioner.cli_distro()
$version = provisioner.cli_version()
$arch = provisioner.cli_arch()
$uid = "${distro}${version}-${arch}" # eg: fedora39-x86_64
$handoff = if provisioner.cli_handoff_code() == "" { # TODO: check other types
""
$handoff = if provisioner.cli_handoff_code() != "" { # TODO: check other types
"code"
} else {
"code" # some non-empty word
if provisioner.cli_handoff_exec() != "" { # TODO: check other types
"exec"
} else {
""
}
}
include base.host("host0", struct{ # TODO: do we need a usable name anywhere?
#repo => $repo.uid, # type unification performance is very slow here
@@ -76,6 +80,7 @@ include base.host("host0", struct{ # TODO: do we need a usable name anywhere?
packages => provisioner.cli_packages(),
#provision => true, # default if unspecified
handoff => $handoff, # alternatively some code word or querystring
handoff_exec => provisioner.cli_handoff_exec(),
#handoff_code => "/etc/mgmt/", # one way to do it
handoff_code => provisioner.cli_handoff_code(),
handoff_module_path => provisioner.cli_handoff_module_path(),
@@ -85,6 +90,6 @@ include base.host("host0", struct{ # TODO: do we need a usable name anywhere?
#if $host0.provisioned {
# print "provisioned" {
# msg => fmt.printf("%s is provisioned!", $host0.name),
# msg => fmt.printf("%s has ssh key: %s", $host0.name, $host0.sshkey),
# }
#}