From 0c0cac140d98529ab7ae955ce01fb23978319ed5 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 30 Apr 2025 17:06:20 -0400 Subject: [PATCH] Add debian profile material Implement Debian 12 installation --- .../S25confluentinit | 214 +++++-- .../debian/profiles/default/initprofile.sh | 7 + .../debian/profiles/default/preseed.cfg | 27 + .../debian/profiles/default/profile.yaml | 3 + .../debian/profiles/default/scripts/confignet | 565 ++++++++++++++++++ .../default/scripts/firstboot.service | 11 + .../profiles/default/scripts/firstboot.sh | 22 + .../debian/profiles/default/scripts/functions | 209 +++++++ .../debian/profiles/default/scripts/post.sh | 67 +++ .../debian/profiles/default/scripts/pre.sh | 92 ++- .../profiles/default/scripts/prechroot.sh | 19 + .../debian/profiles/default/scripts/setupssh | 43 ++ .../profiles/default/scripts/pre.sh | 1 + 13 files changed, 1218 insertions(+), 62 deletions(-) create mode 100644 confluent_osdeploy/debian/profiles/default/initprofile.sh create mode 100644 confluent_osdeploy/debian/profiles/default/preseed.cfg create mode 100644 confluent_osdeploy/debian/profiles/default/profile.yaml create mode 100644 confluent_osdeploy/debian/profiles/default/scripts/confignet create mode 100644 confluent_osdeploy/debian/profiles/default/scripts/firstboot.service create mode 100755 confluent_osdeploy/debian/profiles/default/scripts/firstboot.sh create mode 100644 confluent_osdeploy/debian/profiles/default/scripts/functions create mode 100755 confluent_osdeploy/debian/profiles/default/scripts/post.sh mode change 100644 => 100755 confluent_osdeploy/debian/profiles/default/scripts/pre.sh create mode 100644 confluent_osdeploy/debian/profiles/default/scripts/prechroot.sh create mode 100644 confluent_osdeploy/debian/profiles/default/scripts/setupssh diff --git a/confluent_osdeploy/debian/initramfs/lib/debian-installer-startup.d/S25confluentinit b/confluent_osdeploy/debian/initramfs/lib/debian-installer-startup.d/S25confluentinit index 42f5ab97..227441c1 100644 --- a/confluent_osdeploy/debian/initramfs/lib/debian-installer-startup.d/S25confluentinit +++ b/confluent_osdeploy/debian/initramfs/lib/debian-installer-startup.d/S25confluentinit @@ -14,65 +14,127 @@ setdebopt() { echo d-i $1 $3 $2 >> /preseed.cfg } -dhuuid=$(reverse_uuid $(cat /sys/devices/virtual/dmi/id/product_uuid)) -dhcpid=$(mktemp) mkdir -p /etc/confluent -cp /tls/* /etc/ssl/certs/ -for nic in $(ip link | grep mtu|grep -v LOOPBACK|cut -d: -f 2|sed -e 's/ //'); do - ip link set $nic up +for i in /sys/class/net/*; do + ip link set $(basename $i) up done -for nic in $(ip link | grep mtu|grep -v LOOPBACK|grep LOWER_UP|cut -d: -f 2|sed -e 's/ //'); do - if udhcpc -i $nic -p $dhcpid -t 2 -T 2 -n -x 93:0007 -x 97:00$dhuuid -q; then - /opt/confluent/bin/copernicus > /etc/confluent/confluent.info - if grep ^MANAGER:.*\\. /etc/confluent/confluent.info ; then - break - fi +TRIES=5 +while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do + sleep 2 + TRIES=$((TRIES - 1)) +done +for i in /sys/class/net/*; do + ip link set $(basename $i) down + udevadm info $i | grep ID_NET_DRIVER=cdc_ether > /dev/null && continue + ip link set $(basename $i) up +done +cp -a /tls/* /etc/ssl/certs/ +mkdir -p /etc/confluent +if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then + tmnt=$(mktemp -d) + tcfg=$(mktemp) + mount /dev/disk/by-label/CNFLNT_IDNT $tmnt + cd $tmnt + deploysrvs=$(sed -n '/^deploy_servers:/,/^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :) + nodename=$(grep ^nodename: cnflnt.yml|cut -f 2 -d ' ') + echo NODENAME: $nodename > /etc/confluent/confluent.info + sed -n '/^net_cfgs:/,/^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/,/^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg + autoconfigmethod=$(grep ^ipv4_method: $tcfg) + autoconfigmethod=${autoconfigmethod#ipv4_method: } + if [ "$autoconfigmethod" = "static" ]; then + setdebopt netcfg/disable_dhcp true boolean + v4addr=$(grep ^ipv4_address: $tcfg|cut -d: -f 2|sed -e 's/ //') + v4gw=$(grep ^ipv4_gateway: $tcfg|cut -d: -f 2| sed -e 's/ //') + if [ "$v4gw" = "null" ]; then + v4gw="" + fi + v4nm=$(grep ^ipv4_netmask: $tcfg|cut -d: -f 2|sed -e 's/ //') + setdebopt netcfg/get_netmask $v4nm string + setdebopt netcfg/get_ipaddress ${v4addr%/*} string + setdebopt netcfg/confirm_static true boolean + if [ ! -z "$v4gw" ]; then + setdebopt netcfg/get_gateway $v4gw string + fi + NIC="" + while [ -z "$NIC" ]; do + for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do + ip addr add dev $NICGUESS $v4addr + if [ ! -z "$v4gw" ]; then + ip route add default via $v4gw + fi + for dsrv in $deploysrvs; do + if wget https://$dsrv/confluent-public/ --tries=1 --timeout=1 -O /dev/null > /dev/null 2>&1; then + deploysrvs=$dsrv + NIC=$NICGUESS + setdebopt netcfg/choose_interface $NIC select + break + fi + done + if [ -z "$NIC" ]; then + ip -4 a flush dev $NICGUESS + else + break + fi + done + done + #TODO: nameservers + elif [ "$v4cfgmeth" = "dhcp" ]; then + setdebopt netcfg/disable_dhcp false boolean + setdebopt netcfg/confirm_static false boolean + for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do + udhcpc $NICGUESS + done + for dsrv in $deploysrvs; do + if wget https://$dsrv/confluent-public/ --tries=1 --timeout=1 -O /dev/null > /dev/null 2>&1; then + deploysrvs=$dsrv + fi + done fi - ip -4 flush dev $nic -done -mgr=$(grep ^MANAGER:.*\\. /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //') -nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //') -/opt/confluent/bin/clortho $nodename $mgr > /etc/confluent/confluent.apikey + mgr=$deploysrvs + ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac + hmackeyfile=/tmp/cnflnthmackeytmp + passfile=/tmp/cnflnttmppassfile + passcrypt=/tmp/cnflntcryptfile + hmacfile=/tmp/cnflnthmacfile + echo -n $(grep ^apitoken: cnflnt.yml|cut -d ' ' -f 2) > $hmackeyfile + /opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile + wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" --post-file=$passcrypt https://$mgr/confluent-api/self/registerapikey -O - --quiet + cp $passfile /etc/confluent/confluent.apikey + nic=$NIC +else + dhuuid=$(reverse_uuid $(cat /sys/devices/virtual/dmi/id/product_uuid)) + dhcpid=$(mktemp) + mkdir -p /etc/confluent + cp /tls/* /etc/ssl/certs/ + cat /tls/*.pem >> /etc/confluent/ca.pem + for nic in $(ip link | grep mtu|grep -v LOOPBACK|cut -d: -f 2|sed -e 's/ //'); do + ip link set $nic up + done + for nic in $(ip link | grep mtu|grep -v LOOPBACK|grep LOWER_UP|cut -d: -f 2|sed -e 's/ //'); do + if udhcpc -i $nic -p $dhcpid -t 2 -T 2 -n -x 93:0007 -x 97:00$dhuuid -q; then + /opt/confluent/bin/copernicus > /etc/confluent/confluent.info + if grep ^MANAGER:.*\\. /etc/confluent/confluent.info ; then + break + fi + fi + ip -4 flush dev $nic + done + mgr=$(grep ^MANAGER:.*\\. /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //') + nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //') + /opt/confluent/bin/clortho $nodename $mgr > /etc/confluent/confluent.apikey +fi apikey=$(cat /etc/confluent/confluent.apikey) cd /etc/confluent wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/deploycfg cd - predir=$(mktemp -d) cd $predir +cp /etc/confluent/deploycfg /etc/confluent/confluent.deploycfg profile=$(grep ^profile: /etc/confluent/deploycfg|cut -d ' ' -f 2) -wget https://$mgr/confluent-public/os/$profile/scripts/pre.sh -chmod u+x pre.sh -wget https://$mgr/confluent-public/os/$profile/preseed.cfg -mv preseed.cfg / -setdebopt auto-install/enable true boolean -setdebopt partman/early_command $predir/pre.sh string -cd - -ip -4 a flush dev $nic -setdebopt netcfg/choose_interface $nic select -setdebopt netcfg/get_hostname $nodename string -v4cfgmeth=$(grep ipv4_method: /etc/confluent/deploycfg |cut -d: -f 2|sed -e 's/ //') -if [ "$v4cfgmeth" = "static" ]; then - setdebopt netcfg/disable_dhcp true boolean - v4addr=$(grep ^ipv4_address: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //') - v4gw=$(grep ^ipv4_gateway: /etc/confluent/deploycfg|cut -d: -f 2| sed -e 's/ //') - if [ "$v4gw" = "null" ]; then - v4gw="" - fi - v4nm=$(grep ^ipv4_netmask: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //') - setdebopt netcfg/get_netmask $v4nm string - setdebopt netcfg/get_ipaddress $v4addr string - setdebopt netcfg/confirm_static true boolean - if [ ! -z "$v4gw" ]; then - setdebopt netcfg/get_gateway $v4gw string - fi - namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //') - for namesrv in "$namesrvs"; do - setdebopt netcfg/get_nameservers $namesrv string - done -elif [ "$v4cfgmeth" = "dhcp" ]; then - setdebopt netcfg/disable_dhcp false boolean - setdebopt netcfg/confirm_static false boolean -fi +namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //') +for namesrv in "$namesrvs"; do + setdebopt netcfg/get_nameservers $namesrv string +done rootpass=$(grep ^rootpassword: /etc/confluent/deploycfg|cut -d ' ' -f 2|sed -e 's/ //') if [ "$rootpass" = null ] || [ -z "$rootpass" ]; then setdebopt passwd/root-login false boolean @@ -84,9 +146,8 @@ setdebopt time/zone $(grep ^timezone: /etc/confluent/deploycfg|cut -d ' ' -f 2|s ntpsrvs=$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //') for ntpsrv in "$ntpsrvs"; do setdebopt clock-setup/ntp true boolean - setdebopt clock-setup/ntep-server $ntpsrv string + setdebopt clock-setup/ntp-server $ntpsrv string done -#setdebopt console-setup/layoutcode $(grep ^keymap: /etc/confluent/deploycfg|cut -d ' ' -f 2) string setdebopt debian-installer/locale $(grep ^locale: /etc/confluent/deploycfg|cut -d ' ' -f 2) select domainname=$(grep ^dnsdomain: /etc/confluent/deploycfg|cut -d ' ' -f 2) if [ ! -z "$domainname" ] && [ "$domainname" != "null" ]; then @@ -95,3 +156,54 @@ fi + +wget https://$mgr/confluent-public/os/$profile/scripts/pre.sh +chmod u+x pre.sh +wget https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh +chmod u+x prechroot.sh +wget https://$mgr/confluent-public/os/$profile/scripts/post.sh +chmod u+x post.sh +wget https://$mgr/confluent-public/os/$profile/preseed.cfg +cat preseed.cfg >> /preseed.cfg +echo $mgr > /etc/confluent/deployer +setdebopt auto-install/enable true boolean +setdebopt partman/early_command $predir/pre.sh string +setdebopt preseed/late_command $predir/prechroot.sh string +mv $predir/post.sh /tmp/ +cd - +ip -4 a flush dev $nic +setdebopt netcfg/choose_interface $nic select +setdebopt netcfg/get_hostname $nodename string +setdebopt netcfg/hostname $nodename string +setdebopt mirror/protocol https string +setdebopt mirror/country manual string +setdebopt mirror/https/hostname deb.debian.org string +setdebopt mirror/https/directory /debian/ string +setdebopt mirror/protocol https string +setdebopt mirror/https/proxy "" string +#setdebopt apt-setup/security_host $mgr string +if [ ! -e /dev/disk/by-label/CNFLNT_IDNT ]; then + v4cfgmeth=$(grep ipv4_method: /etc/confluent/deploycfg |cut -d: -f 2|sed -e 's/ //') + if [ "$v4cfgmeth" = "static" ]; then + setdebopt netcfg/disable_dhcp true boolean + v4addr=$(grep ^ipv4_address: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //') + v4gw=$(grep ^ipv4_gateway: /etc/confluent/deploycfg|cut -d: -f 2| sed -e 's/ //') + if [ "$v4gw" = "null" ]; then + v4gw="" + fi + v4nm=$(grep ^ipv4_netmask: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //') + setdebopt netcfg/get_netmask $v4nm string + setdebopt netcfg/get_ipaddress $v4addr string + setdebopt netcfg/confirm_static true boolean + if [ ! -z "$v4gw" ]; then + setdebopt netcfg/get_gateway $v4gw string + fi + namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //') + for namesrv in "$namesrvs"; do + setdebopt netcfg/get_nameservers $namesrv string + done + elif [ "$vpcfgmeth" = "dhcp" ]; then + setdebopt netcfg/disable_dhcp false boolean + setdebopt netcfg/confirm_static false boolean + fi +fi diff --git a/confluent_osdeploy/debian/profiles/default/initprofile.sh b/confluent_osdeploy/debian/profiles/default/initprofile.sh new file mode 100644 index 00000000..9a2705f2 --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/initprofile.sh @@ -0,0 +1,7 @@ +#!/bin/sh +sed -i 's/label: debian/label: Debian/' $2/profile.yaml && \ +ln -s $1/linux $2/boot/kernel && \ +ln -s $1/initrd.gz $2/boot/initramfs/distribution && \ +mkdir -p $2/boot/efi/boot && \ +mcopy -i $1/boot/grub/efi.img ::/efi/boot/* $2/boot/efi/boot + diff --git a/confluent_osdeploy/debian/profiles/default/preseed.cfg b/confluent_osdeploy/debian/profiles/default/preseed.cfg new file mode 100644 index 00000000..81986a85 --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/preseed.cfg @@ -0,0 +1,27 @@ +d-i anna/choose_modules string openssh-server-udeb +d-i partman-auto/method string regular +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-md/device_remove_md boolean true +d-i partman-auto/expert_recipe_file string /tmp/partitionfile +d-i partman/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i passwd/make-user boolean false +d-i clock-setup/utc boolean true +d-i apt-setup/multiverse boolean false +d-i apt-setup/universe boolean false +d-i apt-setup/backports boolean false +d-i apt-setup/updates boolean false +d-i grub-installer/only_debian boolean true +tasksel tasksel/first multiselect standard +d-i pkgsel/include string openssh-server curl +d-i pkgsel/update-policy select none +d-i pkgsel/updatedb boolean false +d-i finish-install/reboot_in_progress note +popularity-contest popularity-contest/participate boolean false +d-i partman-auto/method string lvm +d-i partman-auto/choose_recipe select atomic +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-auto-lvm/guided_size string max diff --git a/confluent_osdeploy/debian/profiles/default/profile.yaml b/confluent_osdeploy/debian/profiles/default/profile.yaml new file mode 100644 index 00000000..b76cdfbf --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/profile.yaml @@ -0,0 +1,3 @@ +label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile) +kernelargs: quiet osprofile=%%PROFILE%% +#installedargs: example # These arguments would be added to the installed system diff --git a/confluent_osdeploy/debian/profiles/default/scripts/confignet b/confluent_osdeploy/debian/profiles/default/scripts/confignet new file mode 100644 index 00000000..5bf0871b --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/confignet @@ -0,0 +1,565 @@ +#!/usr/bin/python + +import glob +import json +import os +import socket +import sys +import time +import shlex +import subprocess +try: + import yaml +except ImportError: + pass +try: + from importlib.machinery import SourceFileLoader + def load_source(mod, path): + return SourceFileLoader(mod, path).load_module() +except ImportError: + from imp import load_source + +try: + apiclient = load_source('apiclient', '/opt/confluent/bin/apiclient') +except IOError: + apiclient = load_source('apiclient', '/etc/confluent/apiclient') + +def add_lla(iface, mac): + pieces = mac.split(':') + initbyte = int(pieces[0], 16) ^ 2 + lla = 'fe80::{0:x}{1}:{2}ff:fe{3}:{4}{5}/64'.format(initbyte, pieces[1], pieces[2], pieces[3], pieces[4], pieces[5]) + try: + with open('/proc/sys/net/ipv6/conf/{0}/disable_ipv6'.format(iface), 'w') as setin: + setin.write('0') + subprocess.check_call(['ip', 'addr', 'add', 'dev', iface, lla, 'scope', 'link']) + except Exception: + return None + return lla + +#cli = apiclient.HTTPSClient(json=True) +#c = cli.grab_url_with_status('/confluent-api/self/netcfg') +def add_missing_llas(): + #NetworkManager goes out of its way to suppress ipv6 lla, so will just add some + added = {} + linkinfo = subprocess.check_output(['ip', '-br', 'l']).decode('utf8') + ifaces = {} + for line in linkinfo.split('\n'): + line = line.strip().split() + if not line or 'LOOPBACK' in line[-1] or 'NO-CARRIER' in line[-1]: + continue + if 'UP' not in line[-1]: + subprocess.call(['ip', 'link', 'set', line[0], 'up']) + ifaces[line[0]] = line[2] + ips = {} + ipinfo = subprocess.check_output(['ip', '-br', '-6', 'a']).decode('utf8') + for line in ipinfo.split('\n'): + line = line.strip().split(None, 2) + if not line: + continue + ips[line[0]] = line[2] + for iface in ifaces: + for addr in ips.get(iface, '').split(): + if addr.startswith('fe80::'): + break + else: + newlla = add_lla(iface, ifaces[iface]) + if newlla: + added[iface] = newlla + return added + +def rm_tmp_llas(tmpllas): + for iface in tmpllas: + subprocess.check_call(['ip', 'addr', 'del', 'dev', iface, tmpllas[iface]]) + +def await_tentative(): + maxwait = 10 + while b'tentative' in subprocess.check_output(['ip', 'a']): + if maxwait == 0: + break + maxwait -= 1 + time.sleep(1) + +def map_idx_to_name(): + map = {} + devtype = {} + prevdev = None + for line in subprocess.check_output(['ip', 'l']).decode('utf8').splitlines(): + if line.startswith(' ') and 'link/' in line: + typ = line.split()[0].split('/')[1] + devtype[prevdev] = typ if typ != 'ether' else 'ethernet' + if line.startswith(' '): + continue + idx, iface, rst = line.split(':', 2) + prevdev = iface.strip() + rst = rst.split() + try: + midx = rst.index('master') + continue + except ValueError: + pass + idx = int(idx) + iface = iface.strip() + map[idx] = iface + return map, devtype + + +def get_interface_name(iname, settings): + explicitname = settings.get('interface_names', None) + if explicitname: + return explicitname + if settings.get('current_nic', False): + return iname + return None + +class NetplanManager(object): + def __init__(self, deploycfg): + self.cfgbydev = {} + self.read_connections() + self.deploycfg = deploycfg + + def read_connections(self): + for plan in glob.glob('/etc/netplan/*.y*ml'): + with open(plan) as planfile: + planinfo = yaml.safe_load(planfile) + if not planinfo: + continue + nicinfo = planinfo.get('network', {}).get('ethernets', {}) + for devname in nicinfo: + if devname == 'lo': + continue + if 'gateway4' in nicinfo[devname]: + # normalize deprecated syntax on read in + gw4 = nicinfo[devname]['gateway4'] + del nicinfo[devname]['gateway4'] + routeinfo = nicinfo[devname].get('routes', []) + for ri in routeinfo: + if ri.get('via', None) == gw4 and ri.get('to', None) in ('default', '0.0.0.0/0', '0/0'): + break + else: + routeinfo.append({ + 'to': 'default', + 'via': gw4 + }) + nicinfo[devname]['routes'] = routeinfo + self.cfgbydev[devname] = nicinfo[devname] + + def apply_configuration(self, cfg): + devnames = cfg['interfaces'] + if len(devnames) != 1: + raise Exception('Multi-nic team/bonds not yet supported') + stgs = cfg['settings'] + needcfgapply = False + for devname in devnames: + needcfgwrite = False + # ipv6_method missing at uconn... + if stgs.get('ipv6_method', None) == 'static': + curraddr = stgs['ipv6_address'] + currips = self.getcfgarrpath([devname, 'addresses']) + if curraddr not in currips: + needcfgwrite = True + currips.append(curraddr) + if stgs.get('ipv4_method', None) == 'static': + curraddr = stgs['ipv4_address'] + currips = self.getcfgarrpath([devname, 'addresses']) + if curraddr not in currips: + needcfgwrite = True + currips.append(curraddr) + gws = [] + gws.append(stgs.get('ipv4_gateway', None)) + gws.append(stgs.get('ipv6_gateway', None)) + for gwaddr in gws: + if gwaddr: + cfgroutes = self.getcfgarrpath([devname, 'routes']) + for rinfo in cfgroutes: + if rinfo.get('via', None) == gwaddr: + break + else: + needcfgwrite = True + cfgroutes.append({'via': gwaddr, 'to': 'default'}) + dnsips = self.deploycfg.get('nameservers', []) + dnsdomain = self.deploycfg.get('dnsdomain', '') + if dnsips: + currdnsips = self.getcfgarrpath([devname, 'nameservers', 'addresses']) + for dnsip in dnsips: + if dnsip and dnsip not in currdnsips: + needcfgwrite = True + currdnsips.append(dnsip) + if dnsdomain: + currdnsdomain = self.getcfgarrpath([devname, 'nameservers', 'search']) + if dnsdomain not in currdnsdomain: + needcfgwrite = True + currdnsdomain.append(dnsdomain) + if needcfgwrite: + needcfgapply = True + newcfg = {'network': {'version': 2, 'ethernets': {devname: self.cfgbydev[devname]}}} + oumask = os.umask(0o77) + with open('/etc/netplan/{0}-confluentcfg.yaml'.format(devname), 'w') as planout: + planout.write(yaml.dump(newcfg)) + os.umask(oumask) + if needcfgapply: + subprocess.call(['netplan', 'apply']) + + def getcfgarrpath(self, devpath): + currptr = self.cfgbydev + for k in devpath[:-1]: + if k not in currptr: + currptr[k] = {} + currptr = currptr[k] + if devpath[-1] not in currptr: + currptr[devpath[-1]] = [] + return currptr[devpath[-1]] + + + +class WickedManager(object): + def __init__(self): + self.teamidx = 0 + self.read_connections() + + def read_connections(self): + self.cfgbydev = {} + for ifcfg in glob.glob('/etc/sysconfig/network/ifcfg-*'): + devname = ifcfg.replace('/etc/sysconfig/network/ifcfg-', '') + if devname == 'lo': + continue + currcfg = {} + self.cfgbydev[devname] = currcfg + for cfg in open(ifcfg).read().splitlines(): + cfg = cfg.split('#', 1)[0] + try: + kv = ' '.join(shlex.split(cfg)).split('=', 1) + except Exception: + # unparseable line, likely having something we can't handle + del self.cfgbydev[devname] + if len(kv) != 2: + continue + k, v = kv + k = k.strip() + v = v.strip() + currcfg[k] = v + + def apply_configuration(self, cfg): + stgs = cfg['settings'] + ipcfg = 'STARTMODE=auto\n' + routecfg = '' + bootproto4 = stgs.get('ipv4_method', 'none') + bootproto6 = stgs.get('ipv6_method', 'none') + if bootproto4 == 'dhcp' and bootproto6 == 'dhcp': + ipcfg += 'BOOTPROTO=dhcp\n' + elif bootproto4 == 'dhcp': + ipcfg += 'BOOTPROTO=dhcp4\n' + elif bootproto6 == 'dhcp': + ipcfg += 'BOOTPROTO=dhcp6\n' + else: + ipcfg += 'BOOTPROTO=static\n' + if stgs.get('ipv4_address', None): + ipcfg += 'IPADDR=' + stgs['ipv4_address'] + '\n' + v4gw = stgs.get('ipv4_gateway', None) + if stgs.get('ipv6_address', None): + ipcfg += 'IPADDR_V6=' + stgs['ipv6_address'] + '\n' + v6gw = stgs.get('ipv6_gateway', None) + cname = None + if len(cfg['interfaces']) > 1: # creating new team + if not stgs.get('team_mode', None): + sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces']))) + return + if not stgs.get('connection_name', None): + stgs['connection_name'] = 'bond{0}'.format(self.teamidx) + self.teamidx += 1 + cname = stgs['connection_name'] + with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as teamout: + teamout.write(ipcfg) + if stgs['team_mode'] == 'lacp': + stgs['team_mode'] = '802.3ad' + teamout.write("BONDING_MODULE_OPTS='mode={0} miimon=100'\nBONDING_MASTER=yes\n".format(stgs['team_mode'])) + idx = 1 + for iface in cfg['interfaces']: + subprocess.call(['wicked', 'ifdown', iface]) + try: + os.remove('/etc/sysconfig/network/ifcfg-{0}'.format(iface)) + os.remove('/etc/sysconfig/network/ifroute-{0}'.format(iface)) + except OSError: + pass + teamout.write('BONDING_SLAVE{0}={1}\n'.format(idx, iface)) + idx += 1 + else: + cname = list(cfg['interfaces'])[0] + priorcfg = self.cfgbydev.get(cname, {}) + for cf in priorcfg: + if cf.startswith('TEAM_'): + ipcfg += '{0}={1}\n'.format(cf, priorcfg[cf]) + with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as iout: + iout.write(ipcfg) + if v4gw: + routecfg += 'default {0} - {1}\n'.format(v4gw, cname) + if v6gw: + routecfg += 'default {0} - {1}\n'.format(v6gw, cname) + if routecfg: + with open('/etc/sysconfig/network/ifroute-{0}'.format(cname), 'w') as routeout: + routeout.write(routecfg) + subprocess.call(['wicked', 'ifup', cname]) + + +class NetworkManager(object): + bondtypes = { + 'lacp': '802.3ad', + 'loadbalance': 'balance-alb', + 'roundrobin': 'balance-rr', + 'activebackup': 'active-backup', + } + def __init__(self, devtypes, deploycfg): + self.deploycfg = deploycfg + self.connections = {} + self.uuidbyname = {} + self.uuidbydev = {} + self.connectiondetail = {} + self.read_connections() + self.teamidx = 0 + self.devtypes = devtypes + + def read_connections(self): + self.connections = {} + self.uuidbyname = {} + self.uuidbydev = {} + self.connectiondetail = {} + ci = subprocess.check_output(['nmcli', '-t', 'c']).decode('utf8') + for inf in ci.splitlines(): + n, u, t, dev = inf.split(':') + if n == 'NAME': + continue + if dev == '--': + dev = None + self.uuidbyname[n] = u + if dev: + self.uuidbydev[dev] = u + self.connections[u] = {'name': n, 'uuid': u, 'type': t, 'dev': dev} + deats = {} + for deat in subprocess.check_output(['nmcli', 'c', 's', u]).decode('utf8').splitlines(): + k, v = deat.split(':', 1) + v = v.strip() + if v == '--': + continue + if '(default)' in v: + continue + deats[k] = v + self.connectiondetail[u] = deats + + + def add_team_member(self, team, member): + bondcfg = {} + if member in self.uuidbydev: + myuuid = self.uuidbydev[member] + deats = self.connectiondetail[myuuid] + currteam = deats.get('connection.master', None) + if currteam == team: + return + for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname'): + if deats.get(stg, None): + bondcfg[stg] = deats[stg] + if member in self.uuidbyname: + subprocess.check_call(['nmcli', 'c', 'del', self.uuidbyname[member]]) + devtype = self.devtypes.get(member, 'bond-slave') + subprocess.check_call(['nmcli', 'c', 'add', 'type', devtype, 'master', team, 'con-name', member, 'connection.interface-name', member]) + if bondcfg: + args = [] + for parm in bondcfg: + args.append(parm) + args.append(bondcfg[parm]) + subprocess.check_call(['nmcli', 'c', 'm', team] + args) + + def apply_configuration(self, cfg, lastchance=False): + cmdargs = {} + cmdargs['connection.autoconnect'] = 'yes' + stgs = cfg['settings'] + cmdargs['ipv6.method'] = stgs.get('ipv6_method', 'link-local') + if stgs.get('ipv6_address', None): + cmdargs['ipv6.addresses'] = stgs['ipv6_address'] + cmdargs['ipv4.method'] = stgs.get('ipv4_method', 'disabled') + if stgs.get('ipv4_address', None): + cmdargs['ipv4.addresses'] = stgs['ipv4_address'] + if stgs.get('ipv4_gateway', None): + cmdargs['ipv4.gateway'] = stgs['ipv4_gateway'] + if stgs.get('ipv6_gateway', None): + cmdargs['ipv6.gateway'] = stgs['ipv6_gateway'] + dnsips = self.deploycfg.get('nameservers', []) + if not dnsips: + dnsips = [] + dns4 = [] + dns6 = [] + for dnsip in dnsips: + if '.' in dnsip: + dns4.append(dnsip) + elif ':' in dnsip: + dns6.append(dnsip) + if dns4: + cmdargs['ipv4.dns'] = ','.join(dns4) + if dns6: + cmdargs['ipv6.dns'] = ','.join(dns6) + if len(cfg['interfaces']) > 1: # team time.. should be.. + if not cfg['settings'].get('team_mode', None): + sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces']))) + return + if not cfg['settings'].get('connection_name', None): + cfg['settings']['connection_name'] = 'team{0}'.format(self.teamidx) + self.teamidx += 1 + cname = cfg['settings']['connection_name'] + cargs = [] + for arg in cmdargs: + cargs.append(arg) + cargs.append(cmdargs[arg]) + if stgs['team_mode'] in self.bondtypes: + stgs['team_mode'] = self.bondtypes[stgs['team_mode']] + subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'mode={}'.format(stgs['team_mode'])] + cargs) + for iface in cfg['interfaces']: + self.add_team_member(cname, iface) + subprocess.check_call(['nmcli', 'c', 'u', cname]) + else: + cname = stgs.get('connection_name', None) + iname = list(cfg['interfaces'])[0] + ctype = self.devtypes.get(iname, None) + if not ctype: + if lastchance: + sys.stderr.write("Warning, no device found for interface_name ({0}), skipping setup\n".format(iname)) + return 1 + if stgs.get('vlan_id', None): + vlan = stgs['vlan_id'] + if ctype == 'infiniband': + vlan = '0x{0}'.format(vlan) if not vlan.startswith('0x') else vlan + cmdargs['infiniband.parent'] = iname + cmdargs['infiniband.p-key'] = vlan + iname = '{0}.{1}'.format(iname, vlan[2:]) + elif ctype == 'ethernet': + ctype = 'vlan' + cmdargs['vlan.parent'] = iname + cmdargs['vlan.id'] = vlan + iname = '{0}.{1}'.format(iname, vlan) + else: + sys.stderr.write("Warning, unknown interface_name ({0}) device type ({1}) for VLAN/PKEY, skipping setup\n".format(iname, ctype)) + return + cname = iname if not cname else cname + u = self.uuidbyname.get(cname, None) + cargs = [] + for arg in cmdargs: + cargs.append(arg) + cargs.append(cmdargs[arg]) + if u: + subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs) + subprocess.check_call(['nmcli', 'c', 'u', u]) + else: + subprocess.check_call(['nmcli', 'c', 'add', 'type', ctype, 'con-name', cname, 'connection.interface-name', iname] + cargs) + self.read_connections() + u = self.uuidbyname.get(cname, None) + if u: + subprocess.check_call(['nmcli', 'c', 'u', u]) + + + +if __name__ == '__main__': + havefirewall = subprocess.call(['systemctl', 'status', 'firewalld']) + havefirewall = havefirewall == 0 + if havefirewall: + subprocess.check_call(['systemctl', 'stop', 'firewalld']) + tmpllas = add_missing_llas() + await_tentative() + idxmap, devtypes = map_idx_to_name() + netname_to_interfaces = {} + myaddrs = apiclient.get_my_addresses() + srvs, _ = apiclient.scan_confluents() + doneidxs = set([]) + dc = None + if not srvs: # the multicast scan failed, fallback to deploycfg cfg file + with open('/etc/confluent/confluent.deploycfg', 'r') as dci: + for cfgline in dci.read().split('\n'): + if cfgline.startswith('deploy_server:'): + srvs = [cfgline.split()[1]] + break + for srv in srvs: + try: + s = socket.create_connection((srv, 443)) + except socket.error: + continue + myname = s.getsockname() + s.close() + if len(myname) == 4: + curridx = myname[-1] + else: + myname = myname[0] + myname = socket.inet_pton(socket.AF_INET, myname) + for addr in myaddrs: + if myname == addr[1].tobytes(): + curridx = addr[-1] + if curridx in doneidxs: + continue + for tries in (1, 2, 3): + try: + status, nc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/netcfg') + break + except Exception: + if tries == 3: + raise + time.sleep(1) + continue + nc = json.loads(nc) + if not dc: + for tries in (1, 2, 3): + try: + status, dc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/deploycfg2') + break + except Exception: + if tries == 3: + raise + time.sleep(1) + continue + dc = json.loads(dc) + iname = get_interface_name(idxmap[curridx], nc.get('default', {})) + if iname: + for iname in iname.split(','): + if 'default' in netname_to_interfaces: + netname_to_interfaces['default']['interfaces'].add(iname) + else: + netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']} + for netname in nc.get('extranets', {}): + uname = '_' + netname + iname = get_interface_name(idxmap[curridx], nc['extranets'][netname]) + if iname: + for iname in iname.split(','): + if uname in netname_to_interfaces: + netname_to_interfaces[uname]['interfaces'].add(iname) + else: + netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]} + doneidxs.add(curridx) + if 'default' in netname_to_interfaces: + for netn in netname_to_interfaces: + if netn == 'default': + continue + netname_to_interfaces['default']['interfaces'] -= netname_to_interfaces[netn]['interfaces'] + if not netname_to_interfaces['default']['interfaces']: + del netname_to_interfaces['default'] + # Make sure VLAN/PKEY connections are created last + netname_to_interfaces = dict(sorted(netname_to_interfaces.items(), key=lambda item: 'vlan_id' in item[1]['settings'])) + rm_tmp_llas(tmpllas) + if os.path.exists('/usr/sbin/netplan'): + nm = NetplanManager(dc) + if os.path.exists('/usr/bin/nmcli'): + nm = NetworkManager(devtypes, dc) + elif os.path.exists('/usr/sbin/wicked'): + nm = WickedManager() + retrynics = [] + for netn in netname_to_interfaces: + redo = nm.apply_configuration(netname_to_interfaces[netn]) + if redo == 1: + retrynics.append(netn) + if retrynics: + idxmap, devtypes = map_idx_to_name() + if os.path.exists('/usr/sbin/netplan'): + nm = NetplanManager(dc) + if os.path.exists('/usr/bin/nmcli'): + nm = NetworkManager(devtypes, dc) + elif os.path.exists('/usr/sbin/wicked'): + nm = WickedManager() + for netn in retrynics: + nm.apply_configuration(netname_to_interfaces[netn], lastchance=True) + if havefirewall: + subprocess.check_call(['systemctl', 'start', 'firewalld']) + await_tentative() + diff --git a/confluent_osdeploy/debian/profiles/default/scripts/firstboot.service b/confluent_osdeploy/debian/profiles/default/scripts/firstboot.service new file mode 100644 index 00000000..209a95e6 --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/firstboot.service @@ -0,0 +1,11 @@ +[Unit] +Description=First Boot Process +Requires=network-online.target +After=network-online.target + +[Service] +ExecStart=/opt/confluent/bin/firstboot.sh + +[Install] +WantedBy=multi-user.target + diff --git a/confluent_osdeploy/debian/profiles/default/scripts/firstboot.sh b/confluent_osdeploy/debian/profiles/default/scripts/firstboot.sh new file mode 100755 index 00000000..e960d6e0 --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/firstboot.sh @@ -0,0 +1,22 @@ +#!/bin/bash +echo "Confluent first boot is running" +HOME=$(getent passwd $(whoami)|cut -d: -f 6) +export HOME +#cp -a /etc/confluent/ssh/* /etc/ssh/ +#systemctl restart sshd +rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}') +if [ ! -z "$rootpw" -a "$rootpw" != "null" ]; then + echo root:$rootpw | chpasswd -e +fi +nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}') +confluent_apikey=$(cat /etc/confluent/confluent.apikey) +confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}') +while ! ping -c 1 $confluent_mgr >& /dev/null; do + sleep 1 +done +source /etc/confluent/functions + +run_remote_parts firstboot.d +run_remote_config firstboot.d +systemctl disable firstboot +curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus diff --git a/confluent_osdeploy/debian/profiles/default/scripts/functions b/confluent_osdeploy/debian/profiles/default/scripts/functions new file mode 100644 index 00000000..f68f3a5e --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/functions @@ -0,0 +1,209 @@ +#!/bin/bash +function test_mgr() { + whost=$1 + if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then + whost="[$whost]" + fi + if curl -gs https://${whost}/confluent-api/ > /dev/null; then + return 0 + fi + return 1 +} + +function confluentpython() { + if [ -x /usr/libexec/platform-python ]; then + /usr/libexec/platform-python $* + elif [ -x /usr/bin/python3 ]; then + /usr/bin/python3 $* + elif [ -x /usr/bin/python ]; then + /usr/bin/python $* + elif [ -x /usr/bin/python2 ]; then + /usr/bin/python2 $* + fi +} + +function set_confluent_vars() { + if [ -z "$nodename" ]; then + nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}') + fi + if [[ "$confluent_mgr" == *"%"* ]]; then + confluent_mgr="" + fi + if [ -z "$confluent_mgr" ]; then + confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //') + if ! test_mgr $confluent_mgr; then + confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //') + if [[ "$confluent_mgr" = *":"* ]]; then + confluent_mgr="[$confluent_mgr]" + fi + fi + if ! test_mgr $confluent_mgr; then + BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//') + OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//') + for confluent_mgr in $BESTMGRS $OKMGRS; do + if [[ $confluent_mgr == *":"* ]]; then + confluent_mgr="[$confluent_mgr]" + fi + if test_mgr $confluent_mgr; then + break + fi + done + fi + fi + if [ -z "$confluent_profile" ]; then + confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //') + fi + export confluent_profile confluent_mgr nodename +} + +fetch_remote() { + curlargs="" + if [ -f /etc/confluent/ca.pem ]; then + curlargs=" --cacert /etc/confluent/ca.pem" + fi + set_confluent_vars + mkdir -p $(dirname $1) + whost=$confluent_mgr + if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then + whost="[$whost]" + fi + curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1 + if [ $? != 0 ]; then echo $1 failed to download; return 1; fi +} + +source_remote_parts() { + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + apiclient=/opt/confluent/bin/apiclient + if [ -f /etc/confluent/apiclient ]; then + apiclient=/etc/confluent/apiclient + fi + scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //') + for script in $scriptlist; do + source_remote $1/$script + done + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir +} + +run_remote_parts() { + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + apiclient=/opt/confluent/bin/apiclient + if [ -f /etc/confluent/apiclient ]; then + apiclient=/etc/confluent/apiclient + fi + scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //') + for script in $scriptlist; do + run_remote $1/$script + done + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir +} + +source_remote() { + set_confluent_vars + unsettmpdir=0 + echo + echo '---------------------------------------------------------------------------' + echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/ + if [ -z "$confluentscripttmpdir" ]; then + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + unsettmpdir=1 + fi + echo Sourcing from $confluentscripttmpdir + cd $confluentscripttmpdir + fetch_remote $1 + if [ $? != 0 ]; then echo $1 failed to download; return 1; fi + chmod +x $1 + cmd=$1 + shift + source ./$cmd + cd - > /dev/null + if [ "$unsettmpdir" = 1 ]; then + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir + unsettmpdir=0 + fi + rm -rf $confluentscripttmpdir + return $retcode +} + +run_remote() { + requestedcmd="'$*'" + unsettmpdir=0 + set_confluent_vars + echo + echo '---------------------------------------------------------------------------' + echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/ + if [ -z "$confluentscripttmpdir" ]; then + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + unsettmpdir=1 + fi + echo Executing in $confluentscripttmpdir + cd $confluentscripttmpdir + fetch_remote $1 + if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi + chmod +x $1 + cmd=$1 + if [ -x /usr/bin/chcon ]; then + chcon system_u:object_r:bin_t:s0 $cmd + fi + shift + ./$cmd $* + retcode=$? + if [ $retcode -ne 0 ]; then + echo "$requestedcmd exited with code $retcode" + fi + cd - > /dev/null + if [ "$unsettmpdir" = 1 ]; then + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir + unsettmpdir=0 + fi + return $retcode +} + +run_remote_python() { + echo + set_confluent_vars + if [ -f /etc/confluent/ca.pem ]; then + curlargs=" --cacert /etc/confluent/ca.pem" + fi + echo '---------------------------------------------------------------------------' + echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/ + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + echo Executing in $confluentscripttmpdir + cd $confluentscripttmpdir + mkdir -p $(dirname $1) + whost=$confluent_mgr + if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then + whost="[$whost]" + fi + curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1 + if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi + confluentpython $* + retcode=$? + echo "'$*' exited with code $retcode" + cd - > /dev/null + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir + return $retcode +} + +run_remote_config() { + echo + set_confluent_vars + apiclient=/opt/confluent/bin/apiclient + if [ -f /etc/confluent/apiclient ]; then + apiclient=/etc/confluent/apiclient + fi + echo '---------------------------------------------------------------------------' + echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile + confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {} + confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204 + echo + echo 'Completed remote configuration' + echo '---------------------------------------------------------------------------' + return +} +#If invoked as a command, use the arguments to actually run a function +(return 0 2>/dev/null) || $1 "${@:2}" diff --git a/confluent_osdeploy/debian/profiles/default/scripts/post.sh b/confluent_osdeploy/debian/profiles/default/scripts/post.sh new file mode 100755 index 00000000..f30d980f --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/post.sh @@ -0,0 +1,67 @@ +#!/bin/bash +mkdir -p /run/sshd +mkdir -p /root/.ssh +cat /tmp/ssh/*pubkey >> /root/.ssh/authorized_keys +cat /tmp/ssh/*.ca | sed -e s/^/'@cert-authority * '/ >> /etc/ssh/ssh_known_hosts +chmod 700 /etc/confluent +chmod go-rwx /etc/confluent/* +sshconf=/etc/ssh/ssh_config +if [ -d /etc/ssh/ssh_config.d/ ]; then + sshconf=/etc/ssh/ssh_config.d/01-confluent.conf +fi +echo 'Host *' >> $sshconf +echo ' HostbasedAuthentication yes' >> $sshconf +echo ' EnableSSHKeysign yes' >> $sshconf +echo ' HostbasedKeyTypes *ed25519*' >> $sshconf +/usr/sbin/sshd +confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | awk '{print $2}') +mkdir -p /opt/confluent/bin +python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh +chmod +x /opt/confluent/bin/firstboot.sh +python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service +systemctl enable firstboot +python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions +source /etc/confluent/functions +python3 /opt/confluent/bin/apiclient /confluent-api/self/nodelist | sed -e s/'^- //' > /tmp/allnodes +cp /tmp/allnodes /root/.shosts +cp /tmp/allnodes /etc/ssh/shosts.equiv +if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then + ntps=$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|sed 1d|sed '$d' | sed -e 's/^- //' | paste -sd ' ') + sed -i "s/#NTP=/NTP=$ntps/" /etc/systemd/timesyncd.conf +fi +textcons=$(grep ^textconsole: /etc/confluent/confluent.deploycfg |awk '{print $2}') +updategrub=0 +if [ "$textcons" = "true" ] && ! grep console= /proc/cmdline > /dev/null; then + cons="" + if [ -f /tmp/autocons.info ]; then + cons=$(cat /tmp/autocons.info) + fi + if [ ! -z "$cons" ]; then + sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 console='${cons#/dev/}'"/' /etc/default/grub + updategrub=1 + fi +fi +kargs=$(python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/profile.yaml | grep ^installedargs: | sed -e 's/#.*//') +if [ ! -z "$kargs" ]; then + sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 '"${kargs}"'"/' /etc/default/grub +fi + +if [ 1 = $updategrub ]; then + update-grub +fi + +if [ -e /sys/firmware/efi ]; then + bootnum=$(efibootmgr | grep ubuntu | sed -e 's/ .*//' -e 's/\*//' -e s/Boot//) + if [ ! -z "$bootnum" ]; then + currboot=$(efibootmgr | grep ^BootOrder: | awk '{print $2}') + nextboot=$(echo $currboot| awk -F, '{print $1}') + [ "$nextboot" = "$bootnum" ] || efibootmgr -o $bootnum,$currboot + efibootmgr -D + fi +fi +run_remote_python syncfileclient +run_remote_parts post.d +run_remote_config post + +python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged' + diff --git a/confluent_osdeploy/debian/profiles/default/scripts/pre.sh b/confluent_osdeploy/debian/profiles/default/scripts/pre.sh old mode 100644 new mode 100755 index 85347f59..6e8b9c4c --- a/confluent_osdeploy/debian/profiles/default/scripts/pre.sh +++ b/confluent_osdeploy/debian/profiles/default/scripts/pre.sh @@ -1,18 +1,88 @@ -anna-install openssh-server-udeb -mkdir -p ~/.ssh/ -cat /ssh/*pubkey > ~/.ssh/authorized_keys -ssh-keygen -A -mgr=$(grep ^MANAGER:.*\\. /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //') -nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //') +#!/bin/sh +## Use the following option to add additional boot parameters for the +## installed system (if supported by the bootloader installer). +## Note: options passed to the installer will be added automatically. +#d-i debian-installer/add-kernel-opts string [from profile.yaml] +deploycfg=/etc/confluent/confluent.deploycfg +mgr=$(cat /etc/confluent/deployer) + +cryptboot=$(grep encryptboot: $deploycfg|sed -e 's/^encryptboot: //') +if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "null" ]; then + echo "****Encrypted boot requested, but not implemented for this OS, halting install" > /dev/console + [ -f '/tmp/autoconsdev' ] && (echo "****Encryptod boot requested, but not implemented for this OS,halting install" >> $(cat /tmp/autoconsdev)) + while :; do sleep 86400; done +fi +cat > /usr/lib/live-installer.d/confluent-certs << EOF +#!/bin/sh +cp /tls/* /target/etc/ssl/certs/ +cat /tls/*.pem >> /target/etc/ssl/certs/ca-certificates.crt +EOF +chmod a+x /usr/lib/live-installer.d/confluent-certs +mkdir -p /.ssh/ +cat /ssh/*pubkey > /.ssh/authorized_keys +mkdir -p /etc/ssh +nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|cut -d ' ' -f 2) apikey=$(cat /etc/confluent/confluent.apikey) +ssh-keygen -A for pubkey in /etc/ssh/ssh_host*key.pub; do - certfile=${pubkey%.pub}-cert.pub + certfile=$(echo $pubkey | sed -e s/.pub/-cert.pub/) keyfile=${pubkey%.pub} - wget --post-file=$pubkey --header='CONFLUENT_NODENAME: '$nodename --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/sshcert -O $certfile + wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" --post-file=$pubkey https://$mgr/confluent-api/self/sshcert -O $certfile --quiet echo HostKey $keyfile >> /etc/ssh/sshd_config echo HostCertificate $certfile >> /etc/ssh/sshd_config done - -echo sshd:x:939:939::/: >> /etc/passwd +if [ -e /tmp/installdisk ]; then + instdisk=$(cat /tmp/installdisk) +else + for blockdev in $(ls /sys/class/block/); do + shortname=$(basename $blockdev) + if [ "$shortname" != "${shortname%loop*}" ]; then + continue + fi + udevadm info --query=property /dev/$shortname |grep DEVTYPE=disk > /dev/null || continue # ignore partitions + udevadm info --query=property /dev/$shortname |grep DM_NAME > /dev/null && continue # not a real disk + sz=$(cat /sys/block/$shortname/size 2> /dev/null) + [ -z "$sz" ] && continue + [ $sz -lt 1048576 ] && continue # Too small + [ -z "$firstdisk" ] && firstdisk=$shortname + if udevadm info --query=property /dev/$shortname|grep ID_MODEL=| sed -e s/' '/_/g | grep -iE '(thinksystem_m.2|m.2_nvme_2-bay_raid_kit)' > /dev/null; then + instdisk=$shortname + break + fi + if udevadm info --query=property /dev/$shortname|grep MD_CONTAINER=imsm; then + sraid=$sortname + else + drv=$(udevadm info -a /dev/sdb|grep DRIVERS==|grep -Ev '""|"sd"' | sed -e s/.*=// -e s/'"'//g) + if [ "ahci" = "$drv" -a -z "$onbdisk" ]; then + onbdisk=$shortname + elif [ "megaraid" = "$drv" -a -z "$rdisk" ]; then + rdisk=$shortname + fi + fi + done +fi +if [ -z "$instdisk" ]; then + if [ ! -z "$sraid"]; then + instdisk=$sraid + elif [ ! -z "$onbdisk" ]; then + instdisk=$onbdisk + elif [ ! -z "$rdisk" ]; then + instdisk=$rdisk + else + instdisk=$firstdisk + fi +fi +if [ ! -z "$instdisk" ]; then + debconf-set partman-auto/disk /dev/$instdisk + debconf-set grub-installer/bootdev /dev/$instdisk +fi +echo HostbasedAuthentication yes >> /etc/ssh/sshd_config +echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config +echo IgnoreRhosts no >> /etc/ssh/sshd_config +echo sshd:x:1:1::/run/sshd:/bin/false >> /etc/passwd /usr/sbin/sshd - +wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/nodelist -O /tmp/allnodes --quiet +#kill -HUP $(ps | grep -v grep | grep /usr/sbin/sshd | sed -e 's/^ *//'|cut -d ' ' -f 1) +#curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /tmp/getinstalldisk +#python3 /tmp/getinstalldisk +#sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml diff --git a/confluent_osdeploy/debian/profiles/default/scripts/prechroot.sh b/confluent_osdeploy/debian/profiles/default/scripts/prechroot.sh new file mode 100644 index 00000000..0824aead --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/prechroot.sh @@ -0,0 +1,19 @@ +#!/bin/sh +mount -o bind /sys /target/sys +mount -o bind /dev /target/dev +mount -o bind /dev/pts /target/dev/pts +mount -o bind /proc /target/proc +mount -o bind /dev/pts /target/dev/pts +mount -o bind /run /target/run +cp -a /etc/confluent /target/etc/confluent +cp -a /opt/confluent /target/opt/confluent +mv /tmp/post.sh /target/tmp/ +cp -a /ssh /tls /target/tmp +cat /tls/*.pem >> /target/etc/confluent/ca.pem +cp -a /etc/ssh/ssh_host_* /target/etc/ssh/ +grep HostCertificate /etc/ssh/sshd_config >> /target/etc/ssh/sshd_config +echo Port 2222 >> /etc/ssh/sshd_config +kill -HUP $(ps |grep -v grep|grep sshd|grep /usr|sed -e s/' root.*//') +cp /tls/* /target/etc/ssl/certs/ +cat /tls/*.pem >> /target/etc/ssl/certs/ca-certificates.crt +chroot /target bash /tmp/post.sh diff --git a/confluent_osdeploy/debian/profiles/default/scripts/setupssh b/confluent_osdeploy/debian/profiles/default/scripts/setupssh new file mode 100644 index 00000000..06ae3e01 --- /dev/null +++ b/confluent_osdeploy/debian/profiles/default/scripts/setupssh @@ -0,0 +1,43 @@ +[ -f /lib/confluent/functions ] && . /lib/confluent/functions +[ -f /etc/confluent/functions ] && . /etc/confluent/functions +[ -f /opt/confluent/bin/apiclient ] && confapiclient=/opt/confluent/bin/apiclient +[ -f /etc/confluent/apiclient ] && confapiclient=/etc/confluent/apiclient +for pubkey in /etc/ssh/ssh_host*key.pub; do + if [ "$pubkey" = /etc/ssh/ssh_host_key.pub ]; then + continue + fi + certfile=${pubkey/.pub/-cert.pub} + rm $certfile + confluentpython $confapiclient /confluent-api/self/sshcert $pubkey -o $certfile +done +if [ -d /etc/ssh/sshd_config.d/ -a ! -e /etc/ssh/sshd_config.d/90-confluent.conf ]; then + for cert in /etc/ssh/ssh*-cert.pub; do + echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf + done + echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/90-confluent.conf + echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/90-confluent.conf + echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/90-confluent.conf +fi + +TMPDIR=$(mktemp -d) +cd $TMPDIR +confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs.tgz +tar xf initramfs.tgz +for ca in ssh/*.ca; do + LINE=$(cat $ca) + cp -af /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts.new + grep -v "$LINE" /etc/ssh/ssh_known_hosts > /etc/ssh/ssh_known_hosts.new + echo '@cert-authority *' $LINE >> /etc/ssh/ssh_known_hosts.new + mv /etc/ssh/ssh_known_hosts.new /etc/ssh/ssh_known_hosts +done +for pubkey in ssh/*.*pubkey; do + LINE=$(cat $pubkey) + cp -af /root/.ssh/authorized_keys /root/.ssh/authorized_keys.new + grep -v "$LINE" /root/.ssh/authorized_keys > /root/.ssh/authorized_keys.new + echo "$LINE" >> /root/.ssh/authorized_keys.new + mv /root/.ssh/authorized_keys.new /root/.ssh/authorized_keys +done +confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' > /etc/ssh/shosts.equiv +cat /etc/ssh/shosts.equiv > /root/.shosts +cd - +rm -rf $TMPDIR diff --git a/confluent_osdeploy/ubuntu18.04/profiles/default/scripts/pre.sh b/confluent_osdeploy/ubuntu18.04/profiles/default/scripts/pre.sh index de2cf5e2..b480cce0 100755 --- a/confluent_osdeploy/ubuntu18.04/profiles/default/scripts/pre.sh +++ b/confluent_osdeploy/ubuntu18.04/profiles/default/scripts/pre.sh @@ -85,3 +85,4 @@ wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apike #curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /tmp/getinstalldisk #python3 /tmp/getinstalldisk #sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml +umount /media