Skip to content

Instantly share code, notes, and snippets.

@MrLenin
Last active March 28, 2024 00:53
Show Gist options
  • Save MrLenin/240b0ffd793858030525c89cc5291937 to your computer and use it in GitHub Desktop.
Save MrLenin/240b0ffd793858030525c89cc5291937 to your computer and use it in GitHub Desktop.
Basic proxmox hookscript and cloud-init bootcmd to insert interface ip addresses into hosts file template
Can be useful for vm guests with dhcp assigned addresses, but can likely be extended to do almost
anything cloud-init can be used to do.
regenerate-cloud-init-data.pl is a hookscript that is attached to your vm template so that it
is present in the config for all VMs cloned from it. It is not likely suitable for environments
with multiple nodes as-is.
It is run prior to the starting of the VM and regenetates the vm-<id>-userconfig.yaml file from a
template containing the bootcmd and any other cloud-config data you wish to use. It also appends
configuration data to set the hostname (the VM name) and fqdn (configured in the script) of the VM.
The bootcmd could be made a bunch simpler if you don't care about readability or formatting of
the final hosts file, or even more complex if you do or require more selective naming abilities.
I do care a little about readability and formatting, but I'm lazy so I just chopped the file in 3.
I included the chopped up debian template for reference, other distros may or may not have their own
template file.
#!/usr/bin/perl
use strict;
use warnings;
use File::Copy;
use PVE::APIClient::LWP;
use PVE::AccessControl;
use PVE::INotify;
################################################################################
# change these
# filesystem path which contains the snippets directory
my $cloudinit = "/var/cloudinit/";
# the volume which contains the snippets directory
my $volumeid = "cloudinit";
# domain suffix used for fqdn
my $domainsuffix = ".fractalrealities.net";
################################################################################
print "GUEST HOOK: " . join(' ', @ARGV). "\n";
my $vmid = shift;
my $phase = shift;
my $tmpl = $cloudinit . "userconfig.yaml.tmpl";
my $genfile = $cloudinit . "snippets/vm-" . $vmid . "-userconfig.yaml";
################################################################################
sub get_local_cert_fingerprint {
my ($node) = @_;
my $cert_path = "/etc/pve/nodes/$node/pve-ssl.pem";
my $custom_cert_path = "/etc/pve/nodes/$node/pveproxy-ssl.pem";
$cert_path = $custom_cert_path if -f $custom_cert_path;
my $bio = Net::SSLeay::BIO_new_file($cert_path, 'r');
my $cert = Net::SSLeay::PEM_read_bio_X509($bio);
Net::SSLeay::BIO_free($bio);
my $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
die "got empty fingerprint" if !defined($fp) || ($fp eq '');
return $fp;
}
################################################################################
# get hostname for this node
my $hostname = PVE::INotify::read_file("hostname");
# obtain ticket and csrf token for the root user
my $ticket = PVE::AccessControl::assemble_ticket('root@pam');
my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token('root@pam');
# get cert fingerprint for this node
my $local_fingerprint = get_local_cert_fingerprint($hostname);
if ($phase eq 'pre-start') {
print "$vmid is starting, doing preparations.\n";
# open an API connection to proxmox using our ticket/token/fingerprint
my $conn = PVE::APIClient::LWP->new(
ticket => $ticket,
csrftoken => $csrftoken,
host => $hostname,
cached_fingerprints => {
$local_fingerprint => 1,
});
# read config data for vm
my $api_path = "/nodes/$hostname/qemu/${vmid}/config";
my $vm_config = $conn->get($api_path);
# overwrite the existing user-data with our template
copy($tmpl, $genfile) or die "Copy failed: $!";
# open the config and append our instance-specific data
open(my $fh, '>>', $genfile) or die "Could not open file '$genfile' $!";
say $fh "fqdn: " . lc($vm_config->{name}) . lc($domainsuffix);
close $fh;
print "$vmid preparations complete.\n";
}
exit(0);
#cloud-config
manage_etc_hosts: true
password: $5$TUPT4znQ$lOYy4APH6ejrPoXsftkCUoxmRL43YtUmu93iyx6aJ1A
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGHWf9A33GIRfj/37IPhYnFn7bhdbo9dVhfKhP702VmV ibutsu
chpasswd:
expire: False
users:
- default
package_upgrade: true
bootcmd:
- [ sh, -c, echo "Generating hosts file template..." ]
- |
#!/bin/sh
(sleep 5;
TMPL="/etc/cloud/templates/hosts.debian.tmpl"
TMP="/run/hoststmp"
LOCALIPS=$(hostname -I)
for ip in ${LOCALIPS:-"127.0.1.1"}
do
echo "Discovered $ip"
if [ "$ip" != "${ip#*[0-9].[0-9]}" ]; then
echo "Adding IPv4 address $ip"
echo "$ip {{fqdn}} {{hostname}}" >> "$TMP.ipv4"
continue
elif [ "$ip" != "${ip#*:[0-9a-fA-F]}" ]; then
echo "Adding IPv6 address $ip"
echo "$ip {{fqdn}} {{hostname}}" >> "$TMP.ipv6"
continue
else
echo "Unrecognized IP format '$ip'"
continue
fi
done
if [ -f "$TMP.ipv6" ]
then
{ cat "$TMPL.begin"; cat "$TMP.ipv4"; cat "$TMPL.mid"; cat "$TMP.ipv6"; cat "$TMPL.end"; } > $TMPL
rm -f "$TMP.ipv4" "$TMP.ipv6"
else
{ cat "$TMPL.begin"; cat "$TMP.ipv4"; cat "$TMPL.mid"; cat "$TMPL.end"; } > $TMPL
rm -f "$TMP.ipv4"
fi)
- [ sh, -c, echo "Done." ]
#/bin/sh
CLOUDINITPATH="/var/cloudinit/"
CLOUDINITVOL="cloudinit"
if [ -z "$1" ]
then
read -p "Enter the source VM ID: " VMIDSRC
else
VMIDSRC="$1"
fi
if [ -z "$2" ]
then
read -p "Enter the destination VM ID: " VMIDDEST
else
VMIDDEST="$2"
fi
if [ -z "$3" ]
then
read -p "Enter the destination VM name: " VMNAMEDEST
else
VMNAMEDEST="$3"
fi
CLONECMD=('qm' 'clone' "$VMIDSRC" "$VMIDDEST" '--name' "$VMNAMEDEST")
REGENCMD=("${CLOUDINITPATH}snippets/regenerate-cloud-init-data.pl" "$VMIDDEST" 'pre-start')
CICSTCMD=('qm' 'set' "$VMIDDEST" '--cicustom' "user=$CLOUDINITVOL:snippets/vm-$VMIDDEST-userconfig.yaml")
"${CLONECMD[@]}"
"${REGENCMD[@]}" > /dev/null
"${CICSTCMD[@]}"
#/bin/sh
CLOUDINITPATH="/var/cloudinit/"
if [ -z "$1" ]
then
read -p "Enter the VM ID: " VMID
else
VMID="$1"
fi
USERCFG="${CLOUDINITPATH}snippets/vm-$VMID-userconfig.yaml"
STATUSCMD=('qm' 'status' "$VMID")
STOPCMD=('qm' 'stop' "$VMID")
DESTROYCMD=('qm' 'destroy' "$VMID" '--purge' "${2:-'false'}")
STATUS=$("${STATUSCMD[@]}")
if [ "$STATUS" != "status: stopped" ]
then
"${STOPCMD[@]}"
fi
"${DESTROYCMD[@]}"
if [ -f "$USERCFG" ]
then
rm -f "$USERCFG"
fi
## template:jinja
# Your system has configured 'manage_etc_hosts' as True.
# As a result, if you wish for changes to this file to persist
# then you will need to either
# a.) make changes to the master file in /etc/cloud/templates/hosts.debian.tmpl
# b.) change or remove the value of 'manage_etc_hosts' in
# /etc/cloud/cloud.cfg or cloud-config from user-data
#
127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment