2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-02 15:36:05 +00:00
Commit Graph

3253 Commits

Author SHA1 Message Date
Markus Hilger 016e08fa63 Catch socket errors, not the socket class, while firmware applies
The retry around the firmware progress poll named socket.socket, which is not
an exception class, so the moment the request it guards actually failed Python
raised "catching classes that do not inherit from BaseException is not
allowed" in place of the error.

socket.error is OSError, which is what a failed poll raises and what the retry
below was written for.
2026-08-11 05:17:05 +02:00
Markus Hilger a325f65076 Pass the address family and type to getaddrinfo by keyword
The loop resolver takes only host and port positionally, so these three calls
raised "BaseEventLoop.getaddrinfo() takes 3 positional arguments but 5 were
given" every time they ran.

get_ipaddr and _find_service have no handler above them, so link local XCC
discovery and a targeted SSDP search both died outright. The snoop copy sits
under an except Exception, which swallowed it and left the MGTIFACE reply
unanswered instead.
2026-08-11 05:17:05 +02:00
Jarrod Johnson 1a4475f64e Merge pull request #270 from Obihoernchen/fix/fpc-sensor-enumeration
Fix FPC/SMM sensor enumeration
2026-08-10 18:31:20 -04:00
Jarrod Johnson 8cbfaa9662 Merge pull request #269 from Obihoernchen/fix/dangling-asyncio-tasks
Keep spawned asyncio tasks referenced
2026-08-10 18:30:47 -04:00
Jarrod Johnson 05e42a3b09 Merge pull request #268 from Obihoernchen/fix/osdeploy-local-trust-awaits
Await the coroutines in osdeploy local node trust setup
2026-08-10 18:28:38 -04:00
Markus Hilger f5ee86f97e Make the FPC sensor generators coroutines
get_sensor_names and get_sensor_descriptions reach get_psu_count for any
sensor whose table entry carries elementsfun, and get_psu_count is a
coroutine. As plain generators they could not await it, so range() was handed
the coroutine object and enumeration died with "'coroutine' object cannot be
interpreted as an integer".

Every DW612S has such entries, so nodesensors returned nothing for the
enclosure. get_sensor_descriptions was doubly broken: the Lenovo handler
already iterated it with async for, which a plain generator cannot satisfy.

Verified against a DW612S SMM (FPC variant 38). Before, descriptions raised at
the async for and readings raised partway through enumeration; after, both
return all 34 sensors, 19 of which are the PSU entries that never enumerated.
2026-08-11 00:20:49 +02:00
Markus Hilger 9e41fdc598 Hold the async HTTP handler task until it finishes
run_handler scheduled the coroutine that serves an async HTTP request and
dropped the returned task. The event loop only keeps a weak reference, so the
task could be collected while still pending, leaving the request unanswered
and "Task was destroyed but it is pending!" in the log.

The session already outlives the request in _asyncsessions, so it holds the
task in a set and discards it from a done callback.
2026-08-10 23:18:24 +02:00
Markus Hilger 151fb1efc8 Await the coroutines in osdeploy local node trust setup
local_node_trust_setup() called get_cluster_list() and sign_host_key() without
awaiting them, so "osdeploy initialize -l" aborted with "TypeError: cannot
unpack non-iterable coroutine object" before doing any work.

Both awaits have to land together: sign_host_key() is called in a loop that
unlinks the existing ssh_host_*_key-cert.pub before writing the new one, so
fixing only the unpack would delete every host certificate and then fail.
2026-08-10 23:05:44 +02:00
Jarrod Johnson 120050ae78 Perodically reopen the multicast sockets
It has been observed there are times where an ethernet switch is partially working with MLD snoop/IGMP snoop.  A workaround for the unreliable behavior seems to be to reassert multicast joins ever so often.

Give it a try to restart the SSDP sockets every minute.
2026-08-10 11:24:02 -04:00
Markus Hilger 1c675c5f24 Port the Eaton PDU plugin to asyncio
The plugin was written against the http.client based SecureHTTPConnection, and
when that went away the reference was pointed at the aiohttp WebConnection,
which shares the name and nothing else. Nothing in it could run: the transport
called an async request() without awaiting it and then reached for a
getresponse() the new class does not have, and three PDUClient methods that
were never coroutines were awaited by the entry points.

Two transports now, both local to this plugin. https is aiohttp and stays on
the event loop, since the cert verifier records new fingerprints through
tasks.spawn. http is http.client in a thread, with its own socket so it can
still ask for a smaller segment size before connect: aiohttp only takes a
socket factory from 3.12 on, newer than el9, el10, ubuntu 24.04 or Leap 16
ship. That side has no cert to verify and its credentials arrive already read,
so the thread touches nothing.

connect() establishes and authenticates, wc is just the accessor now, and
logout() no longer sends a session id it never obtained. update() reports an
unsupported element instead of raising NameError.

On the https side cookies follow aiohttp's domain rules and the one POST with
a body goes out as text/plain, where http.client replayed every cookie and
sent no content type. The http side is as before, and neither can be settled
without an Eaton PDU on the bench. Both transports were exercised against a
stand-in: login, outlet read and set, sensors, logout, and a clamped segment
size on the plaintext path.
2026-08-10 14:36:37 +02:00
Markus Hilger a53351a730 Give the virsh console loop a reason to wake
virEventRunDefaultImpl waits for an event that an idle domain need not
produce, so the thread could outlive a deactivation that reported success, and
every later activation was refused while it did. Registering a timeout is what
makes it return: measured, a thread with nothing registered was still running
four seconds after being asked to stop, and with a half second timer it came
out at once.
2026-08-10 14:36:37 +02:00
Markus Hilger 11dc8196b0 Keep hold of a virsh console thread that will not stop
Deactivation dropped its reference once the wait expired, whether or not the
thread had stopped, so the next activation started a second one and revived
the first by setting run_console again. The reference is cleared only when the
thread is really gone, and activation refuses with 0x80 while one is alive.
That makes the wait a courtesy rather than a correctness measure, so it drops
to a second.
2026-08-10 14:36:37 +02:00
Markus Hilger bda403766e Start and stop the virsh console thread with the payload
Activation started an event thread whichever way the base handler had just
answered, so a refusal started one anyway and an already active console got a
second. activated alone cannot tell the two refusals apart, being true
already on the already active path, so the value from before the call decides.

Deactivation joined that thread on the event loop, where it could stall every
other session and its own response. The wait moves off the loop and is
bounded, and the thread is a daemon.
2026-08-10 14:36:37 +02:00
Markus Hilger 42ae44d5bf Finish the server side SOL and cleanup paths
The console awaits its output handler, but both sample BMCs supplied a plain
function, and both dropped the send_data coroutine. virshbmc additionally
receives its stream callback on a libvirt thread, so the send goes through
run_coroutine_threadsafe against the loop captured at activation, called
asyncloop because the class already has a loop method it uses as a thread
target.

ServerConsole asked the session layer to retry, which a ServerSession cannot
do: it never runs Session.__init__, so it has no timeout, and its _timedout
does nothing. IpmiServer.logout was synchronous and an argument short while
_cleanup awaits logout(False). The boot options handler answered, then read an
unbound name and answered again with 0xff.
2026-08-10 14:36:37 +02:00
Markus Hilger 8521c6ad4b Port the IPMI server side to asyncio
bmc.py, serversession.py, fakebmc.py and virshbmc.py were byte identical to
upstream pyghmi: the async port went through the session layer beneath them
and left the server side alone. So every response created a coroutine and
dropped it, and the overrides the now async parent awaits returned None.
Running fakebmc bound no socket, spun a core, and answered nothing.

Everything that sends a response is a coroutine now, and so is the dispatch
that reaches it; the hooks a subclass implements stay ordinary functions, so
an out of tree Bmc is unaffected unless it overrides the payload handlers.
Two things had to leave their constructors, both being coroutines: assigning
the server socket, into bind(), which is why listen() is no longer a
classmethod, and answering the open session request, into
send_open_session_response.

Verified against upstream with ipmitool over nine commands, with identical
output. SOL is not covered: fakebmc reports the payload disabled on both.
2026-08-10 14:36:37 +02:00
Markus Hilger 7ecc2b2818 Replace the housekeeping thread with a loop owned task
Housekeeper dates from when this work was a blocking select loop. Under
asyncio the event loop is already that place, and a thread around it takes a
captured loop reference, a scheduling step before the thread starts, and a
daemon flag, only to sit waiting on a coroutine that never returns with no way
to stop it. A task runs on the right loop by construction and cancels. The
loop is looked up before the coroutine is built, so calling this without one
raises rather than stranding it.

Nothing in this tree used the class. Out of tree callers need
start_housekeeping() instead, and gain the ability to stop it.
2026-08-10 14:36:37 +02:00
Markus Hilger e828ed4ff4 Close the TSM console web session
TsmConsole created an aiohttp ClientSession and never closed it, and leaked it
again when ws_connect failed. Neither was reachable before the connection path
was repaired. It is closed on both paths, and starts as None so that closing
before a connect does not trip over a missing attribute.
2026-08-10 14:36:37 +02:00
Markus Hilger c9d7343e02 Feed console input through a queue
A task per read swallowed failures, could deliver keystrokes out of order, and
at end of input returned with the reader still registered, so a level
triggered selector called it again for the same EOF. The reader queues now,
one consumer sends in order, and it is gathered with the main loop so a
failure reaches the caller.
2026-08-10 14:36:37 +02:00
Markus Hilger 91960527aa Repair the TSM console connection
Three faults in the same few lines. The redfish Command lost its constructor
for an async create, so building one raised TypeError, which the except below
reported as TargetEndpointUnreachable. await_redirect is defined nowhere in
this repository's history, so that call raised too; create performs the
session setup it was meant to trigger. And oem is a coroutine method rather
than an attribute, with its web connection coming from get_wc, which is what
performs the login that sets csrftok.
2026-08-10 14:36:37 +02:00
Markus Hilger e1071317ed Create shell sessions through the async factory
ConsoleSession grew an async create and lost its constructor, and ShellSession
inherits that. sockapi was updated for the console branch but not the shell
branch immediately below it, so opening a shell session raised TypeError. It
is the only place in the tree that builds one.
2026-08-10 14:36:37 +02:00
Markus Hilger dfde5736e9 Stop the aiohmi event loop from spinning when it has nothing to do
Command.eventloop called wait_for_rsp with no timeout. With nothing waiting or
being kept alive there is no deadline to derive one from, so it returns
without suspending and the loop runs flat out, measured at over 100000
iterations in two tenths of a second.

MAX_IDLE gives it something to wait on, as a ceiling rather than a fixed
delay: real deadlines still shorten it and an arriving packet still ends it
early.
2026-08-10 14:36:37 +02:00
Markus Hilger b996a30a44 Finish porting pyghmicons to async
The Console was never connected, so main_loop ran against a session that had
never been established. Input arrived on a thread that called send_data and
dropped the coroutine; the thread is gone and the loop watches stdin with
add_reader instead. The output handler was a plain function that
Console._print_data awaits.
2026-08-10 14:36:37 +02:00
Markus Hilger ec56e2c67c Rebuild pyghmiutil on the async command API
Command lost its constructor for an async create classmethod, so the utility
raised TypeError before connecting. The onlogon callback it was built around
is gone as well: create establishes the session itself, so both the callback
and the eventloop that waited for it are unnecessary.

docommand awaited nothing, so every operation produced a coroutine that was
printed and dropped, and three of its calls are async generators. Each BMC is
handled in turn now rather than only the last.
2026-08-10 14:36:37 +02:00
Markus Hilger 8428a47d69 Await the console output flushes
ServerConsole._got_sol_payload and Console._got_cons_input both flushed
pending output without awaiting the flush, so nothing was written.
2026-08-10 14:36:37 +02:00
Markus Hilger b96d4b4103 Give the aiohmi command line utilities an event loop
Console.main_loop drives Session.wait_for_rsp, which is a coroutine, so it
spun without ever waiting for a packet. It is a coroutine now, and pyghmicons
runs its main under asyncio.run. pyghmiutil had the same shape around
Command.eventloop.
2026-08-10 14:36:37 +02:00
Markus Hilger 92c9abc74a Await the remaining reachable coroutines
sockapi sent its collective refusal without awaiting tlvdata.send. redfish
handle_sensors returned a coroutine from most branches and None from the short
ones, so the caller's await raised TypeError; it is a coroutine throughout
now. console send_payload waited for a response without awaiting the wait.

Two suppressions are pyrefly limitations rather than bugs: Session defines an
async __new__, and the keepalive registry holds coroutine functions in an
untyped dict.
2026-08-10 14:36:37 +02:00
Markus Hilger 2ea7aed2cc Port the SMM handler, Delta PDU logout and XCC config to async
Taken from fix/asyncio-port-critical, limited to what pyrefly reports.

The SMM handler still used the httplib style connect/request/getresponse that
the async webclient does not have, so nothing was ever sent. It goes through
grab_response_with_status now, with allow_redirects=False to preserve httplib
behaviour, hence the new webclient parameter. That also fixes a login passing
its headers as urlencode's second positional argument.

Delta PDU's logout was a plain function both callers awaited, so the power
paths raised TypeError. XCC's set_system_configuration was the last
synchronous implementation of a method every caller awaits.
2026-08-10 14:36:37 +02:00
Markus Hilger 9148a9e1cf Run the module self tests through asyncio
These __main__ blocks called coroutines as if they were functions, so they did
nothing at all. Single calls go through asyncio.run; sshutil, proxmox and
vcenter needed an _selftest coroutine. Two were invisible to pyrefly because
repr() and list() count as using the result: vcenter's get_vm_serial needs an
await, and proxmox's get_vm_inventory is an async generator.

lldp called _extract_neighbor_data twice, once correctly, so the bare call is
dropped. xcc3.remote_nodecfg is not a self test: every other handler defines
it as a coroutine and selfservice.py awaits it.
2026-08-10 14:36:37 +02:00
Markus Hilger e6f6b2330f Parse SMM answers as bytes, not as decoded text
lxml refuses a str carrying an encoding declaration, and the SMM declares one
when it answers /data/login, so _webconfigcreds has raised ValueError on the
first thing it does after logging in ever since the switch to lxml. stdlib
ElementTree took the same input, which is why it went unnoticed. fromstring
already means to take either shape, so encode there. Confirmed against a
DW612S.
2026-08-10 14:27:14 +02:00
Markus Hilger c6c2d3112e Tidy comparisons, statement layout and a redundant alias (E711, E712, E701, PLC0414)
Hand written rather than autofixed, since three of the four need the
surrounding code read to be sure they are equivalent:

- confetty: `powerstate == None` -> `is None`.
- nodeconfig: `setmode != True` / `!= False` -> `not setmode` / `setmode`.
  Safe because setmode only ever holds None, True or False, and the two
  lines above each test normalise None away first.
- pam: split two `if cond: stmt` one-liners.
- imgutil: `from shutil import copytree as copytree`, an alias that renames
  nothing.  Not a re-export marker, this is a script.
2026-08-10 05:32:00 +02:00
Markus Hilger 644843b892 Remove unused imports and pointless f-string prefixes (F401, F541, E713)
Entirely mechanical, produced by `ruff check --fix --select F401,F541,E713`
and reviewed rather than taken on faith: deleting an import is only safe if
nothing imports it for its side effects or re-exports it.  None of the 19
removed names is referenced anywhere in its file, none appears in any string
literal, and none of the touched files uses eval, exec, globals() or
__import__, so there is no dynamic lookup that could reach them.
2026-08-10 05:32:00 +02:00
Markus Hilger 938070c5b7 Skip pending nodes that have no handler
The guard evaluated the `next` builtin and discarded it, which does
nothing, so a pending node with no handler fell through to
None.NodeHandler(...).  The AttributeError was caught by the enclosing
except and logged as "Unexpected error during discovery", turning a node
that should have been quietly skipped into a spurious error in the log.
2026-08-10 05:32:00 +02:00
Markus Hilger 607845bacf Stop the plugin loader from shadowing the plugin module (F402)
load_plugins() used `plugin` as the loop variable for plugin file names,
which shadows `import confluent.plugin as plugin` for the whole function.
Nothing in the function needed the module, so this was latent rather than
broken, but the next line that does need it would have failed oddly.
2026-08-10 05:32:00 +02:00
Markus Hilger 9984bff909 Fix the dedicated hotspare drive list (B035)
The DedicatedSpareDrives payload was built as a set containing a list
containing a dict comprehension with a constant key, so it collapsed to a
single entry and then raised TypeError on the unhashable list.  Build a
list of drive references, the same shape as the Drives list just above it.
2026-08-10 05:32:00 +02:00
Markus Hilger b119de345b Remove shadowed duplicate definitions (F811)
Three names were defined twice in the same scope, so the first definition
was unreachable:

- lenovo OEM handler: two set_user_access methods, the second silently
  replacing the first.  That made the SMM privilege update dead code.  The
  conditions are mutually exclusive (is_fpc returns None once has_xcc is
  true), so merge both into the surviving method.
- redfish plugin handle_cert_authorities and prepfish
  disable_host_interface: byte identical copies, drop the redundant one.
2026-08-10 05:32:00 +02:00
Markus Hilger 5d9e30de7b Stop loop variables from shadowing what they iterate (B020)
Each of these loops rebinds the name that holds the iterable.  They work
today because the iterable is evaluated once before the loop starts, but
the name is then gone, so any later use reads a loop item instead of the
collection.

- nodeinventory: `for arg in args` / `for arg in arg.split(',')`.
- confignet (common and debian copies): iname holds the comma separated
  interface list and is then reused for each interface in it.
- xcc _get_agentless_firmware: adata holds the adapter query response and
  is then reused for each adapter.

No behaviour change, just distinct names for distinct things.
2026-08-10 05:32:00 +02:00
Markus Hilger 8a3fce85c0 Fix undefined names (F821)
Every one of these raises NameError if its code path is reached:

- nodeapply: run_automation accumulated into an exitcode that only existed
  in run(), so any automation error crashed instead of being reported.  It
  now keeps and returns its own, tracked separately from the exit code of
  the ssh commands: the early exit after the spawn loop tests that one,
  and folding automation failures into it would exit with children already
  running and their pipes abandoned.  Both are reported at the real exits.
- nodeconsole: redraw() reads firstnodename, which was local to
  do_screenshot(); promote it to a module global like the other drawing
  state.
- nodedeploy: the redeploy path appended to a lockednodes list that did not
  exist yet.  The block that follows re-reads the same lock state and acts
  on it, so drop the dead duplicate.
- samples/nodeattrib_from_switch.py, misc/filterpasswd: missing import sys.
- xcc3: fixuuid was never imported.  xcc imports xcc3, so take a local copy
  the way the smm handler does instead of creating an import cycle.
- httpapi: the async session call still passed the WSGI-era env and an
  extra argument to handle_async(), which has taken only querydict since
  the aiohttp port.  Calling it correctly exposed that handle_async()
  registers an AsyncSession before raising on the discontinued long poll
  path, so every request to it would leak a session that is never reaped.
  It now only creates one when there is a websocket handler to yield it to.
- messages: the InputFirmwareUpdate.filename property checked
  self.filebynode[node] with no node in scope.  __init__ already validates
  every expanded path and nodefile() rechecks per node, so drop the checks.
- pam: drop the python2 branches referencing unicode and raw_input.  The
  server has been python3 only since the asyncio port.
- cooltera: the sensor-name listing referenced a nonexistent sensors dict.
  The available sensors depend on the model, which is only known after
  reading the device, so list them from the same status data the readings
  use.
- deltapdu, eatonpdu, geist: the not-implemented response in update() used
  node outside the loop, unlike retrieve() in the same files and unlike
  raritan/enlogic.
- confluentdbgcli: stray self. on a module-level socket connect.
2026-08-10 05:32:00 +02:00
Jarrod Johnson 94c1683663 Add support for specifying tpm2 pcrs in the encryptboot attribute
This allows a user to opt into pcrs if they understand what they are doing.

Some PCRs are sensitive to firmware updates and some are sensitive to boot loader, kernel, boot config, or initramfs.  All of these are an opportunity for an unsuspecting update to remove access to the boot volume.  There are update processes that can be put into place to make this work,
but it is up to the OS update process to address that, and
OS update processes are likely not to address that at this time.
2026-08-06 16:07:19 -04:00
Markus Hilger 4570d9f8af Throttle the insecure mode boot refusal log
reply_dhcp4 logs the insecure mode remediation hint on every DHCP
discover it refuses.  A node in this state never receives a reply, so it
retries for as long as it is powered on and the same message repeats
every few seconds.

Rate limit it per hardware address the way the neighbouring boot attempt
messages already do, reusing the ignoremacs window that check_reply uses
for the missing profile hint.
2026-08-05 03:43:14 +02:00
Markus Hilger ad2d021fcc Restore proxyDHCP log throttling
The per-MAC 90 second log throttle in proxydhcp has been inert: the
`skiplogging = True` reset sat in relay_proxydhcp, where it is a dead
local, while the loop in proxydhcp only ever assigns False.  Once the
first packet is handled the flag stays False for the life of the
process, so every retransmitted boot request logs again even though
ignoredisco is updated to suppress it.

Reset the flag at the top of each loop iteration instead, next to the
timestamp check it belongs to, and drop the dead assignment.
2026-08-05 03:43:14 +02:00
Markus Hilger a7b476b3fc Ignore UEFI HTTP boot on ProxyDHCP port 2026-08-05 03:43:14 +02:00
Markus Hilger fea71a0ce4 Honor deployment.useinsecureprotocols for ProxyDHCP boot
reply_dhcp4 declines to answer a PXE boot request unless
deployment.useinsecureprotocols is set to firmware or always, but
proxydhcp had no such check. A node left at the default of never was
therefore still offered a TFTP bootfile and a plain http boot.ipxe URL
whenever the request arrived on port 4011 rather than port 67, so the
attribute silently did nothing in ProxyDHCP deployments alongside an
independent DHCP server.

Apply the same gate, including the UEFI HTTP boot exemption, and log the
same remediation hint. The node attributes are now fetched once and
passed through to get_deployment_profile instead of being looked up
again there.

Requests whose architecture could not be determined are ignored rather
than falling through to the reply. opts_to_dict stops parsing before the
client architecture option whenever the message type is not a request,
and such a packet would otherwise reach the iPXE branch and be handed a
plain http boot.ipxe URL without ever passing the gate.
2026-08-05 03:43:14 +02:00
Markus Hilger 271e3b4d93 Preserve attributes in syncfiles without disturbing parent directories
The rsync push carried no preservation flags, so files arrived with their
special permission bits explicitly disabled and a setuid/setgid entry could
only be honored by the permissions= chmod on the client side.

Preservation was turned on once before in e52a9ff70f ("Have syncfiles
attempt to preserve more") and rolled back the same day in c0287e93ed
("Roll back rsync ownership"), because rsync also applied the staging copy's
attributes to the parent directories it merely traversed on the way to the
synced files, clobbering the permissions of system directories such as /etc.
Naming every staged file explicitly through --files-from and adding
--no-implied-dirs confines preservation to the content actually being
synchronized, leaving traversed directories alone and creating missing ones
with default attributes.

Two details follow from the way the staging tree is built. Files are staged as
symlinks, so rsync reads their attributes through to the real file, but
directories are staged as directories and need the source attributes copied
onto them for the otherwise empty ones that have to be named explicitly.
Ownership is mapped from the account the daemon runs as to root, since that
account generally does not exist on the node and would otherwise arrive as a
meaningless numeric id.

--xattrs from that earlier attempt is deliberately left out: with --copy-links
rsync reads xattrs off the symlink rather than its referent, so it transfers
nothing here while adding a failure mode on hosts without xattr support.
2026-08-04 05:59:57 +02:00
Markus Hilger c0bc33e494 Report syncfiles failures instead of discarding them
get_syncresult() caught the sync task's exception, logged a repr server
side and returned 200 OK with a null body.  The node then called
.get('options') on that null resulted in:

  c1: 'NoneType' object has no attribute 'get'

and syncfileclient still exited 0 as if syncing had succeeded.

Return the error to the requestor as a 500 with an error payload.  On
the node, unwrap the body that grab_url_with_status raises for a
non-success status, print it once and exit non-zero.  Only a failure the
server deliberately reported for this sync is terminal. Anything else,
such as a dropped connection, is re-raised so the existing retry loop
handles it as before.  The same case now reports

  c1: Error performing syncfiles: Syncing failed due to unreadable files: /etc/dangling.conf
  c1: 'syncfileclient' exited with code 1
2026-08-03 15:17:15 +02:00
Jarrod Johnson 4653d3f959 Move ssh scratch location out of /tmp
/tmp is sometimes locked down, move it into the runtime directory instead.
2026-07-29 15:44:39 -04:00
Jarrod Johnson 5111652e01 Fix spurious log on impossible passkey requests 2026-07-29 09:03:56 -04:00
Jarrod Johnson d7dcb07a3f Implement deployment.storage
This is an attribute for a node to indicate preferences for storage.

For now, 'm2' policy will hit m.2 and mirroring kits.
2026-07-28 15:02:27 -04:00
Jarrod Johnson 035d6849e8 Merge pull request #257 from Obihoernchen/lenovo-async
Fix the NextScale SMM web path on the asyncio port
2026-07-28 08:48:50 -04:00
Jarrod Johnson 86e4603b82 Merge pull request #258 from Obihoernchen/imgutil-async
imgutil: fix async-port fallout in the image pack/capture path
2026-07-28 08:45:18 -04:00
Markus Hilger a9d7b67929 Derive build versions from a tracked VERSION file
Release tags do not live on master: 3.15.2 through 3.15.6 were tagged on branch
3.15, so git describe reaches only 3.15.1 and dev builds were stamped
3.15.2.dev<n>. Besides being confusing, rpm and dpkg both rank the released
3.15.6 above that, so a dev package will not install over a released one.

Add a top-level VERSION file naming the release the branch is working toward
(4.0.0 on master) and a mkversion helper that stamps packages from it, keeping
the tag-derived value as a floor so a forgotten bump cannot go backwards.
mkversion also replaces the block copy-pasted into seven build scripts, and
makesetup no longer writes a per-package VERSION file, so the stale checked-in
confluent_common/VERSION goes with it.
2026-07-27 20:06:22 +02:00