2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-29 00:31:09 +00:00

Compare commits

...

291 Commits

Author SHA1 Message Date
Jarrod Johnson a9ba385996 Fix type of maxnodes 2026-08-21 14:58:13 -04:00
Jarrod Johnson 29a9d417d6 Do not aggressively respawn buffer daemon.
Start buffer daemon only when needed.

Limit restarts to once every 30 seconds.
2026-08-20 07:23:40 -04:00
Jarrod Johnson 7347125b6f Disable implicit tenant creation
If we support more tenants, we will modify that branch.
2026-07-16 12:39:51 -04:00
Jarrod Johnson 66347a937e Ensure prefix is a string 2026-07-14 11:01:32 -04:00
Jarrod Johnson ee01dfd05e Add UEFI HTTP boot for arm64 to recognized archs
For now, we serve up the whole image, no need to distinguish arm from x86 here yet
2026-07-14 09:52:27 -04:00
Jarrod Johnson 611e1f5bc3 Fix nodemedia attach
The hardening blocked all URL patters.
2026-07-14 09:48:09 -04:00
Jarrod Johnson 7622145fea Fall back to x64 if the aarch64 location didn't pan out 2026-07-13 10:01:04 -04:00
Jarrod Johnson 678bd53857 Correct paths to aarch64 boot material in imgutil 2026-07-13 09:23:33 -04:00
Jarrod Johnson 3295778566 Improve webauthn error handling and tighten up routing
If not webauthn, have authorize
bail out on webauthn request instead
of a sessionless authdata.

For some "special" HTTP paths, tighten up routing rules.
2026-07-08 12:03:48 -04:00
Jarrod Johnson b3acf011bd Restart vtbufferd on exit
Notably, if you strace it, it will trigger an exit(1).  There was at least one documented segmentation fault as well.

Buffer content is lost in such an event, but service remains running.
2026-07-02 12:20:01 -04:00
Jarrod Johnson dc4cafc29f Rework autoconsole logic
Match autocons

Skip unless EFI x86_64.

If SPCR, trust it and use that unconditionally.

Otherwise, if only one can respond to TIOCMGET, then use that one.

If multiple can respond, but exactly one shows carrier, use that.
2026-06-25 16:41:32 -04:00
Jarrod Johnson 3340585fb4 Only count copernicus replies that have OK status 2026-06-25 15:24:31 -04:00
Jarrod Johnson 4456767122 When possible, check confluent user access to file
If a confluent user is a system user, do not allow them to
upload paths that their user would not have access to otherwise.

For non-system users, continue with the path based banned behavior.
2026-06-25 12:15:13 -04:00
Jarrod Johnson 556bb1d0ff Prevent staging of files from indicating path traversal 2026-06-25 10:09:59 -04:00
Jarrod Johnson a201e9886e Have messages force normalizing the incoming filenames
This avoids downstream code that may expect specific locations from being confused.
2026-06-25 09:48:40 -04:00
Jarrod Johnson f82993efe7 Fix debian deployment on slow network link up
When network link was slow to establish, it would fall right through
the network initilalization code.

Now keep working it until a result is acheived.
2026-06-25 08:37:19 -04:00
Jarrod Johnson f8366a50ef Do not set 0.0.0.0 gateway 2026-06-24 15:59:10 -04:00
Jarrod Johnson d369dcac55 Fix non-bonding configuration of ubuntu 2026-06-16 12:42:03 -04:00
Jarrod Johnson 3b2d92a219 Correct typo in pthread name 2026-06-08 11:00:40 -04:00
Jarrod Johnson ffacb66c62 Update rdma vintage 2026-06-08 10:56:26 -04:00
Jarrod Johnson 2073f421da Add libraries to genesis 2026-06-08 10:54:50 -04:00
Jarrod Johnson ed2eed66dc Allow nodedeploy to request http boot specifically 2026-06-03 09:28:44 -04:00
Jarrod Johnson d8f9b6c8e6 Add support for http boot
Some redfish require us to be very specific.
2026-06-03 09:12:46 -04:00
Jarrod Johnson d9a18a7bf6 Actually use the interposer for firmware update 2026-06-01 19:52:43 -04:00
Markus Hilger 439a930188 confignet: Fix interface type detection for IB VFs
IB VFs have the following "ip l" output:

4: ibp129s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 2044 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/infiniband 00:00:00:8d:fe:80:00:00:00:00:00:00:60:5e:65:03:00:2c:43:c8 brd 00:ff:ff:ff:ff:12:40:1b:ff:ff:00:00:00:00:00:00:ff:ff:ff:ff
    vf 0     link/infiniband 00:00:00:8d:fe:80:00:00:00:00:00:00:60:5e:65:03:00:2c:43:c8 brd 00:ff:ff:ff:ff:12:40:1b:ff:ff:00:00:00:00:00:00:ff:ff:ff:ff, spoof checking off, NODE_GUID 00:00:00:00:00:00:00:00, PORT_GUID 00:00:00:00:00:00:00:00, link-state enable, trust off, query_rss off
5: eno1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN mode DEFAULT group default qlen 1000
    link/ether 30:56:0f:17:c0:b4 brd ff:ff:ff:ff:ff:ff
    altname enp196s0
    altname enx30560f17c0b4

This breaks the detection script because index 0 of the "vf 0 ..." line is not link/<type> anymore.
This commit improves the detection logic to fix this.
2026-06-01 19:30:54 -04:00
Jarrod Johnson 00b2afd42b Fixes for confignet for Ubuntu
Try to find various layers of network config and normalize.

Ultimately, after post subiquity will do some things and easiest to fix in firstboot instead.
2026-06-01 16:49:42 -04:00
Jarrod Johnson 90c2a4cf73 Fix iterating the netplan configuration 2026-06-01 12:55:11 -04:00
Jarrod Johnson c691dc7159 Remove cloud-init netplan if redundant 2026-06-01 09:33:25 -04:00
Jarrod Johnson a7c188b812 Add support for passing a parameterfile in updates 2026-06-01 07:51:22 -04:00
Jarrod Johnson 5ba43ecaa0 Add bonding to netplan management 2026-05-26 10:06:49 -04:00
Vinícius Ferrão 2f53d3bde6 Include xen-front drivers in confluent-curated initramfs 2026-05-23 14:21:13 -04:00
Jarrod Johnson 9fe9e8672a Support more states 2026-05-21 10:03:08 -04:00
Jarrod Johnson 0fe60175f3 Add missing close 2026-05-21 08:36:16 -04:00
Jarrod Johnson d411041243 Recognize more storage states 2026-05-20 16:14:24 -04:00
Jarrod Johnson cc101d12bc Port diskless enhancements from el9 to ubuntu 2026-05-20 15:30:23 -04:00
Jarrod Johnson 2f08ee81f2 Fix off by one in urlmount 2026-05-20 12:37:36 -04:00
Jarrod Johnson 1e9231eea6 Add megasol method 2026-05-19 09:17:50 -04:00
Jarrod Johnson 24cb05e535 Fix name of ssh in various ubuntu scripts 2026-05-13 13:52:05 -04:00
Jarrod Johnson 72b95abb4f Add missing syncfiles examples to ubuntu profiles 2026-05-13 13:51:59 -04:00
Jarrod Johnson 57a170d0d8 Implement a headless mode
For automation, this can make more sense.
2026-05-12 11:23:37 -04:00
Jarrod Johnson daeabc6fe5 Add expression support to the nodeconsole automation 2026-05-11 16:51:01 -04:00
Jarrod Johnson 28a8f6f0d6 Provide automation facility for nodeconsole
Allow nodeconsole to walk console according to a script
2026-05-11 13:55:54 -04:00
Jarrod Johnson 7542897b43 Normalize perms to int or None 2026-05-07 09:44:00 -04:00
Jarrod Johnson c69952265f Permit override of unix ownership/permissions on sockets
If an environment knows more specifically what should have access in terms of group, allow service.cfg to indicate.
2026-05-07 08:44:16 -04:00
Jarrod Johnson 01cc86fa55 Add a '-r' argument to refresh site contents
If an environment manually manages all materials,
provide -r to let
them request packing of those materials
without trying to generate any of the content.
2026-05-06 08:46:31 -04:00
Jarrod Johnson d6e3c7e837 Backport cert fix 2026-05-05 16:26:49 -04:00
Jarrod Johnson dcb6aeca65 Add ca-only policy
This policy forces CA validation every time.

This also checks things like date validity.
2026-05-05 14:41:02 -04:00
Jarrod Johnson 7bc76b62e6 Backport CA policy changes 2026-05-05 11:31:26 -04:00
Jarrod Johnson db313628c5 Include aarch64 names for key libraries in ubuntu diskless 2026-05-01 14:25:18 -04:00
Jarrod Johnson f260323d2f Fix missing ubuntu diskless content 2026-05-01 12:14:13 -04:00
Jarrod Johnson d60bc7f524 Bring chrony fixes to other scripts 2026-04-30 11:24:20 -04:00
Jarrod Johnson ff0d4cdadf Fix diskless profiles for chrony.conf modification 2026-04-30 11:24:15 -04:00
Timothy Middelkoop db6475c4da Fix el8/el9 hook paths corrupted by symlinked el10 in aarch64 spec
In confluent_osdeploy-aarch64.spec.tmpl, el10 was created as a symlink
to el8, so the subsequent `mv el10/initramfs/usr el10/initramfs/var`
inadvertently renamed el8's usr directory, leaving el8 and el9 (also
symlinked to el8) with hooks at var/lib/dracut/hooks/ instead of
usr/lib/dracut/hooks/. Rocky 9 dracut never found the hooks and dropped
to the emergency shell on all aarch64 nodes.

Use `cp -a el8 el10` as the x86_64 spec already does, so the rename
only affects the el10 copy.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Timothy Middelkoop <tmiddelkoop@internet2.edu>
2026-04-30 08:09:06 -04:00
Jarrod Johnson 6d27e8a009 Allow monitor to read attributes by 'all' resource. 2026-04-29 07:51:08 -04:00
Jarrod Johnson f363796439 Write to stdout as binary
This allows better redirection.

In python3, must write to sys.stdout.buffer.  AttributeError for the unlikely event of a python2 based node being deployed.
2026-04-29 07:45:49 -04:00
Jarrod Johnson dec118a985 Fix mistake in spec file 2026-04-24 09:29:31 -04:00
Jarrod Johnson 38eb0d7b10 Add Ubuntu 26.04 2026-04-24 08:35:44 -04:00
xu_ren_xian ae338daa43 Handle confluent= boot arg and IPv4 NIC autodetect
Add support for a confluent=<host> kernel argument in init-premount: configure networking, flush interfaces, autodetect the primary NIC (saved to /tmp/autodetectnic), verify TLS connectivity to the provided server, call the whoami endpoint over TLS to obtain the node name, and write results to /custom-installation/confluent/confluent.info (with fallback to copernicus on failure).

Also update casper-bottom logic to handle IPv4 manager addresses: for IPv6 the manager is still bracketed and scoped interface resolved as before; for IPv4 the script now uses the previously detected NIC (/tmp/autodetectnic) or falls back to an `ip route get <mgr>` lookup to determine DEVICE. This ensures routed IPv4 deployments work correctly.
2026-04-23 17:50:41 -04:00
Jarrod Johnson 6ad3f0d70c Fix mistakes in the node apoption samples 2026-04-20 09:46:45 -04:00
Jarrod Johnson c0b9bb3ab1 Fix group rename in collective 2026-04-17 11:57:35 -04:00
Jarrod Johnson b32755b0d3 Fix remote client operation with Python 3.12+ 2026-04-17 09:01:12 -04:00
Jarrod Johnson c54ac530e1 Handle some environments where timedatectl does not exist 2026-04-14 13:50:12 -04:00
Jarrod Johnson 8990622470 Improve certificate mismatch handling 2026-04-08 15:37:50 -04:00
Jarrod Johnson 93a35d7e77 Improve srlinux error handling 2026-04-08 15:30:43 -04:00
Jarrod Johnson c49b2fd8ab Update quorum on deletion
If deletion of a node brings quorum, notify followers
of the good news
2026-04-07 14:57:09 -04:00
Jarrod Johnson 3ce2a5bc26 More tightly constrain node profile requests
Normalize paths using abspath and validate the result is within the expected path.
2026-04-06 15:12:44 -04:00
Jarrod Johnson 69d984b9dc Fix syntax mistake in deferred handling in nodeapply 2026-04-03 10:34:20 -04:00
Jarrod Johnson a123165712 Improve error when unknown user specified in syncfiles 2026-04-02 15:29:31 -04:00
Jarrod Johnson b91b10552c EL10 doesn't do setgid keysign
chmod 600 instead
2026-03-25 12:59:40 -04:00
Jarrod Johnson 779b07d2c2 Only try to use ssh_keys if it exists
EL10 changed from using ssh_keys and setgid to just
do setuid root instead.
2026-03-25 12:56:16 -04:00
Jarrod Johnson df73c14475 Support unconfigured good without space
Some platforms try to combine the words
2026-03-19 18:05:38 -04:00
Jarrod Johnson f78b301143 Update usage text 2026-03-19 09:54:08 -04:00
Jarrod Johnson 9b00fe5521 Don't try to open a file that doesn't exist 2026-03-17 13:03:18 -04:00
Jarrod Johnson 13a6444541 Fix incorrectly matching older versions as 'el10' 2026-03-17 12:58:04 -04:00
Jarrod Johnson 52db46be93 Fix python detection from ansible with space in shebang 2026-03-13 11:41:16 -04:00
Jarrod Johnson 550dfbf6a0 Fix reference of inputdata in remoteconfig 2026-03-13 09:26:11 -04:00
Jarrod Johnson e0951b11a6 Fix filename typo 2026-03-13 08:58:58 -04:00
Jarrod Johnson 1a87701fee Fix ansible running
Have results available as they happen

change away from stdout, to avoid being stepped on by ansible modules that print to that
2026-03-09 16:48:42 -04:00
Jarrod Johnson e185f2224f Implement ability for user to kick off confluent ansible runs
Add nodeapply -A and associated API.

This permits orchestrating plays without touching the nodes directly by the user.
2026-03-06 16:24:26 -05:00
Jarrod Johnson 69beaad3c9 Induce more versions of openssh to do the proper thing 2026-02-23 15:07:19 -05:00
Jarrod Johnson 74dda48513 Provide helper script for setting up nokia switches 2026-02-23 10:15:55 -05:00
Jarrod Johnson 08b2e1d008 Wire up FDB and LLDP for srlinux 2026-02-18 16:53:12 -05:00
Jarrod Johnson 582842aec8 Add mac and lldp retrieval for SRLinux 2026-02-18 16:16:22 -05:00
Jarrod Johnson 63307c331e Have nodesensors and nodehealth be more adaptive to partial server data. 2026-02-17 16:19:41 -05:00
Jarrod Johnson 318608cde3 Add draft SRLinux support
Wire up the non-networking facets of Nokia SR Linux support.

Provide stubs for LLDP and FDB
2026-02-17 16:13:43 -05:00
Jarrod Johnson ef7d2414ad Update nodeconfig usage material 2026-02-11 11:35:09 -05:00
Jarrod Johnson 722a0b874a Add notation about certificate and nodemedia 2026-02-11 10:54:30 -05:00
Jarrod Johnson 1deb76989e Recognize 1a/2b style enclosure bay in discovery 2026-02-10 17:10:18 -05:00
Jarrod Johnson 480d399f44 Add missing switch member of info with NX switches 2026-02-09 13:17:45 -05:00
Jarrod Johnson 07369667f7 Become incompatible with pysnmp 7.1.16
The EPEL version of pysnmp is broken, block it from dependecies
2026-02-06 15:13:46 -05:00
Jarrod Johnson e1d4b72f32 Be less picky about megarac url
megarac implementations consistently indicate an .xml file, but wildly vary on what it may be.

Broaden recognition.
2026-02-05 07:57:25 -05:00
Jarrod Johnson 86783a2f12 Fix uninitialized privacy_protocol variable 2026-02-03 07:58:07 -05:00
Jarrod Johnson 99063eb049 Recognize variation in DeviceDescrption.json to see SMM3 2026-02-02 10:17:32 -05:00
Jarrod Johnson 0975bd9e62 Revert "Update some code for async"
This reverts commit 3058dd4141.
2026-01-28 15:04:49 -05:00
Jarrod Johnson 3058dd4141 Update some code for async 2026-01-28 14:49:58 -05:00
Jarrod Johnson 21c9158491 Carry forward some dns attributes into a bond 2026-01-21 15:12:23 -05:00
Jarrod Johnson e6c19388a2 Add device-manager to container build
Confluent needs device-mapper for imgutil operation
2026-01-16 08:45:12 -05:00
Jarrod Johnson 048780e16d Explicitly mknodes for pack/unpack
In some contexts, udev may be asleep
at the wheel. Explictly have dmsetup
refresh the devnodes.
2026-01-15 15:15:11 -05:00
Jarrod Johnson 61d7a49163 Revert "Fallback to filename for PE format kernels"
This reverts commit a0a5887214.
2026-01-15 14:29:31 -05:00
Jarrod Johnson f8b8ce3847 Fallback to filename for PE format kernels
Some ARM64 kernels ship as EFI executables, but it's
not obvious how to extract version numbers from those properly.
2026-01-15 14:29:23 -05:00
Jarrod Johnson a0a5887214 Fallback to filename for PE format kernels
Some ARM64 kernels ship as EFI executables, but it's
not obvious how to extract version numbers from those properly.
2026-01-15 13:27:21 -05:00
Jarrod Johnson ccaf22f44f Add architecture handling in pkglist
To handle amd64/arm64 profiles, have the pkglist allow for architecture specific qualifiers.

Additionally, soften failure to accomplish selinux changes.
2026-01-15 12:52:07 -05:00
Jarrod Johnson 72c4868073 Update container with more packages, volumes, env, and alma 10 2026-01-15 09:46:23 -05:00
Jarrod Johnson afb6356f9d Change ownership
Container runs as internal 'root' user for now
2026-01-14 16:29:31 -05:00
Jarrod Johnson 6e6ac67b3d Provide some build assets
Provide some dockerfiles for creating build containers
2026-01-13 13:57:37 -05:00
Jarrod Johnson 99d10896e8 Fix parameter count unpack for accelerated switch interrogation 2026-01-08 17:07:39 -05:00
Jarrod Johnson 488f23e3ed Fix spelling of rpmbuild 2026-01-06 15:55:36 -05:00
Jarrod Johnson 6ca62cbb35 Provide optional output directory 2026-01-06 15:54:46 -05:00
Jarrod Johnson 45bc9788b4 Correct mistake in SPECS spelling 2026-01-06 15:51:40 -05:00
Jarrod Johnson 289c31e7ac Ensure in expected directory to start 2026-01-06 15:51:06 -05:00
Jarrod Johnson 1a684f2012 Ensure rpmbuild directory exists before building 2026-01-06 15:49:50 -05:00
Jarrod Johnson a4229fc58d Change name to index in apiclient
confignet was using the index for ipv4
2025-12-12 11:18:33 -05:00
Jarrod Johnson 31c1a865dc Update confignet to match apiclient changes 2025-12-12 09:30:56 -05:00
Jarrod Johnson ff84fcf6e9 Merge branch '3.14' 2025-12-11 13:21:33 -05:00
Jarrod Johnson 56dfb6dc6b Fix spelling issue in man page 2025-12-11 08:46:59 -05:00
Jarrod Johnson d7577a04a7 Fix ESXi compatibility of apiclient
apiclient was using Linux specific network  information.

Change to libc getifaddrs for better cross-platform compatibility.
2025-12-11 08:46:19 -05:00
Jarrod Johnson b72d6c9cfc Fix typo 2025-12-10 14:14:14 -05:00
Jarrod Johnson 523c93dfc3 Tolerate more network circumstances in bluefield deploy
If the networking didn't come up well, the 'functions' routines would not be able to handle.

Switch to using apiclient which is designed specifically to handle less cooperative
initial network conditions.
2025-12-09 08:49:27 -05:00
Jarrod Johnson c8745292bf Handle broader memory information being returned from confluent 2025-12-04 13:28:45 -05:00
Jarrod Johnson 75e7b9040b Add more imgutil documentation 2025-12-04 13:27:48 -05:00
Jarrod Johnson 04e983a2d3 Handle broader memory information being returned from confluent 2025-12-04 09:52:15 -05:00
Jarrod Johnson 2464e0ff4f Fix location of the apiclient common resource 2025-12-02 14:35:50 -05:00
Jarrod Johnson c196bf9d55 Fix initial startup of a new confluent
The indexes change failed on a brand new install.
2025-12-02 14:31:10 -05:00
Jarrod Johnson 12d886a4f6 Add more imgutil documentation 2025-11-25 13:19:03 -05:00
Jarrod Johnson 6a26ece782 Merge remote-tracking branch 'xcat/master' 2025-11-25 11:59:43 -05:00
Jarrod Johnson 3cbac38d57 Also autoconsole when exactly one serial port is detected at all. 2025-11-25 11:53:50 -05:00
Jarrod Johnson 224f349053 Extend autocons to more use cases
If SPCR comes up blank, see if there is one and exactly one serial with carrier detect

Failing that, give DMI a chance to indicate a preference, for now just SuperMicro, since they have the most
inconsistent carrier detect behavior
but almost always consider ttyS1 to be the answer.
2025-11-25 11:51:07 -05:00
Jarrod Johnson 9d361d376d Merge pull request #203 from Obihoernchen/bond_desc
Add bond alias to team description
2025-11-21 09:47:09 -05:00
Markus Hilger ec39de3df0 Add bond alias to team description 2025-11-21 14:16:07 +01:00
Jarrod Johnson a3b768c70f Draft bluefield deploymeent facilities 2025-11-20 16:44:24 -05:00
Jarrod Johnson 4f75d4942b Modify adoption process:
Restore useinsecureprotocols if set directly on node

Switch from pxe-style to identity-file based node api token for hardened node authentication
2025-11-20 16:05:22 -05:00
Jarrod Johnson 4d2f36917c Restore useinsecureprotocols after adopt 2025-11-20 15:49:51 -05:00
Jarrod Johnson a2a50d34d1 Merge remote-tracking branch 'xcat' 2025-11-19 15:38:01 -05:00
Jarrod Johnson 041008a524 Remove redundant el10 initramfs fixup 2025-11-19 15:37:29 -05:00
Jarrod Johnson 5923feaa18 Merge pull request #202 from Obihoernchen/custom
Add documentation for custom nodeattribs
2025-11-19 07:47:46 -05:00
Jarrod Johnson 73216fc062 Fix architecture name mismatch
Confluent went with aarch64 consistent
with EL naming, but Ubuntu used
debian naming, recognize and just
handle that.
2025-11-18 09:10:30 -05:00
Jarrod Johnson 100944490c Fix potentially uninitialized curridx 2025-11-17 15:07:17 -05:00
Jarrod Johnson 61b07e0af4 Start index at 1 instead of 0 2025-11-17 12:05:03 -05:00
Jarrod Johnson 53760ab5dd Attribute feature enhancement
Add expression functions upper, lower, block_number, and block_offset.

Add an 'id.index' auto-attribute to
yield a number for nodes.
2025-11-17 11:58:04 -05:00
Jarrod Johnson d3e7a49f92 Simplify by recursion
Use _handle_ast_node to process
everything before the function name in an Attribute call
2025-11-15 10:32:11 -05:00
Jarrod Johnson 1f688ead28 Implement .replace() for attribute expressions
Provide an easy to use replace() to allow removing or substiting values
during expression evaluation.
2025-11-14 17:20:06 -05:00
Jarrod Johnson d20c5ac6eb Move handling of the loop directio straight to onboot
There were difficulties in the devfs after
boot, just let the full system handle it.
2025-11-13 15:33:04 -05:00
Jarrod Johnson 4484216198 Fix issues with the tethered memory optimizations 2025-11-13 15:24:26 -05:00
Jarrod Johnson e1efd6a9c5 Implement new 'uncompressed' image method
This allows the FS to just live, uncompressed, in cache.

This is generally a bad idea, however:

- In a hypothetically super-tuned diskless image, the lack of double-cache can offset the lack of compression
- The image will have supreme read performance
- It will have the most deterministic memory behavior
2025-11-13 14:39:53 -05:00
Jarrod Johnson 58d5209595 Port tethered improvments to EL8 2025-11-13 14:35:18 -05:00
Jarrod Johnson 53c918042a Remove double-caching in tethered diskless
By default, the squashfs file was being cached as well as the contents after extraction.

This is superfluous pressure on the cache of the OS.

However, it does help keep the image afloat through 'confignet', so
leave it on until onboot completes, then reclaim cache and disable further caching.
2025-11-13 14:28:25 -05:00
Markus Hilger 9148a841b5 Add documentation for custom nodeattribs 2025-11-13 00:45:53 +01:00
Jarrod Johnson 6ebb6de107 Allow specifiying SNMP privacy protocol
Modern SNMP devices may require AES.

Unfortunately, older ones may refuse AES.

For compatibility, continue to default to DES, but
allow AES to be indicated in attributes.
2025-11-10 10:21:01 -05:00
Jarrod Johnson d2d77ab1d5 Do not let diskless.conf persist into EL9 diskless images
It fouls run of kdump building the kdump image.
2025-11-07 13:23:44 -05:00
Jarrod Johnson 20292cdfd0 Do not let diskless.conf persist into EL9 diskless images
It fouls run of kdump building the kdump image.
2025-11-07 13:22:21 -05:00
Jarrod Johnson b07da455c2 Fix SAN generation
The nameconstraint support missed
a branch, fix this.
2025-11-07 11:22:12 -05:00
Jarrod Johnson cc9a81103b Do not autosign if the corresponding cryptography is unavailable
We use cryptography verification, but it's relatively new.

For compatibility, we fall back to fingerprint only.

This is pretty bad when inflicted on
unsuspecting users on autosign,
so skip autosign if cert validation
would break.
2025-11-04 15:51:22 -05:00
Jarrod Johnson 21155d2091 Bring untethered changes to el10 diskless 2025-11-04 11:17:28 -05:00
Jarrod Johnson 6c0d7ea60e Simplify end untethered el9 diskless environment
Rather than treat both as the same, since untethered has everything up front anyway, go ahead and extract the filesystem.

This makes the mount look more straightforward and makes it so deletion of files from
the image also frees ram.
2025-11-04 11:14:52 -05:00
Jarrod Johnson 174d204607 Implement compatibility with newer pysnmp
For now, terminate the async nature
if newer pysnmp is detected.
2025-11-04 09:58:11 -05:00
Jarrod Johnson 2826abb7ab Prune excessive leftover ext config files 2025-11-03 14:21:36 -05:00
Jarrod Johnson 5adb5fa780 Automatically sign XCC certificates on discover
If an XCC doesn't have a 'real' certificate, sign it with the confluent
CA for 47 days.
2025-11-03 14:02:33 -05:00
Jarrod Johnson 5de063212f Prepare for supporting constrained CA
If asked to sign using a name constrained CA,
avoid generating a certificate that
would violate those constraints.
2025-11-03 10:43:34 -05:00
Jarrod Johnson 073f6d1389 Wire up cert signing to nodecertutil 2025-10-31 12:04:27 -04:00
Jarrod Johnson f755ba9f91 Implement method to sign BMC certificates 2025-10-31 10:46:42 -04:00
Jarrod Johnson cf8c01ef13 Merge remote-tracking branch 'lenovo' 2025-10-31 09:48:05 -04:00
Jarrod Johnson ce8531375a Update to handle newer XCC2 firmware 2025-10-31 09:46:23 -04:00
Jarrod Johnson 8b12047ae0 Update to handle newer XCC2 firmware 2025-10-31 09:45:59 -04:00
Jarrod Johnson f0a779764d Fix ordering of digest argument
The digest argument was erroneously inserted between startdate and it's
argument, correct this mistake.
2025-10-28 15:39:04 -04:00
Jarrod Johnson 0ad7e99efe Only optionally use cryptography verification
Some supported distributions can't run the newer cryptography.

Make it a feature that only works with newer platforms.
2025-10-27 08:38:14 -04:00
Jarrod Johnson 24a76612ae Use sha284 hash algorithm
Some implementations reject sha256 as inadequate if ecdsa has 384 bit keylength. Bring the digest up to match
the key size for the ECDSA.
2025-10-27 06:41:05 -04:00
Jarrod Johnson 6c9c58f464 Update certutil to prepare for broader usage
For one, apply more rules from CA/B forum. This includes including KU and EKU extensions, marking basicConstraints critical, and
randomized serial numbers.

Also make the backdate and end date configurable, to allow
for the BMC certs to have a more palatable validity interval.
2025-10-26 14:57:26 -04:00
Jarrod Johnson 3125f4171b Begin overhaul of TLS cert management
Begin expanding certutil to sign other certificates from external CSRs more easily.

Have certutil make the CA constraint critical.

Have the fingerprint based validator have a mechanism to check for properly signed certificate in lieu of exact match,
and update the stored fingerprint
on match.

Provide a means to request a custom subject when evaluating a
target.

Change redfish plugin to set that subject in the verifier.
2025-10-24 20:02:51 -04:00
Jarrod Johnson 762adb882a Track client address on checkin
When doing DHCP deployment in particular, it's good to track what the actual ip was.
2025-10-21 13:04:30 -04:00
Jarrod Johnson 36687069aa Fix ESXi8 deployment
The changes for getinstalldisk assumed functionality
in ESXi9.  Target older
functional level for our purposes.

Also expand the fallback to cover cases where the disk interrogation fails.
2025-10-21 11:11:52 -04:00
Jarrod Johnson 11ff2dabfc Clean up kickstart networking
Try to apply hostname through localcli, since
hostname is unsupported through net if dhcp.

Also more affirimatively indicate dhcp.
2025-10-17 10:00:38 -04:00
Jarrod Johnson f9351484a4 Add fallback if getinstalldisk detects no preferred disks 2025-10-17 09:32:33 -04:00
Jarrod Johnson b22c17208a Stop preferring HWE for now
The HWE has some missing hardware support, ironically...
2025-10-16 18:30:46 -04:00
Jarrod Johnson 4982ac1a17 Bump genesis version 2025-10-15 16:51:21 -04:00
Jarrod Johnson a43d7e11e2 Implement an esxi getinstalldisk 2025-10-15 10:43:36 -04:00
Jarrod Johnson c5896c056e Add facility to manage BMC CA certs
For redfish at least, we can manage
some BMC CA certificate trust stores.
2025-10-14 14:30:27 -04:00
Jarrod Johnson a73dced80b Fix configbmc on XCC3
IPv4 based configbmc is now fixed for multi-nic XCC3 configurations.
2025-10-14 13:19:28 -04:00
Jarrod Johnson b6188683b8 Redirect 'xcc' to 'xcc3' for bmcconfig -c 2025-10-14 10:22:15 -04:00
Jarrod Johnson 50243b67d5 Add a more helpful error when bmc not set
When doing a configbmc, need to present
a more reasonable message about lack of address.
2025-10-08 14:20:44 -04:00
Jarrod Johnson 7cdfcd4913 Implement support for multi-manager XCC managed systems 2025-10-08 09:42:17 -04:00
Jarrod Johnson 179ad4e196 Fix IPv6 redfish config fetch for select targets
Not all targets offer up static gateway
2025-10-08 09:39:17 -04:00
Jarrod Johnson be2ae57a38 Skip the '[' when evaluating partial ESC for terminator 2025-10-06 17:34:17 -04:00
Jarrod Johnson f34395648e Add vlan_id management to redfish and ipmi plugins 2025-10-06 17:28:55 -04:00
Jarrod Johnson 3f5d96788e Fix handling of split SGR payloads
Surprisingly frequently, the firmware stacks split right after the \x1b byte in
sending data down. Defer a dangling partial sequence until more data
comes in that should make it complete.
2025-10-06 13:12:43 -04:00
Jarrod Johnson 17866d7657 Change to only force intense if bg == fg
In the interest of interfering with terminal behavior as little as possible,
only apply the forced intensity if the background and foreground color are
identical and would make it otherwise literally impossible to read
when working as designed.
2025-10-06 12:22:21 -04:00
Jarrod Johnson a1144fd49a Auto-intense color
Terminals seem to expect 'bold or intensity' to imply intense color.

There are certain terminals that steadfastly refuse to do bold and intense. So implement the logic on behalf of
the remote terminal.

Commonly, UEFI setup menus request bold white text on white background. This fixes such menus to be readable by explicitly requesting intense white foreground rather than normal background. For example, the kitty terminal has no 'intense on bold feature.
2025-10-06 10:48:35 -04:00
Jarrod Johnson c472d96406 Add '-r' to nodedeploy
This allows a shorthand to request a redeploy of whatever the most appropriate profile is.
2025-10-06 08:24:17 -04:00
Jarrod Johnson 02791418bc Support attribute expansion in filenames
For nodemedia, nodelicense, and nodefirmware, support
for expressions in filenames was
fouled when pass by
filehandle was added.

Restore this by adding all the files matching an expression.
2025-10-02 15:45:48 -04:00
Jarrod Johnson 2d29813320 Store device for future use in ubuntu deployment 2025-10-02 14:28:46 -04:00
Jarrod Johnson a9d15de156 Rework Ubuntu identity image DHCP bringup
The stock Ubuntu approach was inadequate.  It would DHCP out every nic and take the fastest result, and no going back.

Now the CDC nic can frequently win that race.

First, rmmod cdc_ether, as a scenario that is completely right out.

But beyond that, let Ubuntu have one shot at multi-nic bringup.  Beyond that, maintain a list of all link-up devices.

If the check should fail, then start doing one nic at a time, cycling through them.

Also, the openssl s_client timeout is painfully slow, use subshell and kill to speed up things.
2025-10-02 10:55:43 -04:00
Jarrod Johnson a4ba92a2e7 Retry network bringup
ESXi may be slow in being ready for network bringup. Workaround
by retrying.
2025-10-01 13:08:17 -04:00
Jarrod Johnson 6938bba2d3 Have confignet pause until connectivity restored
If we are reconfiguring network for a diskless node, wait for
things to settle back in before continuing.
2025-09-26 13:42:29 -04:00
Jarrod Johnson 871685ea20 Correct missing closure of if 2025-09-25 15:49:25 -04:00
Jarrod Johnson a480cc73df Add connectivity check to esxi ident bringup
If using the identity image bringup
with dhcp, be more careful about waiting
for connectivity before proceeding.
2025-09-25 15:29:33 -04:00
Jarrod Johnson 39eb32df38 Test connection on net cfg apply
When network configuration is applied, wait until we
can reach the deployment server again before exiting.

This should make us more robust against various potential delays after
changing the nature of network interfaces.
2025-09-25 15:18:18 -04:00
Jarrod Johnson 3505fe36e6 Remove hiddenmenu
This no longer applies to most grub2
2025-09-25 14:07:27 -04:00
Jarrod Johnson 29accaa494 Change grub to not prompt
Sometimes grub can get stuck unexpectedly waiting for interaction. Try to get away from this by default
by setting the timeout to 0.
2025-09-25 10:09:25 -04:00
Jarrod Johnson f66093680b Attempt to loop on reconfiguring networking
This may induce DHCP to be retried
2025-09-25 10:08:05 -04:00
Jarrod Johnson 97d4015b09 Handle memory inventory without type indicated 2025-09-22 15:21:53 -04:00
Jarrod Johnson 184132c398 Fix collective manager candidates not in nodelist
For switch operations, need to carry over the same logic as other evaluations.
2025-09-19 19:41:54 -04:00
Jarrod Johnson ac7fdb3ef7 Enhance message for enclosure based discovery
If nodes are accidentally omitted, but present, provide a hint that may clarify
the situation.
2025-09-19 15:46:18 -04:00
Jarrod Johnson d7879bad5b Improve robustness of Ubuntu net bringup
If using DHCP, have the loop to validate connectivity repeat.
2025-09-19 15:44:55 -04:00
Jarrod Johnson 8911193aca Implement a test with retry for basic communication
confuesbox is likely to be a very early utility, and the relevant network is at high risk of being merely 'partially' up.
2025-09-19 11:50:12 -04:00
Jarrod Johnson e7e8daafea Merge pull request #198 from henglikuang/logdirectory
An effort was made at one point to have log directory configurable, but no way was actually done
to make it accessible.

This corrects that.
2025-09-18 15:19:07 -04:00
Jarrod Johnson 3f9a13ed6f Ensure certfile is blanked before writing to it 2025-09-18 15:14:39 -04:00
Jarrod Johnson 500cdf7535 Change boot.img to boot.iso for Windows
Some things expect an iso to be named as such. This drives different handling, but
there's little choice in the matter.
2025-09-18 08:55:09 -04:00
Jarrod Johnson 22c8921455 Place identity files loose in directory as well
Some OS deployment mechanism may wish to convey the identity information more loosely. For those, it's convenient if the files are loose instead
of needing extraction from a VFAT image.
2025-09-17 09:25:40 -04:00
Jarrod Johnson ebcf7d7bf8 Refresh genesis build version 2025-09-15 11:21:03 -04:00
Jarrod Johnson 7a2cb80f6a Make hmac import optional
Some environments do not have this module
2025-09-12 16:57:37 -04:00
Jarrod Johnson dd2b7be2ca Bump genesis version 2025-09-12 16:15:00 -04:00
Jarrod Johnson 678bd6052a Correct path to util-linux in genesis build 2025-09-12 15:59:48 -04:00
Jarrod Johnson cb5fcf077a Fix incorrect character in release filename 2025-09-12 08:50:32 -04:00
Jarrod Johnson 5f26fb73e6 Enable apiclient to be more self-sufficient
Provide a totally 'clortho' and 'copernicus' free behavior.

This allows some flows to skip the cpio addons to go straight to python.

Some scenarios demand the utilities (initramfs) and others are more awkward with the utilities,
so we enable both.
2025-09-09 16:47:44 -04:00
Jarrod Johnson c9ca199b16 Fix preference of netplan
If netplan and nmcli both exist, the intent was to prefer netplan.

However, there was a mistake that caused nmcli to be the most preferred.
2025-09-09 11:17:26 -04:00
Jarrod Johnson 8109adaabf Add BFB recognition to osimage parsing
Recognize BFB embedded OS as a potential osdeploy target.

This is toward the end of identifying the appropriate 'addons.cpio' for setting up for a bf.cfg driven bfb install.

For now, it is disabled until companion os category exists.
2025-09-04 15:23:03 -04:00
Jarrod Johnson 29c6ce230f Tolerate updateboot failure on first import 2025-09-04 10:21:01 -04:00
Jarrod Johnson 87a6891eff Include boot filename in ARM case
ARM PXE solutions often fail to properly implement
PXE, workaround by going ahead and including the boot filename.
2025-09-03 09:09:21 -04:00
Jarrod Johnson a112297e60 Detect ESXi editions for more specific fingerprinting 2025-09-02 10:19:41 -04:00
Jarrod Johnson c567bfbd17 Add sysctl tune check to selfcheck
Apart frem the gc_thresh indirect check, perform other checks.

For now, just highlight that tcp_sack being disabled can really
mess with BMC connections.  Since the management node may have high speed and the BMC may be behind a 100MBit link, SACK
is needed to overcome the massive loss and
induce TCP to rate limit appropriately.
2025-09-02 08:53:55 -04:00
Jarrod Johnson 6d2146f252 Provide more category based firmware query
Some platforms can have a very slow category,
like disks. Give CLI a way to ask for the desired
categories and a chance to optimize away the uninteresting.
2025-08-29 17:12:36 -04:00
Jarrod Johnson 5045b46014 Switch to ISO based boot for windows
Windows boot loader can be easily confused by a plurality
of vfat volumes, coddle it by giving it an ISO image for now.
2025-08-28 15:14:02 -04:00
Jarrod Johnson 5905510a32 Move tmp script execution out of /tmp
Some environments want noexec on /tmp, this will work in such environments.
2025-08-28 08:34:07 -04:00
Jarrod Johnson f321f56109 Make more windows content executable
Other files use the executable
bit as an indication of whether to run or not.
2025-08-28 08:08:30 -04:00
Jarrod Johnson 9defc47474 Give pycdlib a duped filehandle
Attempts to share the filehandle resulted in race conditions around closing,
dedicate a dupe filehandle to pycdlib to avoid the conflict.
2025-08-27 12:29:19 -04:00
Jarrod Johnson 595b628e08 Validate that the agent socket actually works
If agent is 'kill -9', then recover
from that by reaping the now dead socket.
2025-08-26 14:00:36 -04:00
Jarrod Johnson 710b24e9f5 Recover from dead ssh agent
If the ssh-agent is gone, for whatever reason, restart it.
2025-08-26 11:10:43 -04:00
Jarrod Johnson c26fba74e7 Fix issues with EL10 installtodisk 2025-08-26 09:52:21 -04:00
Jarrod Johnson a01eb64adc Remove disused function from confluent2ansible 2025-08-26 09:50:25 -04:00
Jarrod Johnson ac8179b867 Amend swraid example script 2025-08-26 09:49:28 -04:00
Jarrod Johnson 87990c72c3 Make EL10 diskless consistent with EL9 2025-08-26 09:06:28 -04:00
Jarrod Johnson a6a57e8590 Fix ssh operation during install in installtodisk for el9 2025-08-26 08:59:10 -04:00
Jarrod Johnson 6be98c7e60 Fix leaking ssh-agent processes in selfcheck 2025-08-26 08:44:42 -04:00
Jarrod Johnson 1a64768fca Carry forward EL* installtodisk to EL9 2025-08-26 08:08:24 -04:00
Jarrod Johnson 157641e37a Fixup imported windows media
Samba by default needs executable bit on files for them to be executable by windows.

Only give executable bits to .exe files that are PE32, mitigating the chance the executable bit could mean anything for Linux.
It could still mean something with binfmt misc hooks, but that shouldn't be done much.
2025-08-25 08:59:53 -04:00
Jarrod Johnson 63bbe53448 Address numerous issues with 'installtodisk' for el8
Add missing pre.d directory to let user know they can use such scripts

Preserve console directievs from kernelargs into installed system

Retry umount during image2disk, if processes have the filesystem busy.

Fix DNS behavior during post phase of installtodisk

Invoke confignet properly during firstboot to set up additional interfaces.

Have sshd run during the install from '/sysroot', for convenience

Fix some cosmetic error output for setupssh
2025-08-22 08:39:40 -04:00
Jarrod Johnson ec3fcee7d7 Implement updateboot for windows profiles 2025-08-20 20:15:05 -04:00
Jarrod Johnson b2b2b5710b Fix up monolithing sshd_config for platforms that need it 2025-08-20 11:26:41 -04:00
Jarrod Johnson b32ded9c6a Fix skipping of quotation in grub config 2025-08-20 10:31:59 -04:00
Jarrod Johnson 75c228dae4 Fix syntax mistake 2025-08-20 10:10:01 -04:00
Jarrod Johnson afd2b6c219 Add storage drivers to imgutil for el8 diskless 2025-08-20 10:04:13 -04:00
Jarrod Johnson 9a85b9ee94 Fixes for installtodisk for diskless for el9 2025-08-20 09:55:26 -04:00
Jarrod Johnson c9c5165245 Fix syntax error in commit 2025-08-20 08:53:55 -04:00
Jarrod Johnson d4e91b1c7e Back port installtodisk to el8 diskless 2025-08-20 08:49:11 -04:00
Jarrod Johnson 98e78dd43c Reduce size of presumed diskless image for installtodisk
A diskless image is likely to be significantly smaller, have it support install
to smaller disks.
2025-08-18 16:34:26 -04:00
Jarrod Johnson e7606e69bd Wait for redfish account service to be ready before modifying users
If an XCC is booting, it may appear before it's ready to use redfish to manage user accounts.  Handle this by delaying the discovery until
the service is ready.
2025-08-15 10:18:49 -04:00
Jarrod Johnson 580c451945 Explicitly restart sshd on completion
Some changes from setupssh may require sshd restart, perform it
if running with the sshd unit running.
2025-08-14 07:24:28 -04:00
Jarrod Johnson a71804a13b Fix suse/ubuntu paths for apache config 2025-08-12 11:47:39 -04:00
Jarrod Johnson dbda4f45a1 Add 'proxmox' to valid values for console options 2025-08-12 10:49:01 -04:00
Jarrod Johnson 5ac0cccc4d Update proxmoxve for trixie
Have the proxmox post script adaptive between Debian 12 or 13
2025-08-11 17:11:18 -04:00
Jarrod Johnson 465e985cc7 Extract and use HWE by default for Ubuntu 24.04 2025-08-11 13:55:36 -04:00
Jarrod Johnson 836b629986 Remove unused json import from apiclient 2025-08-11 12:31:44 -04:00
Jarrod Johnson 58b6a2d317 Fix python2 incompatibility with apiclient 2025-08-11 12:20:10 -04:00
Jarrod Johnson fc6c1495d3 Change comment to not have unicode
Some environments don't like the UTF
2025-08-11 12:10:33 -04:00
Jarrod Johnson c9b9275bb1 Support clear of custom attributes
middle name attributes and custom attributes should
be checked for explicitly during a clear.
2025-08-11 11:46:35 -04:00
Jarrod Johnson c0a99f63a5 Correct typo in rpm spec changes 2025-08-11 10:31:55 -04:00
Jarrod Johnson 51afcc68a7 Add debian 13 support
Debian 13 changes the installer initramfs
to have lib be under usr, compensate.
2025-08-11 10:14:05 -04:00
Jarrod Johnson 902ff43a9b Extend certificate management
Provide checks for nginx config and apache configuration, perhaps even concurrently.

Latch on the first match, since we are taking care of IP based SANs and subsequent server/virtualhost sections are irrelevant.

Latch onto a chain file, if indicated in the apache configuration, placing our CA in the chain.

For nginx, put our CA in the cert, since nginx
uses the 'certificate' file as the chain.

In this scenario, a cross-signed CA cert is possible.
2025-08-08 17:27:26 -04:00
Jarrod Johnson e01701bcf1 Speed up log initialization
Cache the directory list over a few seconds
to avoid excessive filesystem calls.

Also switchg to a more potent regex to avoid wasting time on timestamped files.
2025-08-08 15:51:45 -04:00
Jarrod Johnson a1cf8023c6 Clarify that nodesensors csv may have a different order
CSV must keep sensors grouped and consistent, so it waits until it gathers
all results to sort.

Normally, it presents the results as quickly as the API provides it.

This results in some different ordering for the "fast as API provides it"
versus "delayed to group the values together" behavior.
2025-08-08 14:30:30 -04:00
Jarrod Johnson 960a890530 Fix discovery mac deletion to also clean associated node records 2025-08-07 15:15:14 -04:00
Jarrod Johnson d43de05b09 Present error when user tries to operate undefined PDU outlets 2025-08-07 14:56:12 -04:00
Jarrod Johnson 36ce0922fc Implement openEuler scripted install
openEuler 24.03 seems closest to el8, so start with that baseline.

Modify el8 handling to recognize a totally missing CA bundle and assume
the original el8 location.

Have osimage recognize openEuler media by presence of openEuler release file.
2025-08-06 14:56:12 -04:00
Jarrod Johnson aafa65274c Do not trip over .discinfo file contents 2025-08-06 14:27:05 -04:00
Jarrod Johnson 63bb5f4d1b Have routed diskless retry checking for interface
If the network interface is slow to link up, wait
until something comes up, ignoring infiniband for now.
2025-08-06 14:15:43 -04:00
Jarrod Johnson d99689f84b Have confluent2ansible support amending inventory
Rather than writing from scratch each time, parse the
existing file if present and merge new content without
perturbing existing.
2025-08-06 11:56:44 -04:00
Hengli Kuang 816f3be2ed Configure the logdirectory from the configuration file
- Set the log directory using the configuration manager
- Add _get_logdirectory function to retrieve the log directory path
- Update _redirectoutput to use the new log directory setting
2025-08-06 04:31:38 -04:00
Jarrod Johnson bab169269c Fix variable mistake in error handling 2025-08-05 08:39:18 -04:00
Jarrod Johnson 85ddf528a2 Add Confluent UUID to identity image
The UUID and tls material usually comes from site, but some
OSes may depend entirely on the identity image, so
make the UUID available that way as well.
2025-08-01 15:53:38 -04:00
Jarrod Johnson 8cfbf40a2e Implement capture of extra volume groups
Provide a mechanism to capture non-boot hosted volume groups
and map them to specified disks via a mapping.
2025-08-01 12:02:04 -04:00
Jarrod Johnson 48a0c21300 Refine getinstalldisk
Reduce obvious output about skipped devices.

Rule out any read-only device.

Amend minimum size to 2GB.

Among same priority devices, select the smallest target.
2025-08-01 09:00:25 -04:00
Jarrod Johnson 2c43055aec Amend handling of malformed bytes in output
Additionally, fix handling of mixed numeric/string in the attribute handler.
2025-07-31 08:44:58 -04:00
Jarrod Johnson 16a1c4d598 Pull mtu into the netcfg api 2025-07-29 15:45:41 -04:00
Jarrod Johnson 97e4d7c3d0 Skip sr0 if attempted 2025-07-29 15:33:05 -04:00
Jarrod Johnson cfa16237e1 Dismantle initramfs by default in EL9/EL10 diskless
If debugssh is not requsted, the initramfs is not needed.

Do not unshare the mount namespace, leave the kernel relevant namespace 'normal'

Remove some initramfs content to free up some memory.
2025-07-29 14:26:43 -04:00
Jarrod Johnson 7066f85520 Merge remote-tracking branch 'xcat' 2025-07-29 07:03:22 -04:00
Jarrod Johnson 8c6f36adf3 Fixes for ansible running
Newer ansible requires deferred plugin initialization explicitly,
support either ansible norm.

Reliably mark a play as complete even if facing a very early init
problem, e.g. if ansible shebang points to a non-existant python.

Fix incorrect association of stderr to tasks, and present
stderr to the client exactly once.
2025-07-28 15:55:16 -04:00
Jarrod Johnson 33cee4174e Merge pull request #196 from Obihoernchen/doclink
Update documentation link
2025-07-25 15:03:49 -04:00
Jarrod Johnson 47710756a5 Implement mtu for netplan backend 2025-07-25 15:01:26 -04:00
Jarrod Johnson 21429c6d7d Implement mtu for networkmanager backend 2025-07-25 14:48:03 -04:00
Jarrod Johnson ff0c11e919 Add mtu to net attributes 2025-07-25 14:47:46 -04:00
Jarrod Johnson bf209a8009 Explicitly recognize fe80::/64 as 'local'
For various reasons, it is likely/expected for fe80:: to fail the 'local'
check. This is fine in most contexts, except for credserver and ssdp.

ssdp already special cased fe80::, so special case fe80:: in credserver
as well.
2025-07-24 16:20:12 -04:00
Jarrod Johnson 6ec072be9d Fix EL10 for ARM nodes 2025-07-22 14:25:39 -04:00
Jarrod Johnson 79e44e420a Fix console handling for vcenter and proxmox plugins 2025-07-22 09:26:50 -04:00
Markus Hilger 5a0b2468f6 Update documentation link 2025-07-11 03:08:50 +02:00
Jarrod Johnson 13d9fe2712 Revert useinsecureprotocols to any group value after adoption 2025-07-02 09:55:24 -04:00
Jarrod Johnson 5028ed9f07 Avoid set changed during iteration 2025-07-01 15:45:24 -04:00
Jarrod Johnson 05dbbd6ce0 Explicitly check root user keys
Replace simple existence check
with a check that assures the content also matches.
2025-06-25 16:10:26 -04:00
Jarrod Johnson 61749c3649 Support older Pillow libraries
Some distributions bundle older pillow, and we have
a way to support them.
2025-06-25 13:47:29 -04:00
Jarrod Johnson 1f3b84cc9d Implement Windows iso extraction
If a user has custom
windows categories, match
them to media import.

To do this, we needed to go to pycdlib, as libarchive can't
do UDF-only isos.

For now, this has no progress indication, but does extract it similar
to most Linux distributions are done.
2025-06-24 16:32:31 -04:00
Jarrod Johnson ac42c1b4c7 Skip IPv6 lookups on IPv4 context
For DHCPv4 context, it makes no sense to bother with IPv6 addresses that cannot be used.
2025-06-24 13:40:40 -04:00
Jarrod Johnson e489d2d532 Fix setupssh behavior on nearly full /tmp/ 2025-06-18 08:55:13 -04:00
184 changed files with 5568 additions and 1009 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ If you're coming from xCAT, check out [this comparison](https://hpc.lenovo.com/u
# Documentation
Confluent documentation is hosted on hpc.lenovo.com: https://hpc.lenovo.com/users/documentation/
Confluent documentation is hosted on: https://xcat2.github.io/confluent-docs/
# Download
+8
View File
@@ -0,0 +1,8 @@
FROM almalinux:10
RUN ["yum", "-y","update"]
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-XML-LibXML", "pinentry-tty", "rpm-sign", "git", "golang"]
ADD rpmmacro /root/.rpmmacros
ADD buildpackages.sh /bin/
#VOLUME ["/rpms", "/srpms"]
CMD ["/bin/bash","/bin/buildpackages.sh"]
+6
View File
@@ -0,0 +1,6 @@
for package in /srpms/*; do
rpmbuild --rebuild $package
done
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
+3
View File
@@ -0,0 +1,3 @@
%_gpg_digest_algo sha256
%_gpg_name Lenovo Scalable Infrastructure
+8
View File
@@ -0,0 +1,8 @@
FROM almalinux:8
RUN ["yum", "-y","update"]
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-Net-DNS", "perl-DB_File", "perl-XML-LibXML", "rpm-sign", "git", "fuse-devel","libcurl-devel"]
ADD rpmmacro /root/.rpmmacros
ADD buildpackages.sh /bin/
#VOLUME ["/rpms", "/srpms"]
CMD ["/bin/bash","/bin/buildpackages.sh"]
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
for package in /srpms/*; do
rpmbuild --rebuild $package
done
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
+2
View File
@@ -0,0 +1,2 @@
%_gpg_digest_algo sha256
%_gpg_name Lenovo Scalable Infrastructure
+10
View File
@@ -0,0 +1,10 @@
FROM almalinux:9
RUN ["yum", "-y","update"]
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-Net-DNS", "perl-DB_File", "perl-XML-LibXML", "pinentry-tty", "rpm-sign", "epel-release", "git"]
RUN ["crb", "enable"]
RUN ["yum", "-y","install","fuse-devel","libcurl-devel"]
ADD rpmmacro /root/.rpmmacros
ADD buildpackages.sh /bin/
#VOLUME ["/rpms", "/srpms"]
CMD ["/bin/bash","/bin/buildpackages.sh"]
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
for package in /srpms/*; do
rpmbuild --rebuild $package
done
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
+2
View File
@@ -0,0 +1,2 @@
%_gpg_digest_algo sha256
%_gpg_name Lenovo Scalable Infrastructure
+12
View File
@@ -0,0 +1,12 @@
FROM ubuntu:noble
ADD stdeb.patch /tmp/
ADD buildapt.sh /bin/
ADD distributions.tmpl /bin/
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "reprepro", "python3-stdeb", "gnupg-agent", "devscripts", "debhelper", "libsoap-lite-perl", "libdbi-perl", "quilt", "git", "python3-pyparsing", "python3-dnspython", "python3-eventlet", "python3-netifaces", "python3-paramiko", "dh-python", "libjson-perl", "ronn", "alien", "gcc", "make"]
RUN ["mkdir", "-p", "/sources/git/"]
RUN ["mkdir", "-p", "/debs/"]
RUN ["mkdir", "-p", "/apt/"]
RUN ["bash", "-c", "patch -p1 < /tmp/stdeb.patch"]
CMD ["/bin/bash", "/bin/buildapt.sh"]
+21
View File
@@ -0,0 +1,21 @@
#cp -a /sources/git /tmp
for builder in $(find /sources/git -name builddeb); do
cd $(dirname $builder)
./builddeb /debs/
done
cp /prebuilt/* /debs/
cp /osd/*.deb /debs/
mkdir -p /apt/conf/
CODENAME=$(grep VERSION_CODENAME= /etc/os-release | sed -e 's/.*=//')
if [ -z "$CODENAME" ]; then
CODENAME=$(grep VERSION= /etc/os-release | sed -e 's/.*(//' -e 's/).*//')
fi
if ! grep $CODENAME /apt/conf/distributions; then
sed -e s/#CODENAME#/$CODENAME/ /bin/distributions.tmpl >> /apt/conf/distributions
fi
cd /apt/
reprepro includedeb $CODENAME /debs/*.deb
for dsc in /debs/*.dsc; do
reprepro includedsc $CODENAME $dsc
done
+7
View File
@@ -0,0 +1,7 @@
Origin: Lenovo HPC Packages
Label: Lenovo HPC Packages
Codename: #CODENAME#
Architectures: amd64 source
Components: main
Description: Lenovo HPC Packages
+34
View File
@@ -0,0 +1,34 @@
diff -urN t/usr/lib/python3/dist-packages/stdeb/cli_runner.py t.patch/usr/lib/python3/dist-packages/stdeb/cli_runner.py
--- t/usr/lib/python3/dist-packages/stdeb/cli_runner.py 2024-06-11 18:30:13.930328999 +0000
+++ t.patch/usr/lib/python3/dist-packages/stdeb/cli_runner.py 2024-06-11 18:32:05.392731405 +0000
@@ -8,7 +8,7 @@
from ConfigParser import SafeConfigParser # noqa: F401
except ImportError:
# python 3.x
- from configparser import SafeConfigParser # noqa: F401
+ from configparser import ConfigParser # noqa: F401
from distutils.util import strtobool
from distutils.fancy_getopt import FancyGetopt, translate_longopt
from stdeb.util import stdeb_cmdline_opts, stdeb_cmd_bool_opts
diff -urN t/usr/lib/python3/dist-packages/stdeb/util.py t.patch/usr/lib/python3/dist-packages/stdeb/util.py
--- t/usr/lib/python3/dist-packages/stdeb/util.py 2024-06-11 18:32:53.864776149 +0000
+++ t.patch/usr/lib/python3/dist-packages/stdeb/util.py 2024-06-11 18:33:02.063952870 +0000
@@ -730,7 +730,7 @@
example.
"""
- cfg = ConfigParser.SafeConfigParser()
+ cfg = ConfigParser.ConfigParser()
cfg.read(cfg_files)
if cfg.has_section(module_name):
section_items = cfg.items(module_name)
@@ -801,7 +801,7 @@
if len(cfg_files):
check_cfg_files(cfg_files, module_name)
- cfg = ConfigParser.SafeConfigParser(cfg_defaults)
+ cfg = ConfigParser.ConfigParser(cfg_defaults)
for cfg_file in cfg_files:
with codecs.open(cfg_file, mode='r', encoding='utf-8') as fd:
cfg.readfp(fd)
+9
View File
@@ -0,0 +1,9 @@
cd ~/confluent
git pull
rm ~/rpmbuild/RPMS/noarch/*osdeploy*
rm ~/rpmbuild/SRPMS/*osdeploy*
sh confluent_osdeploy/buildrpm-aarch64
mkdir -p $HOME/el9/
mkdir -p $HOME/el10/
podman run --rm -it -v $HOME:/build el9build bash /build/confluent/confluent_vtbufferd/buildrpm /build/el9/
+221 -8
View File
@@ -45,6 +45,7 @@ import math
import getpass
import optparse
import os
import re
import select
import shlex
import signal
@@ -132,6 +133,8 @@ def print_help():
def updatestatus(stateinfo={}):
global powerstate, powertime, clearpowermessage
if opts.headless:
return
status = consolename
info = []
for statekey in stateinfo:
@@ -454,8 +457,10 @@ def do_command(command, server):
currconsole = targpath
startrequest = {'operation': 'start', 'path': targpath,
'parameters': {}}
height, width = struct.unpack(
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
height, width = 31, 100
if not opts.headless:
height, width = struct.unpack(
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
startrequest['parameters']['width'] = width
startrequest['parameters']['height'] = height
for param in argv[2:]:
@@ -623,17 +628,19 @@ def startconsole(nodename):
signal.signal(signal.SIGWINCH, do_resize)
didconsole = True
consolename = nodename
tty.setraw(sys.stdin.fileno())
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
if not opts.headless:
tty.setraw(sys.stdin.fileno())
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
inconsole = True
check_automation('') # give any leading 'sends' a chance
def quitconfetty(code=0, fullexit=False, fixterm=True):
global inconsole
global currconsole
global didconsole
if fixterm or didconsole:
if (fixterm or didconsole) and not opts.headless:
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl & ~os.O_NONBLOCK)
if oldtcattr is not None:
@@ -843,6 +850,20 @@ def check_escape_seq(currinput, filehandle):
currinput += filehandle.read()
return currinput
automation_directives = []
current_automation_directive = None
automation_map = {
'<up>': '\x1b[A',
'<down>': '\x1b[B',
'<right>': '\x1b[C',
'<left>': '\x1b[D',
'<enter>': '\r',
'<esc>': '\x1b',
'<tab>': '\t',
}
parser = optparse.OptionParser()
parser.add_option("-s", "--server", dest="netserver",
help="Confluent instance to connect to",
@@ -850,12 +871,64 @@ parser.add_option("-s", "--server", dest="netserver",
parser.add_option("-c", "--control", dest="controlpath",
help="Path to offer terminal control",
metavar="PATH")
parser.add_option('-a', '--automation', type='string', default=None,
help='Specify an automation script to run', metavar='SCRIPT')
parser.add_option('-e', '--headless', action='store_true', default=False,
help='Run in headless mode, which is designed for use with '
'automation scripts and disables interactive features')
parser.add_option(
'-m', '--mintime', default=0,
help='Minimum time to run or else pause for input (used to keep a '
'terminal from closing quickly on error)')
opts, shellargs = parser.parse_args()
def parse_automation_script(script, session_node):
global current_automation_directive
for line in script.splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
if line.startswith('exit'):
automation_directives.append(('exit', None))
continue
if ' ' not in line:
sys.stderr.write("Invalid line in automation script: %s\n" % line)
continue
directive, arg = line.split(' ', 1)
directive = directive.strip().lower()
if directive not in ('expect', 'send', 'forget'):
sys.stderr.write("Unknown directive in automation script: %s\n" % directive)
continue
arg = arg.strip()
origarg = arg
if arg[0] not in ('"', "'"):
arg = '"' + arg + '"'
if arg[0] == "'" and arg[-1] == "'":
# do not process '<>' sequences in single quotes
arg = arg[1:-1]
arg = bytes(arg, "utf-8").decode("unicode_escape")
elif arg[0] == '"' and arg[-1] == '"':
arg = bytes(arg[1:-1], "utf-8").decode("unicode_escape")
for key, value in automation_map.items():
arg = arg.replace(key, value)
arg = re.sub(r'<env:(\w+)>', lambda m: os.environ[m.group(1)], arg)
if '{' in arg: # support confluent expressions
for res in session.create('/nodes/{0}/attributes/expression'.format(session_node),
{'expression': arg}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
sys.exit(1)
if 'value' in res:
arg = res['value']
automation_directives.append((directive, arg, origarg))
if automation_directives:
current_automation_directive = automation_directives.pop(0)
if opts.headless and current_automation_directive[0] == 'expect':
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\r\n')
sys.stdout.flush()
username = None
passphrase = None
def server_connect():
@@ -908,6 +981,7 @@ def main():
# sys.stdout.write('\x1b[H\x1b[J')
# sys.stdout.flush()
global powerstate, powertime, clearpowermessage
if sys.stdout.isatty():
@@ -924,6 +998,9 @@ def main():
targ, session_node = get_session_node(shellargs)
if session_node is not None:
consoleonly = True
if opts.automation:
with open(opts.automation) as f:
parse_automation_script(f.read(), session_node)
do_command("start %s" % targ, netserver)
doexit = True
elif shellargs:
@@ -935,9 +1012,13 @@ def main():
while inconsole or not doexit:
if inconsole:
if opts.headless:
handles = [session.connection]
else:
handles = (sys.stdin, session.connection)
try:
rdylist, _, _ = select.select(
(sys.stdin, session.connection), (), (), 10)
handles, (), (), 10)
except select.error:
rdylist = ()
for fh in rdylist:
@@ -969,8 +1050,81 @@ def main():
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
sgr_re = re.compile(r'(\x1b\[[0-9;]*m)')
sgr_parameters_re = re.compile(r'\x1b\[([0-9;]*)m')
fgcolor = None
bgcolor = None
fgshifted = False
pendseq = ''
automation_check = ''
def check_automation(data):
global automation_check
global current_automation_directive
if type(data) != str:
data = data.decode('utf-8', errors='ignore')
while current_automation_directive:
if current_automation_directive[0] == 'forget' and data:
current_automation_directive = None
automation_check = ''
if automation_directives:
current_automation_directive = automation_directives.pop(0)
if opts.headless and current_automation_directive[0] == 'expect':
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\n')
sys.stdout.flush()
return
if current_automation_directive[0] == 'expect':
expected = current_automation_directive[1]
combined = automation_check + data
if expected and expected in combined:
data = data[combined.rindex(expected) + len(expected):]
if opts.headless:
sys.stdout.write(f'Detected {repr(current_automation_directive[2])}\r\n')
sys.stdout.flush()
current_automation_directive = None
automation_check = ''
if automation_directives:
current_automation_directive = automation_directives.pop(0)
if opts.headless and current_automation_directive[0] == 'expect':
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\r\n')
sys.stdout.flush()
else:
# Check if there's potential start of expected data in the incoming data
combined = automation_check + data
automation_check = ''
for i in range(1, min(len(expected), len(combined)) + 1):
if expected.startswith(combined[-i:]):
automation_check = combined[-i:]
return # wait for next check
elif current_automation_directive[0] == 'send':
data = ''
automation_check = ''
if opts.headless:
sys.stdout.write(f'Sending {repr(current_automation_directive[2])}\r\n')
sys.stdout.flush()
if current_automation_directive[1]:
tlvdata.send(session.connection, current_automation_directive[1])
current_automation_directive = None
if automation_directives:
current_automation_directive = automation_directives.pop(0)
if opts.headless and current_automation_directive[0] == 'expect':
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\r\n')
sys.stdout.flush()
elif current_automation_directive[0] == 'exit':
if opts.headless:
sys.stdout.write('Automation completed\r\n')
sys.stdout.flush()
return True
if opts.headless and not current_automation_directive:
sys.stdout.write('Automation completed\r\n')
sys.stdout.flush()
return True
return False
def consume_termdata(fh, bufferonly=False):
global clearpowermessage
global fgcolor, bgcolor, fgshifted, pendseq
try:
data = tlvdata.recv(fh)
except Exception:
@@ -979,7 +1133,64 @@ def consume_termdata(fh, bufferonly=False):
updatestatus(data)
return ''
if data is not None:
data = client.stringify(data)
shouldexit = check_automation(data)
if opts.headless:
if shouldexit:
quitconfetty(fullexit=True)
return ''
indata = pendseq + client.stringify(data)
pendseq = ''
data = ''
for segment in sgr_re.split(indata):
if sgr_re.match(segment): # it is an sgr, analyze, maybe replace
params = []
for parameters in sgr_parameters_re.findall(segment):
for param in parameters.split(';'):
params.append(param)
if param == '0':
fgcolor = None
bgcolor = None
try:
ival = int(param)
except ValueError:
continue
if 40 <= ival <= 47 or 100 <= ival <= 107:
bgcolor = ival
if 30 <= ival <= 37 or 90 <= ival <= 97:
fgcolor = ival
if bgcolor is not None:
fgindicated = False
for idx, param in enumerate(params):
try:
ival = int(param)
except ValueError:
continue
if 30 <= ival <= 37 and (bgcolor % 10 == ival % 10):
fgindicated = True
fgshifted = True
ival += 60
params[idx] = str(ival)
if not fgindicated and fgcolor is not None:
if bgcolor and (bgcolor % 10) == (fgcolor % 10):
fgshifted = True
params.append(str((fgcolor % 10) + 90))
elif fgshifted:
params.append(str(fgcolor))
segment = '\x1b[' + ';'.join(str(p) for p in params) + 'm'
data += segment
# defer any partial ansi escape sequence for a later pass
escidx = segment.rfind('\x1b[')
if escidx >= 0:
for chr in segment[escidx + 2:]:
if 0x40 <= ord(chr) <= 0x7e:
break
else:
# incomplete escape sequence, don't print it yet
data = data[:-len(segment) + escidx]
pendseq = segment[escidx:]
if not pendseq and segment and segment[-1] == '\x1b':
data = data[:-1]
pendseq = '\x1b'
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
@@ -1006,6 +1217,8 @@ def consume_termdata(fh, bufferonly=False):
# this scenario comfortable that it
# will come out soon enough
pass
if shouldexit:
quitconfetty(fullexit=True)
else:
deadline = 5
connected = False
+42 -25
View File
@@ -1,8 +1,9 @@
#!/usr/bin/python2
#!/usr/bin/python3
import optparse
import signal
import sys
import os
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
@@ -17,18 +18,16 @@ import confluent.client as client
import confluent.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts
\n ''')
usage='''\n %prog noderange -o ansible.hosts -a
''')
argparser.add_option('-o', '--output',
help='Ansible hosts file')
help='Writes an Ansible hosts file')
argparser.add_option('-a', '--append', action='store_true',
help='Appends to existing hosts file')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -45,24 +44,42 @@ def main():
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
nodesbygroup = {}
with open(options.output, 'w') as importfile:
needempty = False
for node in sortutil.natural_sort(databynode):
data = databynode[node]
if not data.get('groups', []):
importfile.write(node + '\n')
needempty = True
for g in data.get('groups', []):
if g not in nodesbygroup:
nodesbygroup[g] = set([node])
for node in sortutil.natural_sort(databynode):
data = databynode[node]
groups = data.get('groups', [])
if not groups:
nodesbygroup.setdefault('', set()).add(node.strip().lower())
else:
for g in groups:
nodesbygroup.setdefault(g, set()).add(node.strip().lower())
existing_data = {}
if options.append and os.path.exists(options.output):
current_group = ''
with open(options.output, 'r') as f:
for line in f:
line = line.strip().lower()
if not line:
continue
if line.startswith('[') and line.endswith(']'):
current_group = line[1:-1]
existing_data.setdefault(current_group, set())
else:
nodesbygroup[g].add(node)
if needempty:
importfile.write('\n')
for group in sortutil.natural_sort(nodesbygroup):
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(nodesbygroup[group]):
existing_data.setdefault(current_group, set()).add(line)
for group, nodes in nodesbygroup.items():
nodes = {n.strip().lower() for n in nodes}
current_nodes = existing_data.get(group, set())
new_nodes = nodes - current_nodes
if new_nodes:
existing_data.setdefault(group, set()).update(nodes)
with open(options.output, 'w') as importfile:
for group in sortutil.natural_sort(existing_data.keys()):
if group:
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(existing_data[group]):
importfile.write('{0}\n'.format(node))
importfile.write('\n')
+46 -8
View File
@@ -36,6 +36,36 @@ import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run_automation(noderange, category, c):
automationbynode = {}
for res in c.update('/noderange/{0}/deployment/remote_config/run'.format(noderange), {
'category': category,
}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
if 'created' in res:
nodename = res['created'].split('/')[2]
automationbynode[nodename] = res['created']
while automationbynode:
for node in list(automationbynode):
for res in c.read(automationbynode[node]):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
for result in res.get('results', []):
sys.stdout.write('{0}: Task [{1}] {2}\n'.format(
node, result['task_name'], result['state']))
for warning in result.get('warnings', []):
sys.stderr.write('{0}: [WARNING] {1}\n'.format(node, warning))
if 'errorinfo' in result:
for errorline in result['errorinfo'].splitlines():
sys.stderr.write('{0}: [ERROR] {1}\n'.format(node, errorline))
if res.get('complete', False):
del automationbynode[node]
sys.stdout.write('{0}: Automation complete\n'.format(node))
def run():
global devnull
devnull = open(os.devnull, 'rb')
@@ -51,6 +81,8 @@ def run():
help='Run the syncfiles associated with the currently completed OS profile on the noderange')
argparser.add_option('-P', '--scripts',
help='Re-run specified scripts, with full path under scripts, e.g. post.d/first,firstboot.d/second')
argparser.add_option('-A', '--automation',
help='Run the automation scripts associated with the current OS profile on the noderange, specifying category (onboot.d/firstboot.d/post.d)')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
@@ -74,16 +106,13 @@ def run():
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
if options.automation:
run_automation(args[0], options.automation, c)
nodemap = {}
cmdparms = []
nodes = []
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
break
node = res['item']['href'][:-1]
nodes.append(node)
cmdstorun = []
if options.security:
@@ -94,8 +123,17 @@ def run():
for script in options.scripts.split(','):
cmdstorun.append(['run_remote', script])
if not cmdstorun:
if options.automation:
sys.exit(0)
argparser.print_help()
sys.exit(1)
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
break
node = res['item']['href'][:-1]
nodes.append(node)
idxbynode = {}
cmdvbase = ['bash', '/etc/confluent/functions']
for sshnode in nodes:
@@ -145,7 +183,7 @@ def run():
run_cmdv(node, cmdv, all, poller, pipedesc)
elif pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, poller. pipedesc)
run_cmdv(node, cmdv, all, poller, pipedesc)
singlepoller.close()
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/python3
import os
import sys
from cryptography import x509
from cryptography.hazmat.primitives import hashes
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
def removebmccacert(noderange, certid, cmd):
for res in cmd.delete(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities/{certid}'):
print(repr(res))
def listbmccacerts(noderange, cmd):
certids = []
for res in cmd.read(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities'):
certids.append(res.get('item', {}).get('href', ''))
for certid in certids:
for res in cmd.read(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities/{certid}'):
for node in res.get('databynode', {}):
certdata = res['databynode'][node].get('pem', {}).get('value', '')
summary = ''
if not certdata:
continue
san = res['databynode'][node].get('san', {}).get('value', '')
if san:
summary += f" SANs: {san}"
subject = res['databynode'][node].get('subject', {}).get('value', '')
if subject:
summary = subject
try:
cert = x509.load_pem_x509_certificate(certdata.encode())
sha256 = cert.fingerprint(hashes.SHA256()).hex().upper()
except Exception as e:
print(f"Error processing certificate for {node}: {e}", file=sys.stderr)
continue
summary += f" (SHA256={sha256})"
print(f"{node}: {certid}: {summary}")
def installbmccacert(noderange, certfile, cmd):
if certfile:
try:
with open(certfile, 'r') as f:
certdata = f.read()
except Exception as e:
print(f"Error reading certificate file: {e}", file=sys.stderr)
sys.exit(1)
# Simple validation: check if it starts and ends with the correct PEM markers
if not (certdata.startswith("-----BEGIN CERTIFICATE-----") and certdata.strip().endswith("-----END CERTIFICATE-----")):
print("Invalid certificate format. Must be a PEM encoded certificate.", file=sys.stderr)
sys.exit(1)
payload = {'pem': certdata}
for res in cmd.update(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities', payload):
print(repr(res))
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Node certificate utility')
parser.add_argument('noderange', help='Node range to operate on')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# installbmccacert subcommand
install_parser = subparsers.add_parser('installbmccacert', help='Install BMC CA certificate')
install_parser.add_argument('filename', help='Certificate file to install')
remove_parser = subparsers.add_parser('removebmccacert', help='Remove BMC CA certificate')
remove_parser.add_argument('id', help='Certificate id to remove')
list_parser = subparsers.add_parser('listbmccacerts', help='List BMC CA certificates')
sign_bmc_parser = subparsers.add_parser('signbmccert', help='Sign BMC certificate')
sign_bmc_parser.add_argument('--days', type=int, help='Number of days the certificate is valid for')
sign_bmc_parser.add_argument('--added-names', type=str, help='Additional names to include in the certificate')
args = parser.parse_args()
c = client.Command()
if args.command == 'installbmccacert':
installbmccacert(args.noderange, args.filename, c)
elif args.command == 'removebmccacert':
removebmccacert(args.noderange, args.id, c)
elif args.command == 'listbmccacerts':
listbmccacerts(args.noderange, c)
elif args.command == 'signbmccert':
payload = {}
if args.days is not None:
payload['days'] = args.days
else:
print("Error: --days is required for signbmccert", file=sys.stderr)
sys.exit(1)
if args.added_names:
payload['added_names'] = args.added_names
for res in c.update(f'/noderange/{args.noderange}/configuration/management_controller/certificate/sign', payload):
print(repr(res))
else:
parser.print_help()
sys.exit(1)
+10 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
# Copyright 2025 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ argparser.add_option('-e', '--extra', dest='extra',
'to be extra configuration')
argparser.add_option('-x', '--exclude', dest='exclude',
action='store_true', default=False,
help='Treat positional arguments as items to not '
help='Treat named settings as items to not '
'examine, compare, or restore default')
argparser.add_option('-a', '--advanced', dest='advanced',
action='store_true', default=False,
@@ -73,7 +73,7 @@ argparser.add_option('-r', '--restoredefault', default=False,
dest='restoredefault', metavar="COMPONENT",
help='Restore the configuration of the node '
'to factory default for given component. '
'Currently only uefi is supported')
'Currently "uefi" and "bmc" are supported components')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to configure, '
@@ -96,6 +96,12 @@ cfgpaths = {
'bmc.static_ipv6_gateway': (
'configuration/management_controller/net_interfaces/management',
'static_v6_gateway'),
'bmc.vlan_id': (
'configuration/management_controller/net_interfaces/management',
'vlan_id'),
'bmc.mac_address': (
'configuration/management_controller/net_interfaces/management',
'hw_addr'),
'bmc.hostname': (
'configuration/management_controller/hostname', 'hostname'),
}
+30 -15
View File
@@ -18,6 +18,7 @@
import base64
import optparse
import os
import shlex
import subprocess
import sys
path = os.path.dirname(os.path.realpath(__file__))
@@ -38,7 +39,7 @@ import termios
import fcntl
import confluent.screensqueeze as sq
try:
from PIL import Image, ImageDraw
from PIL import Image, ImageDraw, ImageFont
except ImportError:
Image = None
@@ -62,8 +63,13 @@ argparser = optparse.OptionParser(
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
"For example, ctrl-'e', then 'c', then '.' will exit the current "
"console")
argparser.add_option('-a', '--automation', type='string', default=None,
help='Specify an automation script')
argparser.add_option('-t', '--tile', action='store_true', default=False,
help='Tile console windows in the terminal')
argparser.add_option('-e', '--headless', action='store_true', default=False,
help='Run in headless mode, which is designed for use with '
'automation scripts and disables interactive features')
argparser.add_option('-l', '--log', action='store_true', default=False,
help='Enter log replay mode instead of showing a live console')
@@ -98,6 +104,12 @@ argparser.add_option('-w','--windowed', action='store_true', default=False,
(options, args) = argparser.parse_args()
automation_args = []
if options.automation:
automation_args = ['-a', options.automation]
if options.headless:
automation_args += ['--headless']
oldtcattr = None
oldfl = None
@@ -227,22 +239,24 @@ def draw_text(text, width, height):
nd = ImageDraw.Draw(nerr)
for txtpiece in text.split('\n'):
fntsize = 8
while nd.textlength(txtpiece, font_size=fntsize) < int(imgwidth * 0.90):
txtfont = ImageFont.truetype('DejaVuSans.ttf', size=fntsize)
while nd.textlength(txtpiece, font=txtfont) < int(imgwidth * 0.90):
fntsize += 1
txtfont = ImageFont.truetype('DejaVuSans.ttf', size=fntsize)
fntsize -= 1
if fntsize < maxfntsize:
maxfntsize = fntsize
hmargin = int(imgwidth * 0.05)
vmargin = int(imgheight * 0.10)
nd.text((hmargin, vmargin), text, font_size=maxfntsize)
nd.rectangle((0, 0, nerr.width - 1, nerr.height -1), outline='white', width=1)
nd.text((hmargin, vmargin), text, font=txtfont)
nd.rectangle((0, 0, nerr.width - 1, nerr.height -1), outline='white')
outfile = io.BytesIO()
nerr.save(outfile, format='PNG')
data = base64.b64encode(outfile.getbuffer())
draw_image(data, width, height, doscale=False)
else:
sys.stdout.write(text)
cursor_left(len(txt))
cursor_left(len(text))
def draw_image(data, width, height, doscale=True):
imageformat = os.environ.get('CONFLUENT_IMAGE_PROTOCOL', 'kitty')
@@ -270,8 +284,8 @@ def draw_image(data, width, height, doscale=True):
rzheight = imgheight
img = img.resize((rzwidth, rzheight))
nd = ImageDraw.Draw(nimg)
nd.rectangle((1, 1, rzwidth + 2, rzheight + 2), outline='black', width=1)
nd.rectangle((0, 0, rzwidth + 3, rzheight + 3), outline='white', width=1)
nd.rectangle((1, 1, rzwidth + 2, rzheight + 2), outline='black')
nd.rectangle((0, 0, rzwidth + 3, rzheight + 3), outline='white')
nimg.paste(img, box=(2, 2))
outfile = io.BytesIO()
nimg.save(outfile, format='PNG')
@@ -653,7 +667,7 @@ if options.windowed:
firstnode=nodes[0]
nodes.pop(0)
with open(os.devnull, 'wb') as devnull:
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode), '-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode) ] , stdin=devnull)
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode)] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode)] , stdin=devnull)
time.sleep(2)
s=socket.socket(socket.AF_UNIX)
winid=''
@@ -725,7 +739,7 @@ if options.windowed:
else:
pass
with open(os.devnull, 'wb') as devnull:
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node), '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node)] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
sys.exit(0)
#end of wcons
if options.tile:
@@ -750,18 +764,18 @@ if options.tile:
panename = '{0}:{1}'.format(sessname, pane)
if initial:
initial = False
confetty_cmd = [confettypath] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(node)]
subprocess.call(
['tmux', 'new-session', '-d', '-s',
sessname, '-x', '800', '-y',
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
'800', ' '.join(shlex.quote(arg) for arg in confetty_cmd)])
else:
subprocess.call(['tmux', 'select-pane', '-t', sessname])
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
confetty_cmd = [confettypath] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(node)]
subprocess.call(
['tmux', 'split', '-h', '-t', sessname,
'{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
' '.join(shlex.quote(arg) for arg in confetty_cmd)])
subprocess.call(['tmux', 'select-layout', '-t', sessname, 'tiled'], stdout=null)
pane += 1
subprocess.call(['tmux', 'select-pane', '-t', sessname])
@@ -769,5 +783,6 @@ if options.tile:
if not in_tmux:
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
else:
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
execl_args = [confettypath] + automation_args + ['start',
'/nodes/{0}/console/session'.format(args[0])]
os.execl(confettypath, *execl_args)
+64 -22
View File
@@ -48,7 +48,18 @@ def armonce(nr, cli):
pass
def setpending(nr, profile, cli):
def setpending(nr, profile, profilebynodes, cli):
if profilebynodes:
for node in sortutil.natural_sort(profilebynodes):
prof = profilebynodes[node]
args = {'deployment.pendingprofile': prof, 'deployment.state': '', 'deployment.state_detail': ''}
if not prof.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
args['deployment.profile'] = ''
for rsp in cli.update('/nodes/{0}/attributes/current'.format(node),
args):
pass
return
args = {'deployment.pendingprofile': profile, 'deployment.state': '', 'deployment.state_detail': ''}
if not profile.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
@@ -67,8 +78,10 @@ def main(args):
ap = argparse.ArgumentParser(description='Deploy OS to nodes')
ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true')
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
ap.add_argument('-b', '--bootmethod', help='Specify network boot method (e.g., network, http)', default='network')
ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true')
ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
ap.add_argument('-r', '--redeploy', help='Redeploy nodes with the current or pending profile', action='store_true')
ap.add_argument('noderange', help='Set of nodes to deploy')
ap.add_argument('profile', nargs='?', help='Profile name to deploy')
args, extra = ap.parse_known_args(args)
@@ -78,7 +91,7 @@ def main(args):
if args.profile and not args.network:
sys.stderr.write('-n is a required argument currently to perform an install, optionally with -p\n')
return 1
if not args.profile and args.network:
if not args.profile and args.network and not args.redeploy:
sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\n')
return 1
if args.clear and args.profile:
@@ -96,27 +109,38 @@ def main(args):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
sys.exit(1)
profilebynode = {}
if args.clear:
cleararm(args.noderange, c)
clearpending(args.noderange, c)
elif args.profile:
profnames = []
for prof in c.read('/deployment/profiles/'):
profname = prof.get('item', {}).get('href', None)
if profname:
profname = profname.replace('/', '')
profnames.append(profname)
if profname == args.profile:
break
else:
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
if profnames:
sys.stderr.write('The following profiles are available:\n')
for profname in profnames:
sys.stderr.write(' ' + profname + '\n')
else:
sys.stderr.write('No deployment profiles available, try osdeploy import or imgutil capture\n')
sys.exit(1)
elif args.redeploy:
hadpending = {}
for rsp in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
for node in rsp.get('databynode', {}):
nodeinfo = rsp['databynode'][node]
for attr in nodeinfo:
if attr == 'deployment.pendingprofile':
curr = nodeinfo[attr].get('value', '')
if curr:
hadpending[node] = True
profilebynode[node] = curr
if attr == 'deployment.stagedprofile':
curr = nodeinfo[attr].get('value', '')
if curr and node not in hadpending:
profilebynode[node] = curr
if attr == 'deployment.profile':
curr = nodeinfo[attr].get('value', '')
if curr and node not in profilebynode:
profilebynode[node] = curr
for lockinfo in c.read('/noderange/{0}/deployment/lock'.format(args.noderange)):
for node in lockinfo.get('databynode', {}):
lockstate = lockinfo['databynode'][node]['lock']['value']
if lockstate == 'locked':
lockednodes.append(node)
if args.profile and profilebynode:
sys.stderr.write('The -r/--redeploy option cannot be used with a profile, it redeploys the current or pending profile\n')
return 1
if args.profile or profilebynode:
lockednodes = []
for lockinfo in c.read('/noderange/{0}/deployment/lock'.format(args.noderange)):
for node in lockinfo.get('databynode', {}):
@@ -127,8 +151,26 @@ def main(args):
sys.stderr.write('Requested noderange has nodes with locked deployment: ' + ','.join(lockednodes))
sys.stderr.write('\n')
sys.exit(1)
if args.profile:
profnames = []
for prof in c.read('/deployment/profiles/'):
profname = prof.get('item', {}).get('href', None)
if profname:
profname = profname.replace('/', '')
profnames.append(profname)
if profname == args.profile:
break
else:
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
if profnames:
sys.stderr.write('The following profiles are available:\n')
for profname in profnames:
sys.stderr.write(' ' + profname + '\n')
else:
sys.stderr.write('No deployment profiles available, try osdeploy import or imgutil capture\n')
sys.exit(1)
armonce(args.noderange, c)
setpending(args.noderange, args.profile, c)
setpending(args.noderange, args.profile, profilebynode, c)
else:
databynode = {}
for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
@@ -172,7 +214,7 @@ def main(args):
print('{0}: {1}{2}'.format(node, profile, armed))
sys.exit(0)
if not args.clear and args.network and not args.prepareonly:
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', args.bootmethod,
bootmode='uefi',
persistent=False,
errnodes=errnodes)
+31 -8
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -59,6 +59,8 @@ argparser = optparse.OptionParser(
"%prog <noderange> [list][updatestatus][update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
argparser.add_option('-p', '--parameterfile', type='string',
help='When updating, use the specified parameter file')
argparser.add_option('-m', '--maxnodes', type='int',
help='When updating, prompt if more than the specified '
'number of servers will be affected')
@@ -112,13 +114,30 @@ def update_firmware(session, filename):
upargs = {'filename': filename}
if options.backup:
upargs['bank'] = 'backup'
if options.parameterfile:
with open(options.parameterfile, 'rb') as pf:
pfdata = pf.read()
upargs['parameterdata'] = pfdata
noderrs = {}
if session.unixdomain:
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
filesbynode = {}
for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': filename}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
filesbynode[node] = ex[node]['value']
if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
filesbynode[node] = filesbynode[node].encode('utf-8')
for node in filesbynode:
endfilename = filesbynode[node]
of = open(endfilename, 'rb')
try:
session.add_file(endfilename, of.fileno(), 'rb')
except Exception:
pass
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
@@ -153,9 +172,13 @@ def show_firmware(session):
firmware_shown = False
nodes_matched = False
for component in components:
category = 'all'
if component in ('adapters', 'disks', 'misc', 'core'):
category = component
component = 'all'
for res in session.read(
'/noderange/{0}/inventory/firmware/all/{1}'.format(
noderange, component)):
'/noderange/{0}/inventory/firmware/{2}/{1}'.format(
noderange, component, category)):
nodes_matched = True
exitcode |= client.printerror(res)
if 'databynode' not in res:
+4 -4
View File
@@ -84,13 +84,13 @@ def main():
healthexplanations[node] = []
for sensor in health[node]['sensors']:
explanation = sensor['name'] + ':'
if sensor['value'] is not None:
if sensor.get('value', None) is not None:
explanation += str(sensor['value'])
if sensor['units'] is not None:
if sensor.get('units', None) is not None:
explanation += sensor['units']
if sensor['states']:
if sensor.get('states', None):
explanation += ','
if sensor['states']:
if sensor.get('states', None):
explanation += ','.join(sensor['states'])
healthexplanations[node].append(explanation)
if node in healthbynode and node in healthexplanations:
+10 -3
View File
@@ -49,7 +49,9 @@ def pretty(text):
def print_mem_info(node, prefix, meminfo):
memdescfmt = '{0}GB PC'
if meminfo['memory_type'] == 'DDR3 SDRAM':
if meminfo['memory_type'] is None:
memdescfmt = '{0}GB '
elif meminfo['memory_type'] == 'DDR3 SDRAM':
memdescfmt += '3-{1} '
elif 'DDR4' in meminfo['memory_type']:
memdescfmt += '4-{1} '
@@ -58,16 +60,21 @@ def print_mem_info(node, prefix, meminfo):
elif 'DCPMM' in meminfo['memory_type']:
memdescfmt = '{0}GB {1} '
meminfo['module_type'] = 'DCPMM'
elif meminfo['memory_type'] == 'HBM':
memdescfmt = '{0}GB HBM '
else:
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
return
if meminfo.get('ecc', False):
memdescfmt += 'ECC '
capacity = meminfo['capacity_mb'] / 1024
modtype = meminfo.get('module_type', None)
if modtype:
memdescfmt += modtype
memdesc = memdescfmt.format(capacity, meminfo['speed'])
if meminfo.get('capacity_mb', None):
capacity = meminfo['capacity_mb'] // 1024
memdesc = memdescfmt.format(capacity, meminfo['speed'])
else:
memdesc = 'Unspecified Module'
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
print('{0}: {1} manufacturer: {2}'.format(
node, prefix, meminfo['manufacturer']))
+20 -6
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
@@ -65,16 +65,30 @@ client.check_globbing(noderange)
def install_license(session, filename):
global exitcode
resource = '/noderange/{0}/configuration/' \
'management_controller/licenses/'.format(noderange)
filename = os.path.abspath(filename)
instargs = {'filename': filename}
if session.unixdomain:
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
filesbynode = {}
for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': filename}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
filesbynode[node] = ex[node]['value']
if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
filesbynode[node] = filesbynode[node].encode('utf-8')
for node in filesbynode:
endfilename = filesbynode[node]
of = open(endfilename, 'rb')
try:
session.add_file(endfilename, of.fileno(), 'rb')
except Exception:
pass
for res in session.create(resource, instargs):
for node in res.get('databynode', []):
if 'error' in res['databynode'][node]:
+25 -10
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
@@ -101,22 +101,37 @@ def detach_media(noderange, media):
def upload_media(noderange, media):
global exitcode
if not os.path.exists(media):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
media))
sys.exit(404)
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
filename = os.path.abspath(media)
resource = '/noderange/{0}/media/uploads/'.format(noderange)
filename = os.path.abspath(filename)
upargs = {'filename': filename}
noderrs = {}
if session.unixdomain:
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
filesbynode = {}
for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': filename}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
filesbynode[node] = ex[node]['value']
if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
filesbynode[node] = filesbynode[node].encode('utf-8')
for node in filesbynode:
endfilename = filesbynode[node]
if not os.path.exists(endfilename):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
endfilename))
sys.exit(404)
of = open(endfilename, 'rb')
try:
session.add_file(endfilename, of.fileno(), 'rb')
except Exception:
pass
nodeurls = {}
for res in session.create(resource, upargs):
if 'created' not in res:
+5 -5
View File
@@ -123,7 +123,7 @@ def sensorpass(showout=True, appendtime=False):
if 'sensors' not in reading[node]:
continue
for sensedata in reading[node]['sensors']:
if sensedata['value'] is None and options.skipnumberless:
if sensedata.get('value', None) is None and options.skipnumberless:
continue
for redundant_state in ('Non-Critical', 'Critical'):
try:
@@ -134,17 +134,17 @@ def sensorpass(showout=True, appendtime=False):
resultdata[node][sensedata['name']] = sensedata
sensorname = sensedata['name']
sensorheaders[sensorname] = sensorname
if sensedata['units'] not in (None, u''):
if sensedata.get('units', None) not in (None, u''):
sensorheaders[sensorname] += u' ({0})'.format(
sensedata['units'])
if showout:
if sensedata['value'] is None:
if sensedata.get('value', None) is None:
showval = ''
elif isinstance(sensedata['value'], float):
showval = u' {0} '.format(floatformat(sensedata['value']))
else:
showval = u' {0} '.format(sensedata['value'])
if sensedata['units'] not in (None, u''):
showval = u' {0} '.format(sensedata.get('value', ''))
if sensedata.get('units', None) not in (None, u''):
showval += sensedata['units']
if sensedata.get('health', 'ok') != 'ok':
datadescription = [sensedata['health']]
+1 -1
View File
@@ -32,7 +32,7 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(
usage='Usage: %prog [options] <noderange> [default|cd|network|setup|hd|usb|floppy]')
usage='Usage: %prog [options] <noderange> [default|cd|network|http|setup|hd|usb|floppy]')
argparser.add_option('-b', '--bios', dest='biosmode',
action='store_true', default=False,
help='Request BIOS style boot (rather than UEFI)')
+11 -5
View File
@@ -57,7 +57,7 @@ def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
# current python version
if isinstance(instr, bytes) and not isinstance(instr, str):
return instr.decode('utf-8')
return instr.decode('utf-8', errors='replace')
elif not isinstance(instr, bytes) and not isinstance(instr, str):
return instr.encode('utf-8')
return instr
@@ -281,6 +281,7 @@ class Command(object):
if maxnodes is None:
return
nsize = self.get_noderange_size(noderange)
maxnodes = int(maxnodes)
if nsize > maxnodes:
if nsize == 1:
nodename = list(self.read(
@@ -391,8 +392,13 @@ class Command(object):
cacert = None
certreqs = ssl.CERT_NONE
knownhosts = True
self.connection = ssl.wrap_socket(self.connection, ca_certs=cacert,
cert_reqs=certreqs)
tlsctx = ssl.create_default_context()
if certreqs == ssl.CERT_NONE:
tlsctx.check_hostname = False
tlsctx.verify_mode = certreqs
if cacert:
tlsctx.load_verify_locations(cacert)
self.connection = tlsctx.wrap_socket(self.connection, server_hostname=server)
if knownhosts:
certdata = self.connection.getpeercert(binary_form=True)
fingerprint = 'sha512$' + hashlib.sha512(certdata).hexdigest()
@@ -464,8 +470,8 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
def _sort_attrib(k):
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
return k[1]['sortid']
return k[0]
return sortutil.naturalize_string('{}'.format(k[1]['sortid']))
return sortutil.naturalize_string(k[0])
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
exitcode = 0
+139
View File
@@ -0,0 +1,139 @@
# imgutil(1) -- Work with confluent OS cloning and diskless images
## SYNOPSIS
`imgutil` `build` [<options>] <scratchdir>
`imgutil` `exec` [<options>] <scratchdir> [<cmd>...]
`imgutil` `unpack` <profilename> <scratchdir>
`imgutil` `pack` [<options>] <scratchdir> <profilename>
`imgutil` `capture` <node> <profilename>
## DESCRIPTION
**imgutil** is a utility for creating, managing, and deploying OS images for diskless boot and system cloning in a Confluent environment. It supports building images from scratch, capturing images from running systems, and packing/unpacking diskless profiles.
## COMMANDS
* `build`:
Build a new diskless image from scratch in the specified scratch directory.
* `exec`:
Start the specified scratch directory as a container and optionally run a command inside it.
* `unpack`:
Unpack a diskless image profile to a scratch directory for modification.
* `pack`:
Pack a scratch directory into a diskless profile that can be deployed.
* `capture`:
Capture an image for cloning from a running system.
## BUILD OPTIONS
* `-r`, `--addrepos` <repository>:
Repositories to add in addition to the main source. May be specified multiple times.
* `-p`, `--packagelist` <file>:
Filename of package list to replace default pkglist.
* `-a`, `--addpackagelist` <file>:
A list of additional packages to include. May be specified multiple times.
* `-s`, `--source` <directory>:
Directory to pull installation from, typically a subdirectory of `/var/lib/confluent/distributions`. By default, the repositories for the build system are used. For Ubuntu, this is not supported; the build system repositories are always used.
* `-y`, `--non-interactive`:
Avoid prompting for confirmation.
* `-v`, `--volume` <mount>:
Directory to make available in the build environment. `-v /` will cause it to be mounted in image as `/run/external/`. `-v /:/run/root` will override the target to be `/run/root`. Something like `/var/lib/repository:-` will cause it to mount to the identical path inside the image. May be specified multiple times.
* <scratchdir>:
Directory to build new diskless root in.
## EXEC OPTIONS
* `-v`, `--volume` <mount>:
Directory to make available in the build environment. `-v /` will cause it to be mounted in image as `/run/external/`. `-v /:/run/root` will override the target to be `/run/root`. May be specified multiple times.
* <scratchdir>:
Directory of an unpacked diskless root.
* <cmd>:
Optional command to run (defaults to a shell).
## UNPACK OPTIONS
* <profilename>:
The diskless OS profile to unpack.
* <scratchdir>:
Directory to extract diskless root to.
## PACK OPTIONS
* `-b`, `--baseprofile` <profile>:
Profile to copy extra info from. For example, to make a new version of an existing profile, reference the previous one as baseprofile.
* `-u`, `--unencrypted`:
Pack an unencrypted image rather than encrypting.
* <scratchdir>:
Directory containing diskless root.
* <profilename>:
The desired diskless OS profile name to pack the root into.
## CAPTURE OPTIONS
* <node>:
Node to capture image from.
* <profilename>:
Profile name for captured image.
## EXAMPLES
Build a diskless image from a distribution:
imgutil build -s alma-9.6-x86_64 /tmp/myimage
Execute a shell in an unpacked image:
imgutil exec /tmp/myimage
Execute a specific command in an image:
imgutil exec /tmp/myimage /bin/rpm -qa
Unpack an existing profile for modification:
imgutil unpack myprofile /tmp/myimage
Pack a modified image into a new profile:
imgutil pack /tmp/myimage myprofile-v2
Capture an image from a running node:
imgutil capture node01 production-image
## FILES
* `/var/lib/confluent/public/os/`:
Default location for OS profiles.
* `/var/lib/confluent/private/os/`:
Location for encrypted image keys and private data.
* `/var/lib/confluent/distributions/`:
Default location for installation sources.
## SEE ALSO
osdeploy(8)
## AUTHOR
Written for the Confluent project.
@@ -24,6 +24,8 @@ For a full list of attributes, run `nodeattrib <node> all` against a node.
If `-c` is specified, this will set the nodeattribute to a null value.
This is different from setting the value to an empty string.
Arbitrary custom attributes can also be created with the `custom.` prefix.
Attributes may be specified by wildcard, for example `net.*switch` will report
all attributes that begin with `net.` and end with `switch`.
+1 -2
View File
@@ -49,8 +49,7 @@ actually be in effect until a reboot.
* `-r COMPONENT`, `--restoredefault=COMPONENT`:
Request that the specified component of the targeted nodes will have its
configuration reset to default. Currently the only component implemented
is uefi.
configuration reset to default. The component may be "uefi" or "bmc".
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to configure, prompting if over
+4 -1
View File
@@ -35,7 +35,10 @@ the out of band facilities. Firmware updates can end in one of three states:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
When updating, prompt if more than the specified number of servers will
be affected
* `-p PARAMETERFILE`, `--paramaterfile=PARAMETERFILE`:
For updating, a parameter file to provife along with the update payload
* `-h`, `--help`:
Show help message and exit
@@ -11,7 +11,7 @@ nodegroupattrib(8) -- List or change confluent nodegroup attributes
## DESCRIPTION
`nodegroupattrip` queries the confluent server to get information about nodes.
`nodegroupattrib` queries the confluent server to get information about nodes.
In the simplest form, it simply takes the given group and lists the attributes of that group.
Contrasted with nodeattrib(8), settings managed by nodegroupattrib will be added
+7
View File
@@ -32,6 +32,13 @@ BMCs map a virtual USB device to that url. Content is loaded on demand, and
as such that URL is referenced potentially once for every IO operation that
the host platform attempts.
## NOTES
When doing an attach of an https:// url, you may hit an error if you have not enrolled your certificate authority.
In a general confluent environment, you can usually address it by:
`# for cert in /var/lib/confluent/public/site/tls/*.pem; do nodecertutil s1-s4 installbmccacert $cert; done`
## OPTIONS
* `-h`, `--help`:
+3 -1
View File
@@ -19,7 +19,9 @@ interval of 1 second is used.
## OPTIONS
* `-c`, `--csv`:
Organize output into CSV format, one sensor per column.
Organize output into CSV format, one sensor per column. Note that while normally nodesensors reports
sensors in order as returned by server, CSV output enforces consistency by sorting after receiving
the results, which may have a different ordering than non-CSV usage of nodesensors.
* `-i`, `--interval`=**SECONDS**:
Repeat data gathering waiting, waiting the specified time between samples. Unless `-n` is
@@ -0,0 +1,74 @@
#!/usr/bin/python3
import glob
import gzip
import base64
import os
import subprocess
import sys
import tempfile
def collect_certificates(tmpdir):
certdata = ''
for cacert in glob.glob(f'{tmpdir}/*.pem'):
with open(cacert, 'r') as f:
certdata += f.read()
return certdata
def embed_certificates(incfg, certdata):
if not certdata:
raise Exception('No certificates found to embed')
incfg = incfg.replace('%CONFLUENTCERTCOLL%', certdata)
return incfg
def embed_identity(incfg, identityjson):
incfg = incfg.replace('%IDENTJSON%', identityjson)
return incfg
def embed_apiclient(incfg, apiclient):
with open(apiclient, 'r') as f:
apiclientdata = f.read()
compressed = gzip.compress(apiclientdata.encode())
encoded = base64.b64encode(compressed).decode()
incfg = incfg.replace('%APICLIENTZ64%', encoded)
return incfg
def embed_data(tmpdir, outfile):
templatefile = f'{tmpdir}/bfb.cfg.template'
with open(templatefile, 'r') as f:
incfg = f.read()
certdata = collect_certificates(tmpdir)
incfg = embed_certificates(incfg, certdata)
with open(f'{tmpdir}/identity.json', 'r') as f:
identityjson = f.read()
incfg = embed_identity(incfg, identityjson)
incfg = embed_apiclient(incfg, f'{tmpdir}/../apiclient')
with open(outfile, 'w') as f:
f.write(incfg)
def get_identity_json(node):
identity_file = f'/var/lib/confluent/private/site/identity_files/{node}.json'
try:
with open(identity_file, 'r') as f:
return f.read()
except FileNotFoundError:
return None
if __name__ == '__main__':
if len(sys.argv) != 4:
print("Usage: bfb-autoinstall <node> <bfbfile> <rshim>")
sys.exit(1)
node = sys.argv[1]
bfbfile = sys.argv[2]
rshim = sys.argv[3]
os.chdir(os.path.dirname(os.path.abspath(__file__)))
currdir = os.getcwd()
tempdir = tempfile.mkdtemp(prefix=f'bfb-autoinstall-{node}-')
embed_data(f'{currdir}/{node}', f'{tempdir}/bfb.cfg')
subprocess.check_call(['bfb-install', '-b', bfbfile, '-c', f'{tempdir}/bfb.cfg', '-r', rshim])
@@ -0,0 +1,74 @@
#!/usr/bin/python3
import glob
import gzip
import base64
import os
import subprocess
import sys
import tempfile
def collect_certificates(tmpdir):
certdata = ''
for cacert in glob.glob(f'{tmpdir}/*.pem'):
with open(cacert, 'r') as f:
certdata += f.read()
return certdata
def embed_certificates(incfg, certdata):
if not certdata:
raise Exception('No certificates found to embed')
incfg = incfg.replace('%CONFLUENTCERTCOLL%', certdata)
return incfg
def embed_identity(incfg, identityjson):
incfg = incfg.replace('%IDENTJSON%', identityjson)
return incfg
def embed_apiclient(incfg, apiclient):
with open(apiclient, 'r') as f:
apiclientdata = f.read()
compressed = gzip.compress(apiclientdata.encode())
encoded = base64.b64encode(compressed).decode()
incfg = incfg.replace('%APICLIENTZ64%', encoded)
return incfg
def embed_data(tmpdir, outfile):
templatefile = f'{tmpdir}/bfb.cfg.template'
with open(templatefile, 'r') as f:
incfg = f.read()
certdata = collect_certificates(tmpdir)
incfg = embed_certificates(incfg, certdata)
with open(f'{tmpdir}/identity.json', 'r') as f:
identityjson = f.read()
incfg = embed_identity(incfg, identityjson)
incfg = embed_apiclient(incfg, f'{tmpdir}/../apiclient')
with open(outfile, 'w') as f:
f.write(incfg)
def get_identity_json(node):
identity_file = f'/var/lib/confluent/private/site/identity_files/{node}.json'
try:
with open(identity_file, 'r') as f:
return f.read()
except FileNotFoundError:
return None
if __name__ == '__main__':
if len(sys.argv) != 4:
print("Usage: bfb-autoinstall <node> <bfbfile> <rshim>")
sys.exit(1)
node = sys.argv[1]
bfbfile = sys.argv[2]
rshim = sys.argv[3]
os.chdir(os.path.dirname(os.path.abspath(__file__)))
currdir = os.getcwd()
tempdir = tempfile.mkdtemp(prefix=f'bfb-autoinstall-{node}-')
embed_data(f'{currdir}/{node}', f'{tempdir}/bfb.cfg')
subprocess.check_call(['bfb-install', '-b', bfbfile, '-c', f'{tempdir}/bfb.cfg', '-r', rshim])
@@ -0,0 +1,76 @@
function bfb_modify_os() {
echo 'ubuntu:!' | chpasswd -e
mkdir -p /mnt/opt/confluent/bin/
cat > /mnt/opt/confluent/bin/confluentbootstrap.sh << 'END_OF_EMBED'
#!/bin/bash
cat > /usr/local/share/ca-certificates/confluent.crt << 'END_OF_CERTS'
%CONFLUENTCERTCOLL%
END_OF_CERTS
update-ca-certificates
mkdir -p /opt/confluent/bin /etc/confluent/
cp /usr/local/share/ca-certificates/confluent.crt /etc/confluent/ca.pem
cat > /opt/confluent/bin/apiclient.gz.b64 << 'END_OF_CLIENT'
%APICLIENTZ64%
END_OF_CLIENT
base64 -d /opt/confluent/bin/apiclient.gz.b64 | gunzip > /opt/confluent/bin/apiclient
cat > /etc/confluent/ident.json << 'END_OF_IDENT'
%IDENTJSON%
END_OF_IDENT
python3 /opt/confluent/bin/apiclient -i /etc/confluent/ident.json /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
PROFILE=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
ROOTPASS=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg | awk '{print $2}'|grep -v null)
if [ -n "$ROOTPASS" ]; then
echo root:$ROOTPASS | chpasswd -e
echo "ubuntu:$ROOTPASS" | chpasswd -e
else
echo 'ubuntu:!' | chpasswd -e
fi
cntmp=$(mktemp -d)
cd "$cntmp" || { echo "Failed to cd to temporary directory $cntmp"; exit 1; }
touch /etc/confluent/confluent.deploycfg
python3 /opt/confluent/bin/apiclient /confluent-public/os/$PROFILE/scripts/confignet > confignet
python3 confignet
cd -
rm -rf "$cntmp"
python3 /opt/confluent/bin/apiclient /confluent-public/os/$PROFILE/scripts/functions > /etc/confluent/functions
bash /etc/confluent/functions run_remote setupssh
for cert in /etc/ssh/ssh*-cert.pub; do
if [ -s $cert ]; then
echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf
fi
done
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
touch /var/log/confluent/confluent-firstboot.log
touch /var/log/confluent/confluent-post.log
chmod 600 /var/log/confluent/confluent-post.log
chmod 600 /var/log/confluent/confluent-firstboot.log
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
bash /etc/confluent/functions run_remote_python syncfileclient
bash /etc/confluent/functions run_remote_parts post.d
bash /etc/confluent/functions run_remote_config post.d
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
bash /etc/confluent/functions run_remote_parts firstboot.d
bash /etc/confluent/functions run_remote_config firstboot.d
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged'
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: complete'
systemctl disable confluentbootstrap
rm /etc/systemd/system/confluentbootstrap.service
END_OF_EMBED
chmod +x /mnt/opt/confluent/bin/confluentbootstrap.sh
cat > /mnt/etc/systemd/system/confluentbootstrap.service << EOS
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/confluentbootstrap.sh
[Install]
WantedBy=multi-user.target
EOS
chroot /mnt systemctl enable confluentbootstrap
}
@@ -0,0 +1,125 @@
#!/usr/bin/python3
import os
import sys
import tempfile
import glob
import shutil
import shlex
import subprocess
import select
sys.path.append('/opt/lib/confluent/python')
import confluent.sortutil as sortutil
import confluent.client as client
def prep_outdir(node):
tmpdir = tempfile.mkdtemp()
for certfile in glob.glob('/var/lib/confluent/public/site/tls/*.pem'):
basename = os.path.basename(certfile)
destfile = os.path.join(tmpdir, basename)
shutil.copy2(certfile, destfile)
subprocess.check_call(shlex.split(f'confetty set /nodes/{node}/deployment/ident_image=create'))
shutil.copy2(f'/var/lib/confluent/private/identity_files/{node}.json', os.path.join(tmpdir, 'identity.json'))
return tmpdir
def exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller):
remotedir = subprocess.check_output(shlex.split(f'ssh {host} mktemp -d /tmp/bfb.XXXXXX')).decode().strip()
bfbbasename = os.path.basename(bfbfile)
subprocess.check_call(shlex.split(f'rsync -avz --info=progress2 {bfbfile} {host}:{remotedir}/{bfbbasename}'))
subprocess.check_call(shlex.split(f'rsync -avc --info=progress2 /opt/lib/confluent/osdeploy/bluefield/hostscripts/ {host}:{remotedir}/'))
for node in nodetorshim:
rshim = nodetorshim[node]
nodeoutdir = prep_outdir(node)
nodeprofile = subprocess.check_output(shlex.split(f'nodeattrib {node} deployment.pendingprofile')).decode().strip().split(':', 2)[2].strip()
shutil.copy2(f'/var/lib/confluent/public/os/{nodeprofile}/bfb.cfg.template', os.path.join(nodeoutdir, 'bfb.cfg.template'))
subprocess.check_call(shlex.split(f'rsync -avz {nodeoutdir}/ {host}:{remotedir}/{node}/'))
shutil.rmtree(nodeoutdir)
run_cmdv(node, shlex.split(f'ssh {host} sh /etc/confluent/functions confluentpython {remotedir}/bfb-autoinstall {node} {remotedir}/{bfbbasename} {rshim}'), all, poller, pipedesc)
def run_cmdv(node, cmdv, all, poller, pipedesc):
try:
nopen = subprocess.Popen(
cmdv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError as e:
if e.errno == 2:
sys.stderr.write('{0}: Unable to find local executable file "{1}"\n'.format(node, cmdv[0]))
return
raise
pipedesc[nopen.stdout.fileno()] = {'node': node, 'popen': nopen,
'type': 'stdout', 'file': nopen.stdout}
pipedesc[nopen.stderr.fileno()] = {'node': node, 'popen': nopen,
'type': 'stderr', 'file': nopen.stderr}
all.add(nopen.stdout)
poller.register(nopen.stdout, select.EPOLLIN)
all.add(nopen.stderr)
poller.register(nopen.stderr, select.EPOLLIN)
if __name__ == '__main__':
if len(sys.argv) < 3:
print(f'Usage: {sys.argv[0]} <host> <bfbfile> <node1:rshim1> [<node2:rshim2> ...]')
sys.exit(1)
host = sys.argv[1]
bfbfile = sys.argv[2]
nodetorshim = {}
for arg in sys.argv[3:]:
node, rshim = arg.split(':')
nodetorshim[node] = rshim
installprocs = {}
pipedesc = {}
all = set()
poller = select.epoll()
exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller)
rdy = poller.poll(10)
pendingexecs = []
exitcode = 0
while all:
pernodeout = {}
for r in rdy:
r = r[0]
desc = pipedesc[r]
r = desc['file']
node = desc['node']
data = True
singlepoller = select.epoll()
singlepoller.register(r, select.EPOLLIN)
while data and singlepoller.poll(0):
data = r.readline()
if data:
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = []
pernodeout[node].append(data)
else:
data = client.stringify(data)
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.flush()
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
poller.unregister(r)
r.close()
if desc['type'] == 'stdout' and pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, poller, pipedesc)
singlepoller.close()
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy = poller.poll(10)
@@ -3,6 +3,7 @@ try:
import http.client as client
except ImportError:
import httplib as client
import base64
import ctypes
import ctypes.util
import glob
@@ -15,7 +16,13 @@ import sys
import struct
import time
import re
import json
import hashlib
try:
import json
import hmac
except ImportError:
json = None
hmac = None
class InvalidApiKey(Exception):
pass
@@ -40,40 +47,115 @@ c_crypt.restype = ctypes.c_char_p
def get_my_addresses():
nlhdrsz = struct.calcsize('IHHII')
ifaddrsz = struct.calcsize('BBBBI')
# RTM_GETADDR = 22
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
nlhdr = struct.pack('IHHII', nlhdrsz + ifaddrsz, 22, 0x301, 0, 0)
# ifaddrmsg struct: u8 family, u8 prefixlen, u8 flags, u8 scope, u32 index
ifaddrmsg = struct.pack('BBBBI', 0, 0, 0, 0, 0)
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
s.bind((0, 0))
s.sendall(nlhdr + ifaddrmsg)
addrs = []
while True:
pdata = s.recv(65536)
v = memoryview(pdata)
if struct.unpack('H', v[4:6])[0] == 3: # netlink done message
break
while len(v):
length, typ = struct.unpack('IH', v[:6])
if typ == 20:
fam, plen, _, scope, ridx = struct.unpack('BBBBI', v[nlhdrsz:nlhdrsz+ifaddrsz])
if scope in (253, 0):
rta = v[nlhdrsz+ifaddrsz:length]
while len(rta):
rtalen, rtatyp = struct.unpack('HH', rta[:4])
if rtalen < 4:
break
if rtatyp == 1:
addrs.append((fam, rta[4:rtalen], plen, ridx))
rta = rta[msg_align(rtalen):]
v = v[msg_align(length):]
for ifa in get_ifaddrs():
if ifa[0] == 'ip':
addrs.append((ifa[1], ifa[2], ifa[3]))
return addrs
def get_mac_addresses():
macs = []
for ifa in get_ifaddrs():
if ifa[0] == 'ETHER':
macs.append((ifa[1], ifa[2]))
return macs
def scan_confluents():
def get_ifaddrs():
class sockaddr(ctypes.Structure):
_fields_ = [
('sa_family', ctypes.c_uint16),
('sa_data', ctypes.c_ubyte * 14),
]
class sockaddr_in(ctypes.Structure):
_fields_ = [
('sin_family', ctypes.c_uint16),
('sin_port', ctypes.c_uint16),
('sin_addr', ctypes.c_ubyte * 4),
('sin_zero', ctypes.c_ubyte * 8),
]
class sockaddr_in6(ctypes.Structure):
_fields_ = [
('sin6_family', ctypes.c_uint16),
('sin6_port', ctypes.c_uint16),
('sin6_flowinfo', ctypes.c_uint32),
('sin6_addr', ctypes.c_ubyte * 16),
('sin6_scope_id', ctypes.c_uint32),
]
class sockaddr_ll(ctypes.Structure):
_fields_ = [
('sll_family', ctypes.c_uint16),
('sll_protocol', ctypes.c_uint16),
('sll_ifindex', ctypes.c_int32),
('sll_hatype', ctypes.c_uint16),
('sll_pkttype', ctypes.c_uint8),
('sll_halen', ctypes.c_uint8),
('sll_addr', ctypes.c_ubyte * 8),
]
class ifaddrs(ctypes.Structure):
pass
ifaddrs._fields_ = [
('ifa_next', ctypes.POINTER(ifaddrs)),
('ifa_name', ctypes.c_char_p),
('ifa_flags', ctypes.c_uint),
('ifa_addr', ctypes.POINTER(sockaddr)),
('ifa_netmask', ctypes.POINTER(sockaddr)),
('ifa_ifu', ctypes.POINTER(sockaddr)),
('ifa_data', ctypes.c_void_p),
]
libc = ctypes.CDLL(ctypes.util.find_library('c'))
libc.getifaddrs.argtypes = [ctypes.POINTER(ctypes.POINTER(ifaddrs))]
libc.getifaddrs.restype = ctypes.c_int
libc.freeifaddrs.argtypes = [ctypes.POINTER(ifaddrs)]
libc.freeifaddrs.restype = None
ifap = ctypes.POINTER(ifaddrs)()
result = libc.getifaddrs(ctypes.pointer(ifap))
if result != 0:
return []
addresses = []
ifa = ifap
try:
while ifa:
if ifa.contents.ifa_addr:
family = ifa.contents.ifa_addr.contents.sa_family
name = ifa.contents.ifa_name.decode('utf-8') if ifa.contents.ifa_name else None
if family in (socket.AF_INET, socket.AF_INET6):
# skip loopback and non-multicast interfaces
if ifa.contents.ifa_flags & 8 or not ifa.contents.ifa_flags & 0x1000:
ifa = ifa.contents.ifa_next
continue
if family == socket.AF_INET:
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_in))
addr_bytes = bytes(addr_ptr.contents.sin_addr)
if_index = socket.if_nametoindex(name) if name else 0
addresses.append(('ip', family, addr_bytes, if_index))
elif family == socket.AF_INET6:
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_in6))
addr_bytes = bytes(addr_ptr.contents.sin6_addr)
scope_id = addr_ptr.contents.sin6_scope_id
addresses.append(('ip', family, addr_bytes, scope_id))
elif family == socket.AF_PACKET:
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_ll))
halen = addr_ptr.contents.sll_halen
if addr_ptr.contents.sll_hatype in (1, 32) and halen > 0: # ARPHRD_ETHER or ARPHRD_INFINIBAND
if addr_ptr.contents.sll_hatype == 1 and addr_ptr.contents.sll_addr[0] & 2: # skip locally administered MACs
ifa = ifa.contents.ifa_next
continue
mac_bytes = bytes(addr_ptr.contents.sll_addr[:halen])
macaddr = ':'.join('{:02x}'.format(b) for b in mac_bytes)
addresses.append(('ETHER', name, macaddr))
ifa = ifa.contents.ifa_next
finally:
libc.freeifaddrs(ifap)
return addresses
def scan_confluents(confuuid=None):
srvs = {}
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
@@ -85,21 +167,24 @@ def scan_confluents():
s4.bind(('0.0.0.0', 1900))
doneidxs = set([])
msg = 'M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:'
with open('/etc/confluent/confluent.deploycfg') as dcfg:
for line in dcfg.read().split('\n'):
if line.startswith('confluent_uuid:'):
confluentuuid = line.split(': ')[1]
msg += '/confluentuuid=' + confluentuuid
break
if not confuuid and os.path.exists('/etc/confluent/confluent.deploycfg'):
with open('/etc/confluent/confluent.deploycfg') as dcfg:
for line in dcfg.read().split('\n'):
if line.startswith('confluent_uuid:'):
confluentuuid = line.split(': ')[1]
msg += '/confluentuuid=' + confluentuuid
break
if not confuuid and os.path.exists('/confluent_uuid'):
with open('/confluent_uuid') as cuuidin:
confluentuuid = cuuidin.read().strip()
msg += '/confluentuuid=' + confluentuuid
try:
with open('/sys/devices/virtual/dmi/id/product_uuid') as uuidin:
msg += '/uuid=' + uuidin.read().strip()
except Exception:
pass
for addrf in glob.glob('/sys/class/net/*/address'):
with open(addrf) as addrin:
hwaddr = addrin.read().strip()
msg += '/mac=' + hwaddr
for iface, hwaddr in get_mac_addresses():
msg += '/mac=' + hwaddr
msg = msg.encode('utf8')
for addr in get_my_addresses():
if addr[0] == socket.AF_INET6:
@@ -127,6 +212,7 @@ def scan_confluents():
srvlist = []
if r:
r = r[0]
nodename = None
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
@@ -134,6 +220,7 @@ def scan_confluents():
current = None
for line in rsp:
if line.startswith(b'NODENAME: '):
nodename = line.replace(b'NODENAME: ', b'').strip().decode('utf8')
current = {}
elif line.startswith(b'DEFAULTNET: 1'):
current['isdefault'] = True
@@ -145,20 +232,37 @@ def scan_confluents():
if currip.startswith('fe80::') and '%' not in currip:
currip = '{0}%{1}'.format(currip, peer[-1])
srvs[currip] = current
srvlist.append(currip)
if currip not in srvlist:
srvlist.append(currip)
r = select.select((s4, s6), (), (), 2)
if r:
r = r[0]
if not os.path.exists('/etc/confluent/confluent.info'):
with open('/etc/confluent/confluent.info', 'w+') as cinfo:
if nodename:
cinfo.write('NODENAME: {0}\n'.format(nodename))
for srv in srvlist:
cinfo.write('MANAGER: {0}\n'.format(srv))
return srvlist, srvs
def get_net_apikey(nodename, mgr):
def get_net_apikey(nodename, mgr, hmackey=None, confuuid=None):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./'
newpass = ''.join([alpha[x >> 2] for x in bytearray(os.urandom(32))])
salt = '$5$' + ''.join([alpha[x >> 2] for x in bytearray(os.urandom(8))])
newpass = newpass.encode('utf8')
salt = salt.encode('utf8')
crypted = c_crypt(newpass, salt)
if hmackey:
hmacvalue = hmac.new(hmackey.encode('utf8'), crypted, hashlib.sha256).digest()
hmacvalue = base64.b64encode(hmacvalue).decode('utf8')
client = HTTPSClient(host=mgr, phmac=hmacvalue, nodename=nodename, confuuid=confuuid)
try:
status, rsp = client.grab_url_with_status('/confluent-api/self/registerapikey', data=crypted, returnrsp=True)
if status == 200:
return newpass.decode('utf8')
except Exception:
pass
for addrinfo in socket.getaddrinfo(mgr, 13001, 0, socket.SOCK_STREAM):
try:
clisock = socket.socket(addrinfo[0], addrinfo[1])
@@ -196,7 +300,7 @@ def get_net_apikey(nodename, mgr):
return ''
def get_apikey(nodename, hosts, errout=None):
def get_apikey(nodename, hosts, errout=None, hmackey=None, confuuid=None):
apikey = ""
if os.path.exists('/etc/confluent/confluent.apikey'):
apikey = open('/etc/confluent/confluent.apikey').read().strip()
@@ -205,16 +309,16 @@ def get_apikey(nodename, hosts, errout=None):
while not apikey:
for host in hosts:
try:
apikey = get_net_apikey(nodename, host)
apikey = get_net_apikey(nodename, host, hmackey=hmackey, confuuid=confuuid)
except OSError:
apikey = None
if apikey:
break
else:
srvlist, _ = scan_confluents()
srvlist, _ = scan_confluents(confuuid=confuuid)
for host in srvlist:
try:
apikey = get_net_apikey(nodename, host)
apikey = get_net_apikey(nodename, host, hmackey=hmackey, confuuid=confuuid)
except OSError:
apikey = None
if apikey:
@@ -232,35 +336,43 @@ def get_apikey(nodename, hosts, errout=None):
return apikey
class HTTPSClient(client.HTTPConnection, object):
def __init__(self, usejson=False, port=443, host=None, errout=None, phmac=None, checkonly=False):
def __init__(self, usejson=False, port=443, host=None, errout=None, phmac=None, checkonly=False, hmackey=None, nodename=None, confuuid=None):
self.ignorehosts = set([])
self.phmac = phmac
self.hmackey = hmackey
self.confuuid = confuuid
self.errout = None
self.stdheaders = {}
if nodename:
self.stdheaders['CONFLUENT_NODENAME'] = nodename
if errout:
self.errout = open(errout, 'w')
self.errout.flush()
self.stdheaders = {}
mgtiface = None
if usejson:
self.stdheaders['ACCEPT'] = 'application/json'
if host:
self.hosts = [host]
with open('/etc/confluent/confluent.info') as cinfo:
info = cinfo.read().split('\n')
for line in info:
if line.startswith('NODENAME:'):
node = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = node
if not nodename:
with open('/etc/confluent/confluent.info') as cinfo:
info = cinfo.read().split('\n')
for line in info:
if line.startswith('NODENAME:'):
nodename = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = nodename
else:
self.hosts = []
info = open('/etc/confluent/confluent.info').read().split('\n')
try:
info = open('/etc/confluent/confluent.info').read().split('\n')
except Exception:
info = []
havedefault = '0'
plainhost = ''
for line in info:
host = ''
if line.startswith('NODENAME:'):
node = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = node
nodename = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = nodename
if line.startswith('MANAGER:') and not host:
host = line.split(' ')[1]
self.hosts.append(host)
@@ -295,15 +407,14 @@ class HTTPSClient(client.HTTPConnection, object):
if plainhost and not self.hosts:
self.hosts.append(plainhost)
if self.phmac:
with open(phmac, 'r') as hmacin:
self.stdheaders['CONFLUENT_CRYPTHMAC'] = hmacin.read()
self.stdheaders['CONFLUENT_CRYPTHMAC'] = self.phmac
elif not checkonly:
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, self.hosts, errout=self.errout)
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(nodename, self.hosts, errout=self.errout, hmackey=hmackey, confuuid=self.confuuid)
if mgtiface:
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
self.port = port
self.host = None
self.node = node
self.node = nodename
host = self.check_connections()
client.HTTPConnection.__init__(self, host, port)
self.connect()
@@ -343,7 +454,7 @@ class HTTPSClient(client.HTTPConnection, object):
continue
break
if not foundsrv:
srvlist, srvs = scan_confluents()
srvlist, srvs = scan_confluents(self.confuuid)
hosts = []
for srv in srvlist:
if srvs[srv].get('isdefault', False):
@@ -417,7 +528,7 @@ class HTTPSClient(client.HTTPConnection, object):
with open('/etc/confluent/confluent.apikey', 'w+') as akfile:
akfile.write('')
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(
self.node, [self.host], errout=self.errout)
self.node, [self.host], errout=self.errout, hmackey=self.hmackey, confuuid=self.confuuid)
if rsp.status == 503: # confluent is down, but the server running confluent is otherwise up
authed = False
self.ignorehosts.add(self.host)
@@ -464,7 +575,7 @@ def get_available_nics():
parts = re.split(r'\s{2,}', line.strip())
if len(parts) >= 5:
nic_name = parts[0]
nic_status = parts[4] # "Link Status" este al 5-lea câmp
nic_status = parts[4] # "Link Status" is the 5th field
available_nics[nic_name] = nic_status
return available_nics
@@ -509,7 +620,7 @@ if __name__ == '__main__':
try:
fix_vswitch()
except Exception as e:
print(f"fix_vswitch() error: {e}")
print("fix_vswitch() error: {}".format(e))
sys.argv.remove('-f')
sys.exit(0)
usejson = False
@@ -546,8 +657,24 @@ if __name__ == '__main__':
phmac = sys.argv.index('-p')
sys.argv.pop(phmac)
phmac = sys.argv.pop(phmac)
with open(phmac, 'r') as hmacin:
phmac = hmacin.read()
except ValueError:
phmac = None
try:
identfile = sys.argv.index('-i')
sys.argv.pop(identfile)
identfile = sys.argv.pop(identfile)
with open(identfile) as idin:
data = idin.read()
identinfo = json.loads(data)
nodename = identinfo.get('nodename', None)
hmackey = identinfo.get('apitoken', None)
confuuid = identinfo.get('confluent_uuid', None)
except ValueError:
hmackey = None
nodename = None
confuuid = None
try:
checkonly = False
idxit = sys.argv.index('-c')
@@ -559,7 +686,7 @@ if __name__ == '__main__':
data = open(sys.argv[-1]).read()
if outbin:
with open(outbin, 'ab+') as outf:
reader = HTTPSClient(usejson=usejson, errout=errout).grab_url(
reader = HTTPSClient(usejson=usejson, errout=errout, hmackey=hmackey, nodename=nodename, confuuid=confuuid).grab_url(
sys.argv[1], data, returnrsp=True)
chunk = reader.read(16384)
while chunk:
@@ -567,7 +694,7 @@ if __name__ == '__main__':
chunk = reader.read(16384)
sys.exit(0)
mclient = HTTPSClient(usejson, errout=errout, phmac=phmac, checkonly=checkonly)
mclient = HTTPSClient(usejson, errout=errout, phmac=phmac, checkonly=checkonly, hmackey=hmackey, nodename=nodename, confuuid=confuuid)
if waitfor:
status = 201
while status != waitfor:
@@ -576,4 +703,7 @@ if __name__ == '__main__':
elif checkonly:
sys.stdout.write(mclient.check_connections())
else:
sys.stdout.write(mclient.grab_url(sys.argv[1], data).decode())
try:
sys.stdout.buffer.write(mclient.grab_url(sys.argv[1], data))
except AttributeError:
sys.stdout.write(mclient.grab_url(sys.argv[1], data))
@@ -10,11 +10,13 @@
# serial port is not reporting DCD, then it doesn't look like a comfortable enough scenario
import fcntl
import glob
import os
import os.path
import struct
import subprocess
import termios
import platform
addrtoname = {
@@ -74,9 +76,8 @@ def fixup_ubuntu_grub_serial():
grubout.write(grubline + '\n')
subprocess.check_call(['update-grub'])
def get_serial_config():
if not os.path.exists('/sys/firmware/efi'):
return None
def get_spcr_config():
if not os.path.exists('/sys/firmware/acpi/tables/SPCR'):
return None
spcr = open("/sys/firmware/acpi/tables/SPCR", "rb")
@@ -99,14 +100,39 @@ def get_serial_config():
currattr = termios.tcgetattr(ttyf)
currattr[4:6] = [0, termiobaud[retval['speed']]]
termios.tcsetattr(ttyf, termios.TCSANOW, currattr)
retval['connected'] = bool(struct.unpack('<I', fcntl.ioctl(
ttyf, termios.TIOCMGET, '\x00\x00\x00\x00'))[0] & termios.TIOCM_CAR)
os.close(ttyf)
return retval
def get_serial_config():
if platform.machine() != 'x86_64':
return None # Trust non-x86 to do the right thing
if not os.path.exists('/sys/firmware/efi'):
return None # BIOS might fail at grub output, defer to stock OS behavior
retval = get_spcr_config()
if retval:
return retval
firstfound = None
numpossible = 0
numconnected = 0
for serdev in glob.glob('/dev/ttyS*'):
ttyf = os.open(serdev, os.O_RDWR | os.O_NOCTTY)
try:
statusreg = fcntl.ioctl(ttyf, termios.TIOCMGET, '\x00\x00\x00\x00')
numpossible += 1
if not firstfound:
firstfound = serdev
except Exception:
continue
finally:
os.close(ttyf)
if struct.unpack('<I', statusreg)[0] & termios.TIOCM_CAR:
numconnected += 1
firstfound = serdev
if numpossible == 1 or numconnected == 1:
return { 'tty': firstfound, 'speed': 115200 }
def main():
autoconscfg = get_serial_config()
if not autoconscfg or not autoconscfg['connected']:
if not autoconscfg:
return
if os.path.exists('/etc/redhat-release'): # redhat family
deserialize_grub_rh()
@@ -80,14 +80,18 @@ def await_tentative():
time.sleep(1)
def map_idx_to_name():
map = {}
map_dict = {}
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(' '):
if 'link/' in line and prevdev and prevdev not in devtype:
for word in line.split():
if word.startswith('link/'):
typ = word.split('/')[1]
devtype[prevdev] = typ if typ != 'ether' else 'ethernet'
break # Stop detection after the first type hit
continue
idx, iface, rst = line.split(':', 2)
prevdev = iface.strip()
@@ -99,9 +103,8 @@ def map_idx_to_name():
pass
idx = int(idx)
iface = iface.strip()
map[idx] = iface
return map, devtype
map_dict[idx] = iface
return map_dict, devtype
def get_interface_name(iname, settings):
explicitname = settings.get('interface_names', None)
@@ -114,8 +117,10 @@ def get_interface_name(iname, settings):
class NetplanManager(object):
def __init__(self, deploycfg):
self.cfgbydev = {}
self.cfgbybond = {}
self.read_connections()
self.deploycfg = deploycfg
self.teamidx = 0
def read_connections(self):
for plan in glob.glob('/etc/netplan/*.y*ml'):
@@ -124,29 +129,61 @@ class NetplanManager(object):
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]
bondinfo = planinfo.get('network', {}).get('bonds', {})
for currinfo in (nicinfo, bondinfo):
currcfg = self.cfgbydev if currinfo is nicinfo else self.cfgbybond
for devname in currinfo:
if devname == 'lo':
continue
if 'gateway4' in currinfo[devname]:
# normalize deprecated syntax on read in
gw4 = currinfo[devname]['gateway4']
del currinfo[devname]['gateway4']
routeinfo = currinfo[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
})
currinfo[devname]['routes'] = routeinfo
currcfg[devname] = currinfo[devname]
def apply_configuration(self, cfg):
devnames = cfg['interfaces']
if len(devnames) != 1:
raise Exception('Multi-nic team/bonds not yet supported')
if len(devnames) > 1:
teammode = cfg['settings'].get('team_mode', None)
if not teammode:
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(devnames)))
return
if teammode == 'lacp':
teammode = '802.3ad'
elif teammode == 'activebackup':
teammode = 'active-backup'
for currdev in self.cfgbybond:
for iface in self.cfgbybond[currdev].get('interfaces', []):
if iface in devnames:
break
else:
continue
else:
continue
# this bond is identified as matching
self.cfgbybond[currdev]['interfaces'] = list(devnames)
self.cfgbybond[currdev]['parameters']['mode'] = teammode
devnames = [currdev]
break
# no current bond, make a new one
connname = cfg['settings'].get('connection_name', None)
if not connname:
connname = 'bond{0}'.format(self.teamidx)
while connname in self.cfgbybond:
self.teamidx += 1
connname = 'bond{0}'.format(self.teamidx)
self.cfgbybond[connname] = {'interfaces': list(devnames), 'parameters': {'mode': teammode, 'mii-monitor-interval': 100}}
devnames = [connname]
stgs = cfg['settings']
needcfgapply = False
for devname in devnames:
@@ -164,11 +201,14 @@ class NetplanManager(object):
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('mtu', None):
devdict = self.getcfgarrpath([devname])
devdict['mtu'] = int(stgs['mtu'])
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
for gwaddr in gws:
if gwaddr:
if gwaddr and gwaddr != '0.0.0.0':
cfgroutes = self.getcfgarrpath([devname, 'routes'])
for rinfo in cfgroutes:
if rinfo.get('via', None) == gwaddr:
@@ -189,18 +229,56 @@ class NetplanManager(object):
if dnsdomain not in currdnsdomain:
needcfgwrite = True
currdnsdomain.append(dnsdomain)
prune_from_cloudinit = []
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:
if devname in self.cfgbydev:
prune_from_cloudinit.append(devname)
newcfg = {'network': {'version': 2, 'ethernets': {devname: self.cfgbydev[devname]}}}
cfgfile = '/etc/netplan/10-{0}-confluentcfg.yaml'.format(devname)
elif devname in self.cfgbybond:
newcfg = {'network': {'version': 2, 'bonds': {devname: self.cfgbybond[devname]}}}
for iface in newcfg['network']['bonds'][devname]['interfaces']:
prune_from_cloudinit.append(iface)
with open('/etc/netplan/10-{0}-confluentcfg.yaml'.format(iface), 'w') as planout:
planout.write(yaml.dump({'network': {'version': 2, 'ethernets': {iface: {'dhcp4': False}}}}))
cfgfile = '/etc/netplan/30-{0}-confluentcfg.yaml'.format(devname)
with open(cfgfile, 'w') as planout:
planout.write(yaml.dump(newcfg))
os.umask(oumask)
if prune_from_cloudinit:
prunecfgs = ['/etc/netplan/00-installer-config.yaml',
'/etc/netplan/50-cloud-init.yaml.dist-subiquity',
'/etc/netplan/50-cloud-init.yaml']
prunecfgs.extend(glob.glob('/etc/cloud/cloud.cfg.d/*.cfg'))
for defcfg in prunecfgs:
if not os.path.exists(defcfg):
continue
with open(defcfg, 'r') as cloudinit:
cloudinfo = yaml.safe_load(cloudinit)
if 'network' not in cloudinfo:
continue
for clouddev in list(cloudinfo.get('network', {}).get('ethernets', {})):
if clouddev in prune_from_cloudinit:
del cloudinfo['network']['ethernets'][clouddev]
if not cloudinfo['network'].get('ethernets', {}):
os.remove(defcfg)
if '/etc/cloud/cloud.cfg.d/' in defcfg:
# need to also change datasource, probably
if os.path.exists('/var/lib/cloud/instances/iid-datasource-none/network-config.json'):
os.remove('/var/lib/cloud/instances/iid-datasource-none/network-config.json')
else:
oumask = os.umask(0o77)
with open(defcfg, 'w') as cloudinit:
cloudinit.write(yaml.dump(cloudinfo))
os.umask(oumask)
if needcfgapply:
subprocess.check_call(['netplan', 'generate'])
subprocess.call(['netplan', 'apply'])
def getcfgarrpath(self, devpath):
currptr = self.cfgbydev
currptr = self.cfgbybond if devpath[0] in self.cfgbybond else self.cfgbydev
for k in devpath[:-1]:
if k not in currptr:
currptr[k] = {}
@@ -353,7 +431,7 @@ class NetworkManager(object):
currteam = deats.get('connection.master', None)
if currteam == team:
return
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname'):
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname', 'ipv4.dns-search', 'ipv6.dns-search'):
if deats.get(stg, None):
bondcfg[stg] = deats[stg]
if member in self.uuidbyname:
@@ -381,6 +459,8 @@ class NetworkManager(object):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if stgs.get('mtu', None):
cmdargs['802-3-ethernet.mtu'] = stgs['mtu']
dnsips = self.deploycfg.get('nameservers', [])
if not dnsips:
dnsips = []
@@ -406,7 +486,7 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(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', 'miimon=100,mode={}'.format(stgs['team_mode'])] + cargs)
@@ -441,7 +521,7 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(cmdargs[arg]))
if u:
subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
@@ -455,6 +535,9 @@ class NetworkManager(object):
if __name__ == '__main__':
checktarg = None
if '-c' in sys.argv:
checktarg = sys.argv[sys.argv.index('-c') + 1]
havefirewall = subprocess.call(['systemctl', 'status', 'firewalld'])
havefirewall = havefirewall == 0
if havefirewall:
@@ -480,15 +563,16 @@ if __name__ == '__main__':
continue
myname = s.getsockname()
s.close()
curridx = None
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():
if myname == addr[1]:
curridx = addr[-1]
if curridx in doneidxs:
if curridx is not None and curridx in doneidxs:
continue
for tries in (1, 2, 3):
try:
@@ -540,7 +624,7 @@ if __name__ == '__main__':
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
if os.path.exists('/usr/bin/nmcli'):
elif os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes, dc)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
@@ -562,4 +646,27 @@ if __name__ == '__main__':
if havefirewall:
subprocess.check_call(['systemctl', 'start', 'firewalld'])
await_tentative()
maxwait = 10
while maxwait:
try:
tclient = apiclient.HTTPSClient(checkonly=True)
tclient.check_connections()
break
except Exception:
maxwait -= 1
time.sleep(1)
maxwait = 10
if checktarg:
while maxwait:
try:
addrinf = socket.getaddrinfo(checktarg, 443)[0]
psock = socket.socket(addrinf[0], socket.SOCK_STREAM)
psock.settimeout(10)
psock.connect(addrinf[4])
psock.close()
break
except Exception:
maxwait -= 1
time.sleep(1)
@@ -7,7 +7,7 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do
continue
fi
certfile=${pubkey/.pub/-cert.pub}
rm $certfile
echo -n > $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
@@ -17,6 +17,13 @@ if [ -d /etc/ssh/sshd_config.d/ -a ! -e /etc/ssh/sshd_config.d/90-confluent.conf
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
elif [ ! -d /etc/ssh/sshd_config.d/ ] && ! grep HostCertificate /etc/ssh/sshd_config > /dev/null; then
for cert in /etc/ssh/ssh*-cert.pub; do
echo HostCertificate $cert >> /etc/ssh/sshd_config
done
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config
echo IgnoreRhosts no >> /etc/ssh/sshd_config
fi
TMPDIR=$(mktemp -d)
@@ -25,13 +32,20 @@ confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs
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
if [ -z "$LINE" ]; then continue; fi
if [ -f /etc/ssh/ssh_known_hosts ]; then
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
fi
echo '@cert-authority *' $LINE >> /etc/ssh/ssh_known_hosts.new
mv /etc/ssh/ssh_known_hosts.new /etc/ssh/ssh_known_hosts
done
mkdir -p /root/.ssh/
chmod 700 /root/.ssh/
touch /root/.ssh/authorized_keys
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
if [ -z "$LINE" ]; then continue; fi
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
@@ -41,3 +55,9 @@ confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' >
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
# ssh may be sshd or ssh, depending
if systemctl list-unit-files | grep -q '^sshd\.service'; then
systemctl try-restart sshd
elif systemctl list-unit-files | grep -q '^ssh\.service'; then
systemctl try-restart ssh
fi
@@ -26,8 +26,12 @@ mkdir -p opt/confluent/bin
mkdir -p stateless-bin
cp -a el8bin/* .
ln -s el8 el9
ln -s el8 el10
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
cp -a el8 el10
cp -a debian debian13
mkdir -p debian13/initramfs/usr
mv debian13/initramfs/lib debian13/initramfs/usr/
mv el10/initramfs/usr el10/initramfs/var
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04 coreos el9 el10; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
@@ -43,7 +47,7 @@ for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreo
mv ../addons.cpio .
cd ..
done
for os in el7 el8 suse15 el9 el10 ubuntu20.04; do
for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04; do
mkdir ${os}disklessout
cd ${os}disklessout
if [ -d ../${os}bin ]; then
@@ -81,7 +85,10 @@ cp -a esxi7 esxi8
%install
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
#cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
for os in rhvh4 el7 el8 el9 el10 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/common
cp common/initramfs/opt/confluent/bin/apiclient %{buildroot}/opt/confluent/lib/osdeploy/common
for os in rhvh4 el7 el8 el9 el10 debian debian13 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04 esxi6 esxi7 esxi8 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
if [ -d ${os}disklessout ]; then
@@ -29,8 +29,11 @@ This contains support utilities for enabling deployment of x86_64 architecture s
#cd ..
ln -s el8 el9
cp -a el8 el10
cp -a debian debian13
mkdir -p debian13/initramfs/usr
mv debian13/initramfs/lib debian13/initramfs/usr/
mv el10/initramfs/usr el10/initramfs/var
for os in rhvh4 el7 genesis el8 suse15 debian ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04 coreos el9 el10; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
@@ -46,7 +49,7 @@ for os in rhvh4 el7 genesis el8 suse15 debian ubuntu18.04 ubuntu20.04 ubuntu22.0
mv ../addons.cpio .
cd ..
done
for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04; do
for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04; do
mkdir ${os}disklessout
cd ${os}disklessout
if [ -d ../${os}bin ]; then
@@ -86,7 +89,7 @@ cp -a esxi7 esxi9
%install
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
for os in rhvh4 el7 el8 el9 el10 genesis suse15 ubuntu20.04 debian ubuntu18.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 esxi9 coreos; do
for os in rhvh4 el7 el8 el9 el10 genesis suse15 ubuntu20.04 debian debian13 ubuntu18.04 ubuntu22.04 ubuntu24.04 ubuntu26.04 esxi6 esxi7 esxi8 esxi9 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
@@ -110,14 +110,22 @@ else
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
nic=
while [ -z "$nic" ]; do
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 address flush dev $nic
nic=""
done
if [ -z "$nic" ]; then
echo "No network interface could be detected, retrying...."
sleep 2
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/ //')
@@ -164,6 +164,9 @@ class NetplanManager(object):
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('mtu', None):
devdict = self.getcfgarrpath([devname])
devdict['mtu'] = int(stgs['mtu'])
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
@@ -381,6 +384,8 @@ class NetworkManager(object):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if stgs.get('mtu', None):
cmdargs['802-3-ethernet.mtu'] = stgs['mtu']
dnsips = self.deploycfg.get('nameservers', [])
if not dnsips:
dnsips = []
@@ -406,10 +411,10 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(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)
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'miimon=100,mode={}'.format(stgs['team_mode'])] + cargs)
for iface in cfg['interfaces']:
self.add_team_member(cname, iface)
subprocess.check_call(['nmcli', 'c', 'u', cname])
@@ -441,7 +446,7 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(cmdargs[arg]))
if u:
subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,12 +2,18 @@
# This script would run in post.d
#
export DEBIAN_FRONTEND=noninteractive
echo "deb [arch=amd64] http://download.proxmox.com/debian/pve bookworm pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
wget https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg
sum=$(sha512sum /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg)
if [ "$sum" -ne "7da6fe34168adc6e479327ba517796d4702fa2f8b4f0a9833f5ea6e6b48f6507a6da403a274fe201595edc86a84463d50383d07f64bdde2e3658108db7d6dc87" ]; then
codename=$(grep ^VERSION_CODENAME /etc/os-release | cut -d= -f2)
echo "deb [arch=amd64] http://download.proxmox.com/debian/pve $codename pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
wget https://enterprise.proxmox.com/debian/proxmox-release-$codename.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg
sum=$(sha512sum /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg)
if [ $codename == "bookworm" ]; then
expectedsum=7da6fe34168adc6e479327ba517796d4702fa2f8b4f0a9833f5ea6e6b48f6507a6da403a274fe201595edc86a84463d50383d07f64bdde2e3658108db7d6dc87
elif [ $codename == "trixie" ]; then
expectedsum=8678f2327c49276615288d7ca11e7d296bc8a2b96946fe565a9c81e533f9b15a5dbbad210a0ad5cd46d361ff1d3c4bac55844bc296beefa4f88b86e44e69fa51
fi
if [ "$sum" -ne "$expectedsum" ]; then
echo "Mismatch in fingerprint!"
rm /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg
rm /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg
exit 1
fi
apt-get update && apt-get -y full-upgrade < /dev/null
@@ -7,7 +7,6 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do
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
@@ -25,6 +24,7 @@ confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs
tar xf initramfs.tgz
for ca in ssh/*.ca; do
LINE=$(cat $ca)
if [ -z "$LINE" ]; then continue; fi
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
@@ -32,6 +32,7 @@ for ca in ssh/*.ca; do
done
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
if [ -z "$LINE" ]; then continue; fi
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
@@ -41,3 +42,4 @@ confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' >
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
systemctl try-restart sshd
@@ -3,8 +3,8 @@ confluent_whost=$confluent_mgr
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
confluent_whost="[$confluent_mgr]"
fi
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
@@ -40,20 +40,53 @@ fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
if [ ! "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
fi
TETHERED=0
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -o discard /dev/zram0 /sysroot
else
mount -t tmpfs disklessroot /sysroot
fi
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
cp -ax /mnt/remote/* /sysroot/
umount /mnt/remote
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
wait
echo -e "Decrypting and extracting root filesystem: 100%"
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
TETHERED=1
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
fi
fi
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
@@ -109,7 +142,7 @@ echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
chmod 640 /sysroot/etc/ssh/*_key
chmod 600 /sysroot/etc/ssh/*_key
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
chroot /sysroot/ update-ca-trust
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
@@ -129,4 +162,25 @@ ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
mv /lib/firmware /lib/firmware-ramfs
ln -s /sysroot/lib/firmware /lib/firmware
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
exec /opt/confluent/bin/start_root
if [ $TETHERED -eq 1 ]; then
(
sleep 86400 &
ONBOOTPID=$!
mkdir -p /run/confluent
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
wait $ONBOOTPID
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
) &
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
sleep 0.1
done
else
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
fi
if grep debugssh /proc/cmdline >& /dev/null; then
exec /opt/confluent/bin/start_root
else
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
fi
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -21,17 +21,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
fi
if [ -f /tmp/timeservers ]; then
ntpsrvs=$(cat /tmp/timeservers)
sed -i "1,/^pool * /c\\
${ntpsrvs//$'\n'/\\$'\n'}" /etc/chrony.conf
systemctl restart chronyd
rm -f /tmp/timeservers
sed -i '/^[[:space:]]*\(pool\|server\)[[:space:]]/d' /etc/chrony.conf
cat /tmp/timeservers >> /etc/chrony.conf
systemctl restart chronyd
rm -f /tmp/timeservers
fi
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -107,7 +107,11 @@ if [ ! -z "$confluentsrv" ]; then
/usr/libexec/nm-initrd-generator ip=:dhcp6
else
confluenthttpsrv=$confluentsrv
ifname=$(ip -br link|grep LOWER_UP|grep -v UNKNOWN|head -n 1|awk '{print $1}')
ifname=""
while [ -z "$ifname" ]; do
ifname=$(ip -br link|grep LOWER_UP|grep -v ib|grep -v UNKNOWN|head -n 1|awk '{print $1}')
sleep 0.5
done
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
while ! ip -br addr show dev $ifname | grep \\. > /dev/null; do
@@ -25,7 +25,8 @@ if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
confluentpython /root/confignet
rm /root/confignet
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot.d
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -56,7 +56,11 @@ def get_image_metadata(imgpath):
for md in get_multipart_image_meta(img):
yield md
else:
raise Exception('Installation from single part image not supported')
yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 4294967296, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760}
yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 536870912, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656}
yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 33554432, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960}
#raise Exception('Installation from single part image not supported')
class PartedRunner():
def __init__(self, disk):
@@ -75,8 +79,17 @@ def fixup(rootdir, vols):
for vol in vols:
devbymount[vol['mount']] = vol['targetdisk']
fstabfile = os.path.join(rootdir, 'etc/fstab')
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
if os.path.exists(fstabfile):
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
else:
# fabricate a reference fstab
fstab = [
"#ORIGFSTAB#/dev/mapper/root# / xfs defaults 0 0",
"#ORIGFSTAB#UUID=aaf9e0f9-aa4d-4d74-9e75-3537620cfe23# /boot xfs defaults 0 0",
"#ORIGFSTAB#UUID=C21D-B881# /boot/efi vfat umask=0077,shortname=winnt 0 2",
"#ORIGFSTAB#/dev/mapper/swap# none swap defaults 0 0",
]
while not fstab[0]:
fstab = fstab[1:]
if os.path.exists(os.path.join(rootdir, '.autorelabel')):
@@ -126,8 +139,10 @@ def fixup(rootdir, vols):
newcfg = ifcfg.split('/')[-1]
newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg))
shutil.copy2(ifcfg, newcfg)
shutil.rmtree(os.path.join(rootdir, 'etc/confluent/'))
shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent'))
rootconfluentdir = os.path.join(rootdir, 'etc/confluent/')
if os.path.exists(rootconfluentdir):
shutil.rmtree(rootconfluentdir)
shutil.copytree('/etc/confluent', rootconfluentdir)
if policy:
sys.stdout.write('Applying SELinux labeling...')
sys.stdout.flush()
@@ -142,14 +157,41 @@ def fixup(rootdir, vols):
grubsyscfg = os.path.join(rootdir, 'etc/sysconfig/grub')
if not os.path.exists(grubsyscfg):
grubsyscfg = os.path.join(rootdir, 'etc/default/grub')
with open(grubsyscfg) as defgrubin:
defgrub = defgrubin.read().split('\n')
currcmdline = []
with open('/proc/cmdline') as cmdlinein:
cmdline = cmdlinein.read().strip()
for arg in cmdline.split():
if arg.startswith('console='):
currcmdline.append(arg)
elif arg == 'quiet':
currcmdline.append(arg)
currcmdlinestr = ' '.join(currcmdline)
if os.path.exists(grubsyscfg):
with open(grubsyscfg) as defgrubin:
defgrub = defgrubin.read().split('\n')
else:
defgrub = [
'GRUB_TIMEOUT=5',
'GRUB_DISTRIBUTOR="$(sed ' + "'s, release .*$,,g'" + ' /etc/system-release)"',
'GRUB_DEFAULT=saved',
'GRUB_DISABLE_SUBMENU=true',
'GRUB_TERMINAL=""',
'GRUB_SERIAL_COMMAND=""',
'GRUB_CMDLINE_LINUX="{} crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"'.format(currcmdlinestr),
'GRUB_DISABLE_RECOVERY="true"',
'GRUB_ENABLE_BLSCFG=true',
]
if not os.path.exists(os.path.join(rootdir, "etc/kernel/cmdline")):
with open(os.path.join(rootdir, "etc/kernel/cmdline"), "w") as cmdlineout:
cmdlineout.write("{} root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root".format(currcmdlinestr))
with open(grubsyscfg, 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
for ent in gline:
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
if ent.endswith('"'):
newline.append('"')
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
@@ -159,6 +201,12 @@ def fixup(rootdir, vols):
grubcfg = grubcfg[:-1]
if len(grubcfg) == 1:
grubcfg = grubcfg[0]
elif not grubcfg:
grubcfg = '/boot/grub2/grub.cfg'
paths = glob.glob(os.path.join(rootdir, 'boot/efi/EFI/*'))
for path in paths:
with open(os.path.join(path, 'grub.cfg'), 'w') as stubgrubout:
stubgrubout.write("search --no-floppy --root-dev-only --fs-uuid --set=dev " + bootuuid + "\nset prefix=($dev)/grub2\nexport $prefix\nconfigfile $prefix/grub.cfg\n")
else:
for gcfg in grubcfg:
rgcfg = os.path.join(rootdir, gcfg[1:]) # gcfg has a leading / to get rid of
@@ -214,10 +262,18 @@ def fixup(rootdir, vols):
shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip()
shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\')
subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum])
try:
os.makedirs(os.path.join(rootdir, 'opt/confluent/bin'))
except Exception:
pass
shutil.copy2('/opt/confluent/bin/apiclient', os.path.join(rootdir, 'opt/confluent/bin/apiclient'))
#other network interfaces
def had_swap():
if not os.path.exists('/etc/fstab'):
# diskless source, assume swap
return True
with open('/etc/fstab') as tabfile:
tabs = tabfile.read().split('\n')
for tab in tabs:
@@ -362,6 +418,8 @@ def install_to_disk(imgpath):
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ'])
source = vol['mount'].replace('/', '_')
source = '/run/imginst/sources/' + source
if not os.path.exists(source):
source = '/run/imginst/sources/_' + vol['mount']
blankfsstat = os.statvfs('/run/imginst/targ')
blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize
sys.stdout.write('\nWriting {0}: '.format(vol['mount']))
@@ -419,8 +477,14 @@ def install_to_disk(imgpath):
subprocess.check_call(['umount', '/run/imginst/targ'])
while True:
try:
subprocess.check_call(['umount', '/run/imginst/targ'])
except subprocess.CalledProcessError:
print("Failed to unmount /run/imginst/targ, retrying")
time.sleep(1)
else:
break
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
@@ -4,10 +4,12 @@ if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
confluent_whost="[$confluent_mgr]"
fi
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
TETHERED=0
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
TETHERED=1
confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs"
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
fi
@@ -130,4 +132,17 @@ ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
mv /lib/firmware /lib/firmware-ramfs
ln -s /sysroot/lib/firmware /lib/firmware
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
if [ $TETHERED -eq 1 ]; then
(
sleep 86400 &
ONBOOTPID=$!
mkdir -p /run/confluent
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
wait $ONBOOTPID
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
) &
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
sleep 0.1
done
fi
exec /opt/confluent/bin/start_root
@@ -5,6 +5,7 @@
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /dev/pts /sysroot/dev/pts
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
@@ -21,8 +22,14 @@ else
done
fi
cd /sysroot/run
cp /run/sshd.pid /tmp/dbgssh.pid
chroot /sysroot/ bash -c "/usr/sbin/sshd"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
for nameserver in $(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //'); do
echo "nameserver $nameserver" >> /sysroot/etc/resolv.conf
done
#chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python confignet"
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
@@ -39,7 +46,10 @@ chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python imag
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
kill $(cat /sysroot/var/run/sshd.pid)
kill -HUP $(cat /tmp/dbgssh.pid)
cp /sysroot/etc/pki/ca-trust/source/anchors/* /sysroot/run/imginst/targ/etc/pki/ca-trust/source/anchors/
chroot /sysroot/run/imginst/targ update-ca-trust
chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh"
chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)"
@@ -27,17 +27,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
fi
if [ -f /tmp/timeservers ]; then
ntpsrvs=$(cat /tmp/timeservers)
sed -i "1,/^pool * /c\\
${ntpsrvs//$'\n'/\\$'\n'}" /etc/chrony.conf
systemctl restart chronyd
rm -f /tmp/timeservers
sed -i '/^[[:space:]]*\(pool\|server\)[[:space:]]/d' /etc/chrony.conf
cat /tmp/timeservers >> /etc/chrony.conf
systemctl restart chronyd
rm -f /tmp/timeservers
fi
@@ -59,7 +52,7 @@ rpm --import /etc/pki/rpm-gpg/*
run_remote_python add_local_repositories
run_remote_python syncfileclient
run_remote_python confignet
run_remote_python confignet -c $confluent_mgr
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
@@ -68,5 +61,14 @@ run_remote_parts onboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
run_remote_config onboot.d
if [ -f /run/confluent/onboot_sleep.pid ]; then
loopdev=$(losetup -j /mnt/remoteimg/rootimg.sfs|cut -d: -f 1)
if [ -n "$loopdev" ]; then
losetup "$loopdev" --direct-io=on
fi
sleeppid=$(cat /run/confluent/onboot_sleep.pid)
kill "$sleeppid"
rm -f /run/confluent/onboot_sleep.pid
fi
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -2,13 +2,17 @@
# This script is executed 'chrooted' into a cloned disk target before rebooting
#
if [ -f /etc/dracut.conf.d/diskless.conf ]; then
rm /etc/dracut.conf.d/diskless.conf
fi
for kver in /lib/modules/*; do kver=$(basename $kver); kernel-install add $kver /boot/vmlinuz-$kver; done
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}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
run_remote setupssh
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
@@ -33,6 +37,8 @@ run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
cd /root/
fetch_remote confignet
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -277,7 +277,10 @@ def synchronize():
try:
uid = pwd.getpwnam(opts[fname][opt]['name']).pw_uid
except KeyError:
uid = opts[fname][opt]['id']
try:
uid = opts[fname][opt]['id']
except KeyError:
raise Exception(f"Unable to map owner of {fname}")
elif opt == 'group':
try:
gid = grp.getgrnam(opts[fname][opt]['name']).gr_gid
@@ -3,10 +3,12 @@ echo -n "" >> /tmp/net.ifaces
echo -n "" > /tmp/01-autocons.devnode
BUNDLENAME=/etc/pki/tls/certs/ca-bundle.crt
if [ ! -e "$BUNDLENAME" ]; then
BUNDLENAME=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
mkdir -p /etc/pki/tls/certs
ln -s $BUNDLENAME /etc/pki/tls/certs/ca-bundle.crt
fi
if [ -e /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem ]; then
BUNDLENAME=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
ln -s $BUNDLENAME /etc/pki/tls/certs/ca-bundle.crt
fi
fi
cat /tls/*.0 >> $BUNDLENAME
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
@@ -3,7 +3,7 @@ sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/; s/oraclelinux/Oracle
if grep Fedora $2/profile.yaml > /dev/null; then
sed -i 's/@^minimal-environment/#/' $2/packagelist
fi
if grep ^label: $2/profile.yaml | grep 10 > /dev/null; then
if grep ^label: $2/profile.yaml | grep ' 10' > /dev/null; then
echo 'echo openssh-keysign >> /tmp/addonpackages' > $2/scripts/pre.d/enablekeysign
chmod 644 $2/scripts/pre.d/enablekeysign
fi
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -31,8 +31,10 @@ done
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
confluentpython /root/confignet
rm /root/confignet
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot.d
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -11,6 +11,10 @@ import struct
import sys
import subprocess
import traceback
try:
import yaml
except ImportError:
yaml = None
bootuuid = None
vgname = 'localstorage'
@@ -66,9 +70,9 @@ def get_image_metadata(imgpath):
yield md
else:
# plausible filesystem structure to apply to a nominally "diskless" image
yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 39513563136, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760}
yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 232316928, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656}
yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 7835648, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960}
yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 4294967296, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760}
yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 536870912, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656}
yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 33554432, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960}
#raise Exception('Installation from single part image not supported')
class PartedRunner():
@@ -166,6 +170,15 @@ def fixup(rootdir, vols):
grubsyscfg = os.path.join(rootdir, 'etc/sysconfig/grub')
if not os.path.exists(grubsyscfg):
grubsyscfg = os.path.join(rootdir, 'etc/default/grub')
currcmdline = []
with open('/proc/cmdline') as cmdlinein:
cmdline = cmdlinein.read().strip()
for arg in cmdline.split():
if arg.startswith('console='):
currcmdline.append(arg)
elif arg == 'quiet':
currcmdline.append(arg)
currcmdlinestr = ' '.join(currcmdline)
kcmdline = os.path.join(rootdir, 'etc/kernel/cmdline')
if os.path.exists(kcmdline):
with open(kcmdline) as kcmdlinein:
@@ -177,8 +190,10 @@ def fixup(rootdir, vols):
elif ent.startswith('root='):
newkcmdlineent.append('root={}'.format(newrootdev))
elif ent.startswith('rd.lvm.lv='):
ent = convert_lv(ent)
if ent:
nent = convert_lv(ent)
if nent:
newkcmdlineent.append(ent)
else:
newkcmdlineent.append(ent)
else:
newkcmdlineent.append(ent)
@@ -200,8 +215,10 @@ def fixup(rootdir, vols):
elif cfgpart.startswith('resume='):
newcfgparts.append('resume={}'.format(newswapdev))
elif cfgpart.startswith('rd.lvm.lv='):
cfgpart = convert_lv(cfgpart)
if cfgpart:
ncfgpart = convert_lv(cfgpart)
if ncfgpart:
newcfgparts.append(ncfgpart)
else:
newcfgparts.append(cfgpart)
else:
newcfgparts.append(cfgpart)
@@ -217,13 +234,13 @@ def fixup(rootdir, vols):
'GRUB_DISABLE_SUBMENU=true',
'GRUB_TERMINAL=""',
'GRUB_SERIAL_COMMAND=""',
'GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"',
'GRUB_CMDLINE_LINUX="{}crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"'.format(currcmdlinestr),
'GRUB_DISABLE_RECOVERY="true"',
'GRUB_ENABLE_BLSCFG=true',
]
if not os.path.exists(os.path.join(rootdir, "etc/kernel/cmdline")):
with open(os.path.join(rootdir, "etc/kernel/cmdline"), "w") as cmdlineout:
cmdlineout.write("root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root")
cmdlineout.write("{} root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root".format(currcmdlinestr))
with open(grubsyscfg, 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
@@ -234,11 +251,11 @@ def fixup(rootdir, vols):
elif ent.startswith('root='):
newline.append('root={}'.format(newrootdev))
elif ent.startswith('rd.lvm.lv='):
ent = convert_lv(ent)
if ent:
nent = convert_lv(ent)
if nent:
newline.append(nent)
else:
newline.append(ent)
elif '""' in ent:
newline.append('""')
else:
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
@@ -301,8 +318,8 @@ def fixup(rootdir, vols):
for vol in vols:
if vol['mount'] == '/boot/efi':
targdev = vol['targetdisk']
partnum = re.search('(\d+)$', targdev).group(1)
targblock = re.search('(.*)\d+$', targdev).group(1)
partnum = re.search(r'(\d+)$', targdev).group(1)
targblock = re.search(r'(.*)\d+$', targdev).group(1)
if targblock:
if targblock.endswith('p') and 'nvme' in targblock:
targblock = targblock[:-1]
@@ -334,13 +351,16 @@ def had_swap():
newrootdev = None
newswapdev = None
vgmap = None
def install_to_disk(imgpath):
global vgmap
global bootuuid
global newrootdev
global newswapdev
global vgname
global oldvgname
lvmvols = {}
vgmap = {}
deftotsize = 0
mintotsize = 0
deflvmsize = 0
@@ -365,24 +385,30 @@ def install_to_disk(imgpath):
mintotsize = swapsize
for fs in get_image_metadata(imgpath):
allvols.append(fs)
deftotsize += fs['initsize']
mintotsize += fs['minsize']
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
if fs['device'].startswith('/dev/mapper'):
oldvgname = fs['device'].rsplit('/', 1)[-1]
odevname = fs['device'].rsplit('/', 1)[-1]
# if node has - then /dev/mapper will double up the hypen
if '_' in oldvgname and '-' in oldvgname.split('_', 1)[-1]:
oldvgname = oldvgname.rsplit('-', 1)[0].replace('--', '-')
if '_' in odevname and '-' in odevname.split('_', 1)[-1]:
oldvgname = odevname.rsplit('-', 1)[0].replace('--', '-')
osname = oldvgname.split('_')[0]
nodename = socket.gethostname().split('.')[0]
vgname = '{}_{}'.format(osname, nodename)
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
elif '-' in odevname: # unique one
vgmap[odevname] = odevname.split('-')[0]
lvmvols[odevname] = fs
continue
lvmvols[odevname] = fs
deflvmsize += fs['initsize']
minlvmsize += fs['minsize']
else:
plainvols[int(re.search('(\d+)$', fs['device'])[0])] = fs
plainvols[int(re.search(r'(\d+)$', fs['device'])[0])] = fs
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
deftotsize += fs['initsize']
mintotsize += fs['minsize']
with open('/tmp/installdisk') as diskin:
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
@@ -440,6 +466,28 @@ def install_to_disk(imgpath):
lvmpart = get_partname(instdisk, volidx + 1)
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
subprocess.check_call(['vgcreate', vgname, lvmpart])
vgroupmap = {}
if yaml and vgmap and os.path.exists('/tmp/volumegroupmap.yml'):
with open('/tmp/volumegroupmap.yml') as mapin:
vgroupmap = yaml.safe_load(mapin)
donedisks = {}
for morevolname in vgmap:
morevg = vgmap[morevolname]
if morevg not in vgroupmap:
raise Exception("No mapping defined to create volume group {}".format(morevg))
targdisk = vgroupmap[morevg]
if targdisk not in donedisks:
moreparted = PartedRunner(targdisk)
moreparted.run('mklabel gpt')
moreparted.run('mkpart lvm 0% 100%')
morelvmpart = get_partname(targdisk, 1)
subprocess.check_call(['pvcreate', '-ff', '-y', morelvmpart])
subprocess.check_call(['vgcreate', morevg, morelvmpart])
donedisks[targdisk] = 1
morelvname = morevolname.split('-', 1)[1]
subprocess.check_call(['lvcreate', '-L', '{}b'.format(lvmvols[morevolname]['initsize']), '-y', '-n', morelvname, morevg])
lvmvols[morevolname]['targetdisk'] = '/dev/{}/{}'.format(morevg, morelvname)
vginfo = subprocess.check_output(['vgdisplay', vgname, '--units', 'b']).decode('utf8')
vginfo = vginfo.split('\n')
pesize = 0
@@ -452,6 +500,9 @@ def install_to_disk(imgpath):
pes = int(infline[4])
takeaway = swapsize // pesize
for volidx in lvmvols:
if volidx in vgmap:
# was handled previously
continue
vol = lvmvols[volidx]
if vol is biggestfs:
continue
@@ -460,6 +511,10 @@ def install_to_disk(imgpath):
biggestextents = pes - takeaway
for volidx in lvmvols:
vol = lvmvols[volidx]
if volidx in vgmap:
# was handled previously
continue
if vol is biggestfs:
extents = biggestextents
else:
@@ -546,7 +601,13 @@ def install_to_disk(imgpath):
subprocess.check_call(['umount', '/run/imginst/targ'])
while True:
try:
subprocess.check_call(['umount', '/run/imginst/targ'])
break
except subprocess.CalledProcessError:
print("Failed to unmount /run/imginst/targ, retrying")
time.sleep(1)
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
@@ -3,8 +3,8 @@ confluent_whost=$confluent_mgr
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
confluent_whost="[$confluent_mgr]"
fi
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
@@ -40,20 +40,53 @@ fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
if [ ! "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
fi
TETHERED=0
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -o discard /dev/zram0 /sysroot
else
mount -t tmpfs disklessroot /sysroot
fi
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
cp -ax /mnt/remote/* /sysroot/
umount /mnt/remote
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
wait
echo -e "Decrypting and extracting root filesystem: 100%"
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
TETHERED=1
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
fi
fi
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
@@ -109,8 +142,10 @@ echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
chmod 640 /sysroot/etc/ssh/*_key
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
if grep ^ssh_keys: /etc/group > /dev/null; then
chmod 640 /sysroot/etc/ssh/*_key
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
fi
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
chroot /sysroot/ update-ca-trust
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
@@ -129,5 +164,37 @@ mv /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs
ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
mv /lib/firmware /lib/firmware-ramfs
ln -s /sysroot/lib/firmware /lib/firmware
rm -f /sysroot/etc/dracut.conf.d/diskless.conf # remove diskless dracut from runtime, to make kdump happier
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
exec /opt/confluent/bin/start_root
if grep debugssh /proc/cmdline >& /dev/null; then
debugssh=1
else
debugssh=0
fi
if [ $TETHERED -eq 1 ]; then
# In tethered mode, the double-caching is useful to get through tricky part of
# onboot with confignet. After that, it's excessive cache usage.
# Give the onboot script a hook to have us come in and enable directio to the
# squashfs and drop the cache of the rootimg so far
(
sleep 86400 &
ONBOOTPID=$!
mkdir -p /run/confluent
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
wait $ONBOOTPID
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
if [ $debugssh -eq 0 ]; then
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
fi
) &
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
sleep 0.1
done
elif [ $debugssh -eq 0 ]; then
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
fi
if grep debugssh /proc/cmdline >& /dev/null; then
exec /opt/confluent/bin/start_root
else
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
fi
@@ -5,8 +5,12 @@
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /dev/pts /sysroot/dev/pts
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
mount -t efivarfs none /sysroot/sys/firmware/efi/efivars
if [ ! -f /tmp/mountparts.sh ]; then
@@ -21,8 +25,16 @@ else
done
fi
cd /sysroot/run
[ -f /run/sshd.pid ] &&
cp /run/sshd.pid /tmp/dbgssh.pid
chmod 0600 /sysroot/etc/ssh/ssh*key
chroot /sysroot/ bash -c "/usr/sbin/sshd"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
for nameserver in $(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //'); do
echo "nameserver $nameserver" >> /sysroot/etc/resolv.conf
done
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
@@ -40,7 +52,8 @@ chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python imag
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
kill $(cat /sysroot/var/run/sshd.pid)
[ -f /tmp/dbgssh.pid ] && kill -HUP $(cat /tmp/dbgssh.pid)
cp /sysroot/etc/pki/ca-trust/source/anchors/* /sysroot/run/imginst/targ/etc/pki/ca-trust/source/anchors/
chroot /sysroot/run/imginst/targ update-ca-trust
@@ -26,17 +26,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
fi
if [ -f /tmp/timeservers ]; then
ntpsrvs=$(cat /tmp/timeservers)
sed -i "1,/^pool * /c\\
${ntpsrvs//$'\n'/\\$'\n'}" /etc/chrony.conf
systemctl restart chronyd
rm -f /tmp/timeservers
sed -i '/^[[:space:]]*\(pool\|server\)[[:space:]]/d' /etc/chrony.conf
cat /tmp/timeservers >> /etc/chrony.conf
systemctl restart chronyd
rm -f /tmp/timeservers
fi
export nodename confluent_mgr confluent_profile
@@ -53,7 +46,7 @@ rpm --import /etc/pki/rpm-gpg/*
run_remote_python add_local_repositories
run_remote_python syncfileclient
run_remote_python confignet
run_remote_python confignet -c $confluent_mgr
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
@@ -62,5 +55,15 @@ run_remote_parts onboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
run_remote_config onboot.d
if [ -f /run/confluent/onboot_sleep.pid ]; then
loopdev=$(losetup -j /mnt/remoteimg/rootimg.sfs|cut -d: -f 1)
if [ -n "$loopdev" ]; then
losetup "$loopdev" --direct-io=on
fi
sleeppid=$(cat /run/confluent/onboot_sleep.pid)
kill "$sleeppid"
rm -f /run/confluent/onboot_sleep.pid
fi
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -43,7 +43,8 @@ run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
cd /root/
fetch_remote confignet
# rebuild initrd, pick up new drivers if needed
dracut -f /boot/initramfs-$(uname -r).img $(uname -r)
@@ -62,8 +62,8 @@ if [ -e /tmp/confluentident/cnflnt.yml ]; then
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
localcli network ip interface ipv4 set -i vmk0 -I $v4addr -N $v4nm -g $v4gw -t static
localcli network ip route ipv4 add -n default -g $v4gw
while ! localcli network ip interface ipv4 set -i vmk0 -I $v4addr -N $v4nm -g $v4gw -t static; do echo "Retrying..."; sleep 5; done
while ! localcli network ip route ipv4 add -n default -g $v4gw; do sleep 1; done
fi
hmackeyfile=$(mktemp)
echo -n $(grep ^apitoken: /tmp/confluentident/cnflnt.yml|awk '{print $2}') > $hmackeyfile
@@ -73,6 +73,20 @@ if [ -e /tmp/confluentident/cnflnt.yml ]; then
hmacfile=$(mktemp)
ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac
/opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
echo -n 'Checking connectivity to server: '
maxwait=30
while ! /opt/confluent/bin/apiclient -c >& /dev/null; do
echo -n '.'
sleep 1
maxwait=$((maxwait - 1))
if [ $maxwait -le 0 ]; then
echo "Unable to contact deployment server, verify network connectivity"
echo "A debug session has been made available on Alt-F1"
sleep 30
maxwait=30
fi
done
echo
echo -n 'Registering new API key with deployment server: '
/opt/confluent/bin/apiclient -p $hmacfile /confluent-api/self/registerapikey $passcrypt
echo
@@ -1,6 +1,5 @@
accepteula
clearpart --firstdisk --overwritevmfs
install --firstdisk --overwritevmfs
%include /tmp/storagecfg
%include /tmp/ksnet
%include /tmp/rootpw
reboot
@@ -0,0 +1,148 @@
#!/usr/bin/python3
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname, devinfo):
self.name = devname
self.path = '/dev/' + devname
self.wwn = None
self.model = devinfo.get('model', 'Unknown')
self.driver = devinfo.get('adapter_driver', 'Unknown')
self.size = devinfo.get('size', 0) # in MiB
if not devinfo.get('is_local', False):
raise SilentException("Not local")
if devinfo.get('is_removable', False):
raise SilentException("Removable")
if devinfo.get('is_usb', False):
raise SilentException("USB device")
if devinfo.get('type', '').lower() in ('cd-rom',):
raise SilentException("CD-ROM device")
if self.size < 2048:
raise SilentException("Too small")
@property
def priority(self):
if self.model.lower() in ('m.2 nvme 2-bay raid kit', 'thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
return 0
if self.driver == 'vmw_ahci':
return 2
if self.driver == 'nvme_pcie':
return 3
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def list_disks():
current_dev = None
disks = {}
devlist = subprocess.check_output(['localcli', 'storage', 'core', 'device', 'list'])
if not isinstance(devlist, str):
devlist = devlist.decode('utf8')
devbyadp = {}
for line in devlist.split('\n'):
if not line.strip():
continue
if not line.startswith(' '):
current_dev = line.rsplit(':', 1)[0]
if current_dev not in disks:
disks[current_dev] = {}
elif current_dev:
if ' Model:' in line:
disks[current_dev]['model'] = ' '.join(line.split()[1:])
elif ' Driver:' in line:
disks[current_dev]['driver'] = ' '.join(line.split()[1:])
elif ' Is Local:' in line:
disks[current_dev]['is_local'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Is Removable:' in line:
disks[current_dev]['is_removable'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Size:' in line: # in MiB
disks[current_dev]['size'] = int(line.split()[1])
elif ' Is SSD:' in line:
disks[current_dev]['is_ssd'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Is USB:' in line:
disks[current_dev]['is_usb'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Is Removable:' in line:
disks[current_dev]['is_removable'] = ' '.join(line.split()[2:]).lower() == 'true'
elif 'Device Type:' in line:
disks[current_dev]['type'] = ' '.join(line.split()[2:])
for dev in disks:
pathlist = subprocess.check_output(['localcli', 'storage', 'core', 'path', 'list', '--device', dev])
if not isinstance(pathlist, str):
pathlist = pathlist.decode('utf8')
for line in pathlist.split('\n'):
if not line.strip():
continue
if not line.startswith(' '):
continue
if ' Adapter Identifier:' in line:
adpname = ' '.join(line.split()[2:])
disks[dev]['adapter_id'] = adpname
elif ' Adapter:' in line:
adp = ' '.join(line.split()[1:])
disks[dev]['adapter'] = adp
devbyadp.setdefault(adp, []).append(dev)
adapterlist = subprocess.check_output(['localcli', 'storage', 'core', 'adapter', 'list'])
if not isinstance(adapterlist, str):
adapterlist = adapterlist.decode('utf8')
driverbyadp = {}
linenum = 0
for line in adapterlist.split('\n'):
linenum += 1
if not line.strip():
continue
if linenum < 3:
continue
parts = line.split()
if len(parts) < 2:
continue
adp = parts[0]
driver = parts[1]
driverbyadp[adp] = driver
for adp in devbyadp:
driver = driverbyadp.get(adp, 'Unknown')
for dev in devbyadp[adp]:
disks[dev]['adapter_driver'] = driver
return disks
def main():
disks = []
try:
alldisks = list_disks()
except Exception as e:
print("Error listing disks: {0}".format(str(e)))
alldisks = {}
for disk in alldisks:
try:
disks.append(DiskInfo(disk, alldisks[disk]))
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
with open('/tmp/storagecfg', 'w') as sc:
sc.write(f'clearpart --all --drives={nd[0]} --overwritevmfs\n')
sc.write(f'install --drive={nd[0]} --overwritevmfs\n')
else:
with open('/tmp/storagecfg', 'w') as sc:
sc.write(f'clearpart --firstdisk --overwritevmfs\n')
sc.write(f'install --firstdisk --overwritevmfs\n')
if __name__ == '__main__':
main()
@@ -45,7 +45,10 @@ try:
cfg['ipv4_gateway'] = ncfg['ipv4_gateway']
except Exception:
pass
netline = 'network --hostname={0} --bootproto={1}'.format(nodename, cfg['ipv4_method'])
if cfg['ipv4_method'] == 'static':
netline = 'network --hostname={0} --bootproto={1}'.format(nodename, cfg['ipv4_method'])
else:
netline = 'network --bootproto=dhcp'
if vmnic:
netline += ' --device={0}'.format(vmnic)
if cfg['ipv4_method'] == 'static':
@@ -1,9 +1,12 @@
#!/bin/sh
mv /etc/confluent/confluent.deploycfg /etc/confluent/confluent.newdeploycfg
/opt/confluent/bin/apiclient /confluent-public/os/$profile/scripts/makeksnet >> /tmp/makeksnet
/opt/confluent/bin/apiclient /confluent-public/os/$profile/scripts/getinstalldisk >> /tmp/getinstalldisk
mv /etc/confluent/confluent.newdeploycfg /etc/confluent/confluent.deploycfg
chmod +x /tmp/makeksnet
/tmp/makeksnet > /tmp/ksnet
localcli system hostname set --host $node
python3 /tmp/getinstalldisk
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
echo rootpw --iscrypted $rootpw > /tmp/rootpw
export BOOT_CMDLINE=ks=/etc/confluent/ks.cfg
@@ -56,7 +56,7 @@ cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
TRIES=0
touch /etc/confluent/confluent.info
TRIES=5
echo -n "Waitiing for disks..."
echo -n "Waiting for disks..."
while [ ! -e /dev/disk/by-label ] && [ $TRIES -gt 0 ]; do
sleep 1
TRIES=$((TRIES - 1))
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -0,0 +1,29 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
# owner, group, and permissions in octal notation:
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
# contain the data in the source already in its entirety. This allows append in a fashion that
# is friendly to being run repeatedly
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -8,7 +8,9 @@ for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed
fi
done
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if grep confluent_imagemethod=untethered /proc/cmdline > /dev/null; then
TETHERED=1
if grep -q confluent_imagemethod=untethered /proc/cmdline || grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
TETHERED=0
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
@@ -44,15 +46,46 @@ fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
modprobe xfs
mkdir /sysroot
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
if ! grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
modprobe xfs
mkfs.xfs /dev/zram0 > /dev/null
if [ "$TETHERED" = 1 ]; then
mount -o discard /dev/zram0 /mnt/overlay
else
mount -o discard /dev/zram0 /sysroot
fi
mount -o discard /dev/zram0 /mnt/overlay
elif grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
mount -t tmpfs disklessroot /sysroot
fi
if [ "$TETHERED" = 0 ]; then
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
cp -a /mnt/remote/* /sysroot/
umount /mnt/remote
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
wait
echo -e "Decrypting and extracting root filesystem: 100%"
elif [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
@@ -68,6 +101,7 @@ cp /root/.ssh/* /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cp /etc/confluent/* /sysroot/etc/confluent/
cp /etc/ssh/*key* /sysroot/etc/ssh/
cp /tls/* /sysroot/etc/ssl/certs
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
@@ -0,0 +1,29 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
# owner, group, and permissions in octal notation:
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
# contain the data in the source already in its entirety. This allows append in a fashion that
# is friendly to being run repeatedly
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -52,13 +52,40 @@ while ! grep NODENAME /custom-installation/confluent/confluent.info; do
ipconfig -d $MYIP::$MYGW:$MYNM::$NIC
echo $NIC > /tmp/autodetectnic
else
configure_networking
for dsrv in $deploysrvs; do
if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
deploysrvs=$dsrv
break
rmmod cdc_ether 2> /dev/null
while [ ! -f /run/confirmednic ]; do
ALLNETDEVS=$(ip a|grep LOWER_UP|grep MULTICAST|awk '{print $2}'|sed -e s/://)
rm -rf /run/net* /run/dhcpcd /var/lib/dhcpcd
for dev in $(ip a|grep MULTICAST|awk '{print $2}'|sed -e s/://); do
ip a flush $dev
echo 1 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode
echo 0 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode
done
unset DEVICE DEVICE6 IP IP6 dev
[ -z "$1" ] || DEVICE=$1
shift
configure_networking
echo $DEVICE > /tmp/autodetectnic
for dsrv in $deploysrvs; do
(if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
echo $dsrv > /run/confirmednic
break
fi) &
chkpid=$!
( sleep 10 && kill $chkpid ) &
timeoutpid=$!
wait $chkpid
kill $timeoutpid 2> /dev/null
unset chkpid timeoutpid
done
if [ ! -f /run/confirmednic ]; then
echo "No connectivity to deployment servers, retrying..."
[ -z "$1" ] && set -- $ALLNETDEVS
fi
done
deploysrvs=$(cat /run/confirmednic)
rm /run/confirmednic
fi
MGR=$deploysrvs
NODENAME=$(grep ^nodename: /tmp/idntmnt/cnflnt.yml | awk '{print $2}')

Some files were not shown because too many files have changed in this diff Show More