FreeNAS/freenas 6169061src/middlewared/middlewared main.py, src/middlewared/middlewared/plugins support.py usage.py

Convert system.vendor to the typesafe port pattern

## Context
system.vendor was an old-style service with inline BaseModel args/results and @api_method-wrapped methods. All three methods (name, unvendor, is_vendored) are private with no over-the-wire surface and there is no datastore, so it fits the fully-private port pattern — a lean private shim delegating to plain, fully type-annotated module functions — rather than a Generic*/Pydantic conversion. The wire shapes (str | None, bool, None) are unchanged.

## Solution
- vendor.py is now a plain typed logic module (get_vendor, remove_vendor_file, is_vendored); get_vendor stays importable there for scripts/vendor_service.py. The lean VendorService shim in __init__.py keeps the try/except + logging orchestration and delegates to it, with Config private. unvendor keeps the etc.generate string call since that's CtxMethod dynamic dispatch.
- Registered VendorService on SystemServicesContainer in main.py so it resolves as self.s.system.vendor.
- Switched the six in-process callers (support x2, usage x2, nvmet.subsys, device_ netlink events) from string middleware.call to call2/call_sync2 against the typed method handle.
- Added the plugin to the mypy workflow list.
DeltaFile
+4-57src/middlewared/middlewared/plugins/system_vendor/vendor.py
+40-0src/middlewared/middlewared/plugins/system_vendor/__init__.py
+2-2src/middlewared/middlewared/plugins/support.py
+2-2src/middlewared/middlewared/plugins/usage.py
+1-1src/middlewared/middlewared/plugins/nvmet/subsys.py
+2-0src/middlewared/middlewared/main.py
+51-622 files not shown
+53-638 files

FreeNAS/freenas 63dfe0asrc/middlewared/middlewared/plugins/container info.py, src/middlewared/middlewared/plugins/system product.py

Consolidate license feature checks into truenas.license.feature_available

This commit adds changes to route every "is the system licensed to use feature X" check through a single truenas.license.feature_available method, with the surrounding hardware/product gating captured as a FeaturePolicy enum (ANY, ENTERPRISE, HA_APPLIANCE, IX_HARDWARE) instead of being re-implemented at each call site. system.feature_enabled and system.sed_enabled now just delegate to it.

As part of this the SED check becomes legacy-license aware (it previously consulted only the daemon) and feature expiry is now honored everywhere, while the license is still never consulted on hardware where a feature was never gated, so apps and VMs stay unrestricted off HA/iX appliances exactly as before.

Legacy on-disk licenses grant their feature bits perpetually -- contract_end there is only the support-contract end, after which the system stays fully functional -- so those features are no longer stamped with that date as their expiry; only SUPPORT tracks contract_end. Without this, enforcing expiry would have wrongly disabled SED, FC, VMs and apps on legacy-licensed systems past their support contract.
DeltaFile
+310-0src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_feature_available.py
+46-0src/middlewared/middlewared/plugins/truenas/license.py
+1-19src/middlewared/middlewared/pytest/unit/plugins/test_vm.py
+19-0src/middlewared/middlewared/plugins/truenas/license_utils.py
+5-9src/middlewared/middlewared/plugins/container/info.py
+3-9src/middlewared/middlewared/plugins/system/product.py
+384-379 files not shown
+419-6115 files

FreeNAS/freenas 0fad4e3src/middlewared/middlewared/plugins/container info.py, src/middlewared/middlewared/plugins/system product.py

Consolidate license feature checks into truenas.license.feature_available

This commit adds changes to route every "is the system licensed to use feature X" check through a single truenas.license.feature_available method, with the surrounding hardware/product gating captured as a FeaturePolicy enum (ANY, ENTERPRISE, HA_APPLIANCE, IX_HARDWARE) instead of being re-implemented at each call site. system.feature_enabled and system.sed_enabled now just delegate to it.

As part of this the SED check becomes legacy-license aware (it previously consulted only the daemon) and feature expiry is now honored everywhere, while the license is still never consulted on hardware where a feature was never gated, so apps and VMs stay unrestricted off HA/iX appliances exactly as before.
DeltaFile
+310-0src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_feature_available.py
+46-0src/middlewared/middlewared/plugins/truenas/license.py
+1-19src/middlewared/middlewared/pytest/unit/plugins/test_vm.py
+19-0src/middlewared/middlewared/plugins/truenas/license_utils.py
+5-9src/middlewared/middlewared/plugins/container/info.py
+3-9src/middlewared/middlewared/plugins/system/product.py
+384-377 files not shown
+408-5713 files

FreeNAS/freenas dfb3eadsrc/middlewared/middlewared/plugins/cloud_backup crud.py __init__.py

Convert cloud_backup plugin to the typesafe pattern

This commit adds changes to convert the cloud_backup plugin to the typesafe service/part pattern, so query and get_instance return Pydantic models, public methods use @api_method(check_annotations=True), and same-process calls go through call2/call_sync2.

The shared CloudTaskServiceMixin is left untyped since cloud_sync still depends on it, with a single sibling-safe edit to its zvol validation path. All in-process consumers were updated for model access: the cloud_sync credential delete check, the cron.d mako, and the path-resolution migration. Since the password is a Secret field, the create/update and restic paths dump with expose_secrets so an unchanged password isn't written back as the redaction string.
DeltaFile
+89-149src/middlewared/middlewared/plugins/cloud_backup/crud.py
+179-0src/middlewared/middlewared/plugins/cloud_backup/__init__.py
+73-82src/middlewared/middlewared/plugins/cloud_backup/sync.py
+65-83src/middlewared/middlewared/plugins/cloud_backup/snapshot.py
+60-63src/middlewared/middlewared/plugins/cloud_backup/init.py
+38-37src/middlewared/middlewared/plugins/cloud_backup/restore.py
+504-41410 files not shown
+579-43216 files

FreeNAS/freenas 89758c2src/middlewared/middlewared/plugins/container info.py, src/middlewared/middlewared/plugins/system product.py

Consolidate license feature checks into truenas.license.feature_available

This commit adds changes to route every "is the system licensed to use feature X" check through a single truenas.license.feature_available method, with the surrounding hardware/product gating captured as a FeaturePolicy enum (ANY, ENTERPRISE, HA_APPLIANCE, IX_HARDWARE) instead of being re-implemented at each call site. system.feature_enabled and system.sed_enabled now just delegate to it.

As part of this the SED check becomes legacy-license aware (it previously consulted only the daemon) and feature expiry is now honored everywhere, while the license is still never consulted on hardware where a feature was never gated, so apps and VMs stay unrestricted off HA/iX appliances exactly as before.
DeltaFile
+310-0src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_feature_available.py
+46-0src/middlewared/middlewared/plugins/truenas/license.py
+1-19src/middlewared/middlewared/pytest/unit/plugins/test_vm.py
+19-0src/middlewared/middlewared/plugins/truenas/license_utils.py
+5-9src/middlewared/middlewared/plugins/container/info.py
+3-9src/middlewared/middlewared/plugins/system/product.py
+384-376 files not shown
+407-5612 files

FreeNAS/freenas e6e04e9src/middlewared/middlewared/plugins support.py, src/middlewared/middlewared/plugins/alert runtime.py

Convert support plugin to typesafe pattern

## Context
The support plugin was an old-style dict-based `ConfigService`. This converts it to the typesafe pattern: a lean `GenericConfigService[SupportEntry]` service class delegating to a `ConfigServicePart`, with `generic = True`, `check_annotations=True` on every public method, and typed `call2` for same-process calls.

## Solution
- **Package split**: `plugins/support.py` becomes `plugins/support/` with `__init__.py` (lean service), `config.py` (`SupportModel` + `SupportConfigServicePart` holding `do_update`/`validate`), and `execute.py` (the `post` helper plus the `similar_issues`/`new_ticket`/`attach_ticket` logic as `ServiceContext`-typed functions).
- **API models**: decoupled `SupportAttachTicketArgs` from `@single_argument_args` into an explicit `SupportAttachTicket` inner model plus a plain wrapper (wire shape unchanged) so the method param can be annotated and field-accessed under `check_annotations`; exported every directly-imported model in `__all__`.
- **Registration**: registered the service in `main.py`'s `ServiceContainer` and added the plugin dir to `mypy.yml`.
- **Internal consumers**: `alert/source/proactive_support.py`, `alert/runtime.py`, and `truenas/tn.py` now use attribute access on the returned `SupportEntry` and typed `call2`/`context.call2` (constructing `SupportNewTicketEnterprise`) instead of dict access and string `middleware.call`.

The public wire shape is unchanged; live verification on the test VM confirmed read-only outputs, the update round-trip, the required-field validation path, and the ProactiveSupport alert consumer all behave identically to before.
DeltaFile
+0-354src/middlewared/middlewared/plugins/support.py
+239-0src/middlewared/middlewared/plugins/support/execute.py
+159-0src/middlewared/middlewared/plugins/support/__init__.py
+39-0src/middlewared/middlewared/plugins/support/config.py
+18-17src/middlewared/middlewared/plugins/alert/runtime.py
+15-12src/middlewared/middlewared/plugins/truenas/tn.py
+470-3834 files not shown
+491-39810 files

FreeNAS/freenas 024f970src/middlewared/middlewared/plugins/support execute.py __init__.py

Apply ruff formatting to new support package files

## Context
ruff's `format --diff` CI check only runs on git-added files and, with no quote-style configured, enforces its default (double quotes), so the newly added `support/` package needs reformatting once committed.

## Solution
Ran `ruff format` on the three new files in `plugins/support/`; pure style changes (quote style and call-argument wrapping), no logic changes.
DeltaFile
+73-66src/middlewared/middlewared/plugins/support/execute.py
+37-28src/middlewared/middlewared/plugins/support/__init__.py
+6-6src/middlewared/middlewared/plugins/support/config.py
+116-1003 files

FreeNAS/freenas 335f131src/middlewared/middlewared main.py, src/middlewared/middlewared/plugins/support execute.py

fix pipes mypy
DeltaFile
+3-3src/middlewared/middlewared/plugins/support/execute.py
+1-1src/middlewared/middlewared/main.py
+4-42 files

FreeNAS/freenas 93b7395src/middlewared/middlewared/plugins support.py, src/middlewared/middlewared/plugins/alert runtime.py

Convert support plugin to typesafe pattern

## Context
The support plugin was an old-style dict-based `ConfigService`. This converts it to the typesafe pattern: a lean `GenericConfigService[SupportEntry]` service class delegating to a `ConfigServicePart`, with `generic = True`, `check_annotations=True` on every public method, and typed `call2` for same-process calls.

## Solution
- **Package split**: `plugins/support.py` becomes `plugins/support/` with `__init__.py` (lean service), `config.py` (`SupportModel` + `SupportConfigServicePart` holding `do_update`/`validate`), and `execute.py` (the `post` helper plus the `similar_issues`/`new_ticket`/`attach_ticket` logic as `ServiceContext`-typed functions).
- **API models**: decoupled `SupportAttachTicketArgs` from `@single_argument_args` into an explicit `SupportAttachTicket` inner model plus a plain wrapper (wire shape unchanged) so the method param can be annotated and field-accessed under `check_annotations`; exported every directly-imported model in `__all__`.
- **Registration**: registered the service in `main.py`'s `ServiceContainer` and added the plugin dir to `mypy.yml`.
- **Internal consumers**: `alert/source/proactive_support.py`, `alert/runtime.py`, and `truenas/tn.py` now use attribute access on the returned `SupportEntry` and typed `call2`/`context.call2` (constructing `SupportNewTicketEnterprise`) instead of dict access and string `middleware.call`.

The public wire shape is unchanged; live verification on the test VM confirmed read-only outputs, the update round-trip, the required-field validation path, and the ProactiveSupport alert consumer all behave identically to before.
DeltaFile
+0-354src/middlewared/middlewared/plugins/support.py
+232-0src/middlewared/middlewared/plugins/support/execute.py
+150-0src/middlewared/middlewared/plugins/support/__init__.py
+39-0src/middlewared/middlewared/plugins/support/config.py
+18-17src/middlewared/middlewared/plugins/alert/runtime.py
+15-12src/middlewared/middlewared/plugins/truenas/tn.py
+454-3834 files not shown
+474-39710 files

FreeNAS/freenas aad4520src/middlewared/middlewared/plugins/cloud_backup crud.py __init__.py

Convert cloud_backup plugin to the typesafe pattern

This commit adds changes to convert the cloud_backup plugin to the typesafe service/part pattern, so query and get_instance return Pydantic models, public methods use @api_method(check_annotations=True), and same-process calls go through call2/call_sync2.

The shared CloudTaskServiceMixin is left untyped since cloud_sync still depends on it, with a single sibling-safe edit to its zvol validation path. All in-process consumers were updated for model access: the cloud_sync credential delete check, the cron.d mako, and the path-resolution migration. Since the password is a Secret field, the create/update and restic paths dump with expose_secrets so an unchanged password isn't written back as the redaction string.
DeltaFile
+88-149src/middlewared/middlewared/plugins/cloud_backup/crud.py
+179-0src/middlewared/middlewared/plugins/cloud_backup/__init__.py
+73-82src/middlewared/middlewared/plugins/cloud_backup/sync.py
+65-83src/middlewared/middlewared/plugins/cloud_backup/snapshot.py
+60-63src/middlewared/middlewared/plugins/cloud_backup/init.py
+38-37src/middlewared/middlewared/plugins/cloud_backup/restore.py
+503-41410 files not shown
+578-43216 files

FreeNAS/freenas 14afe2bsrc/middlewared/middlewared/plugins/truecommand portal.py update.py

Convert truecommand plugin to typesafe pattern

This commit adds changes to convert the truecommand plugin to the typesafe pattern, splitting the old compound ConfigService into a lean GenericConfigService that delegates to a ConfigServicePart with Pydantic models, while the portal/wireguard/state logic moves into plain context-first functions and same-process calls use call2. In-process consumers of truecommand.config (truenas and security) switch from dict access to typed attribute access.
DeltaFile
+182-149src/middlewared/middlewared/plugins/truecommand/portal.py
+0-229src/middlewared/middlewared/plugins/truecommand/update.py
+98-102src/middlewared/middlewared/plugins/truecommand/wireguard.py
+184-0src/middlewared/middlewared/plugins/truecommand/config.py
+87-7src/middlewared/middlewared/plugins/truecommand/__init__.py
+45-0src/middlewared/middlewared/plugins/truecommand/state.py
+596-4876 files not shown
+610-53612 files

FreeNAS/freenas e4197c5src/middlewared/middlewared/plugins/truecommand config.py portal.py

Address reviews
DeltaFile
+5-1src/middlewared/middlewared/plugins/truecommand/config.py
+1-1src/middlewared/middlewared/plugins/truecommand/portal.py
+6-22 files

FreeNAS/freenas aaf8345src/middlewared/middlewared/alembic/versions/26.0 2026-06-19_00-00_restrict_totp_interval.py, src/middlewared/middlewared/api/v26_0_0 user.py

Restrict TOTP interval to supported values

This commit adds changes to restrict the per-user two-factor TOTP interval to 30 or 60 seconds, since the OATH users file consumed by pam_oath only understands those time-steps and any other value silently breaks 2FA for the user. A migration clears the secret and resets the interval for existing rows holding an unsupported value so affected users re-enroll, and the render-time coercion is dropped now that the input is validated at the API.
DeltaFile
+64-0src/middlewared/middlewared/alembic/versions/26.0/2026-06-19_00-00_restrict_totp_interval.py
+16-3tests/api2/test_twofactor_auth.py
+5-3src/middlewared/middlewared/api/v26_0_0/user.py
+1-5src/middlewared/middlewared/plugins/auth_/2fa.py
+86-114 files

FreeNAS/freenas b6cb3f0src/middlewared/middlewared/alembic/versions/27.0 2026-06-16_12-00_normalize_nic_mac.py, src/middlewared/middlewared/api/base/types network.py

NAS-141350 / 27.0.0-BETA.1 / Reject and normalize non-colon NIC MAC addresses (#19154)

## Problem
A custom NIC MAC entered with dash, no-separator, or mixed separators
(e.g. `10-66-6A-1F-F1-B1`) passed the permissive `mac` pattern but
libvirt's `defineXML` only parses colon-separated MACs, so the
container/VM saved fine and then failed to start with `XML error: unable
to parse mac address`. The colon-only `MACAddr(separator=':')` guard the
VM plugin used through electriceel was dropped when devices moved to the
pydantic models at fangtooth, and containers (26.0+) never had it, so
these values can already be sitting in `vm_device` and
`container_device`.

## Solution
- Tightened the shared `MACAddress` type to colon-only with a clear
message, and switched the v27 VM and Container NIC `mac` fields to use
it (removing the duplicated permissive inline pattern). Frozen API
versions are left as-is.
- Added a migration that normalizes existing NIC MACs in both

    [5 lines not shown]
DeltaFile
+65-0src/middlewared/middlewared/alembic/versions/27.0/2026-06-16_12-00_normalize_nic_mac.py
+57-0src/middlewared/middlewared/pytest/unit/api/base/types/test_mac_address.py
+2-2src/middlewared/middlewared/api/base/types/network.py
+2-2src/middlewared/middlewared/api/v27_0_0/container_device.py
+2-2src/middlewared/middlewared/api/v27_0_0/vm_device.py
+128-65 files

FreeNAS/freenas bab7eb3src/middlewared/middlewared/alembic/versions/27.0 2026-06-16_12-00_normalize_nic_mac.py, src/middlewared/middlewared/api/base/types network.py

Reject and normalize non-colon NIC MAC addresses

## Problem
A custom NIC MAC entered with dash, no-separator, or mixed separators (e.g. `10-66-6A-1F-F1-B1`) passed the permissive `mac` pattern but libvirt's `defineXML` only parses colon-separated MACs, so the container/VM saved fine and then failed to start with `XML error: unable to parse mac address`. The colon-only `MACAddr(separator=':')` guard the VM plugin used through electriceel was dropped when devices moved to the pydantic models at fangtooth, and containers (26.0+) never had it, so these values can already be sitting in `vm_device` and `container_device`.

## Solution
- Tightened the shared `MACAddress` type to colon-only with a clear message, and switched the v27 VM and Container NIC `mac` fields to use it (removing the duplicated permissive inline pattern). Frozen API versions are left as-is.
- Added a migration that normalizes existing NIC MACs in both `vm_device` and `container_device` to libvirt's canonical lowercase colon form, regenerating the rare value that isn't a real MAC. This is required because `*.device.query` re-validates rows through the model, so an un-normalized non-colon MAC would otherwise make `query` fail once the pattern is tightened. Normalization preserves the user's intended address and heals instances that were stuck failing to start.
DeltaFile
+65-0src/middlewared/middlewared/alembic/versions/27.0/2026-06-16_12-00_normalize_nic_mac.py
+57-0src/middlewared/middlewared/pytest/unit/api/base/types/test_mac_address.py
+2-2src/middlewared/middlewared/api/v27_0_0/vm_device.py
+2-2src/middlewared/middlewared/api/v27_0_0/container_device.py
+2-2src/middlewared/middlewared/api/base/types/network.py
+128-65 files

FreeNAS/freenas 7e48dacsrc/middlewared/middlewared/etc_files/local/ssh sshd_config.mako config.py, src/middlewared/middlewared/plugins ssh.py

Convert SSH plugin to typesafe pattern

## Context
Migrates the `ssh` plugin from the legacy dict-based `SystemServiceService` to the typesafe pattern, matching the `ups`/`ftp` shape.

## Solution
Split the single `ssh.py` into a package: a lean `SSHService` (`generic = True`) in `__init__.py` delegating to `SSHServicePart` in `config.py`, with the host-key helpers moved to plain functions in `keys.py`. `config`/`update` now return the `SSHEntry` Pydantic model in-process, so every internal consumer was updated: the `sshd_config` mako and the SSH `config.py` renderer `.model_dump()` the model at the top, and the in-process callers (`keychain`, `failover` nftables, the `service_` start/reload hooks, and the plugin's own `setup()`) were switched from string `middleware.call('ssh.…')` to typed `call2`/`call_sync2`. The only remaining string call is `etc.py`'s dynamic `CtxMethod` dispatch, which has no static method handle. Registered the service in `main.py` and added the package to the mypy workflow.
DeltaFile
+0-186src/middlewared/middlewared/plugins/ssh.py
+83-0src/middlewared/middlewared/plugins/ssh/__init__.py
+80-0src/middlewared/middlewared/plugins/ssh/keys.py
+66-0src/middlewared/middlewared/plugins/ssh/config.py
+19-22src/middlewared/middlewared/etc_files/local/ssh/sshd_config.mako
+9-15src/middlewared/middlewared/etc_files/local/ssh/config.py
+257-2236 files not shown
+273-23312 files

FreeNAS/freenas e00ab91src/middlewared/middlewared/plugins/vm capabilities.py

NAS-141493 / 27.0.0-BETA.1 / Annotate supported_archs to fix mypy inference with VMGuestArch keys (#19169)

This was introduced recently in PR #19167
DeltaFile
+1-1src/middlewared/middlewared/plugins/vm/capabilities.py
+1-11 files

FreeNAS/freenas 040c690src/middlewared/middlewared/etc_files/local/ssh sshd_config.mako config.py, src/middlewared/middlewared/plugins ssh.py

Convert SSH plugin to typesafe pattern

## Context
Migrates the `ssh` plugin from the legacy dict-based `SystemServiceService` to the typesafe pattern, matching the `ups`/`ftp` shape.

## Solution
Split the single `ssh.py` into a package: a lean `SSHService` (`generic = True`) in `__init__.py` delegating to `SSHServicePart` in `config.py`, with the host-key helpers moved to plain functions in `keys.py`. `config`/`update` now return the `SSHEntry` Pydantic model in-process, so every internal consumer was updated: the `sshd_config` mako and the SSH `config.py` renderer `.model_dump()` the model at the top, and the in-process callers (`keychain`, `failover` nftables, the `service_` start/reload hooks, and the plugin's own `setup()`) were switched from string `middleware.call('ssh.…')` to typed `call2`/`call_sync2`. The only remaining string call is `etc.py`'s dynamic `CtxMethod` dispatch, which has no static method handle. Registered the service in `main.py` and added the package to the mypy workflow.
DeltaFile
+0-186src/middlewared/middlewared/plugins/ssh.py
+83-0src/middlewared/middlewared/plugins/ssh/__init__.py
+80-0src/middlewared/middlewared/plugins/ssh/keys.py
+66-0src/middlewared/middlewared/plugins/ssh/config.py
+19-22src/middlewared/middlewared/etc_files/local/ssh/sshd_config.mako
+9-15src/middlewared/middlewared/etc_files/local/ssh/config.py
+257-2236 files not shown
+273-23312 files

FreeNAS/freenas 1d7e659src/middlewared/middlewared/plugins/cloud_backup crud.py __init__.py

Convert cloud_backup plugin to the typesafe pattern

This commit adds changes to convert the cloud_backup plugin to the typesafe service/part pattern, so query and get_instance return Pydantic models, public methods use @api_method(check_annotations=True), and same-process calls go through call2/call_sync2.

The shared CloudTaskServiceMixin is left untyped since cloud_sync still depends on it, with a single sibling-safe edit to its zvol validation path. All in-process consumers were updated for model access: the cloud_sync credential delete check, the cron.d mako, and the path-resolution migration. Since the password is a Secret field, the create/update and restic paths dump with expose_secrets so an unchanged password isn't written back as the redaction string.
DeltaFile
+88-150src/middlewared/middlewared/plugins/cloud_backup/crud.py
+179-0src/middlewared/middlewared/plugins/cloud_backup/__init__.py
+73-82src/middlewared/middlewared/plugins/cloud_backup/sync.py
+65-83src/middlewared/middlewared/plugins/cloud_backup/snapshot.py
+60-63src/middlewared/middlewared/plugins/cloud_backup/init.py
+38-37src/middlewared/middlewared/plugins/cloud_backup/restore.py
+503-41510 files not shown
+578-43316 files

FreeNAS/freenas eaa833bsrc/middlewared/middlewared/plugins/truecommand config.py portal.py

Address reviews
DeltaFile
+5-1src/middlewared/middlewared/plugins/truecommand/config.py
+1-1src/middlewared/middlewared/plugins/truecommand/portal.py
+6-22 files

FreeNAS/freenas 80f43cfsrc/middlewared/middlewared/plugins/truecommand portal.py update.py

Convert truecommand plugin to typesafe pattern

This commit adds changes to convert the truecommand plugin to the typesafe pattern, splitting the old compound ConfigService into a lean GenericConfigService that delegates to a ConfigServicePart with Pydantic models, while the portal/wireguard/state logic moves into plain context-first functions and same-process calls use call2. In-process consumers of truecommand.config (truenas and security) switch from dict access to typed attribute access.
DeltaFile
+182-149src/middlewared/middlewared/plugins/truecommand/portal.py
+0-229src/middlewared/middlewared/plugins/truecommand/update.py
+98-102src/middlewared/middlewared/plugins/truecommand/wireguard.py
+184-0src/middlewared/middlewared/plugins/truecommand/config.py
+87-7src/middlewared/middlewared/plugins/truecommand/__init__.py
+45-0src/middlewared/middlewared/plugins/truecommand/state.py
+596-4876 files not shown
+610-53612 files

FreeNAS/freenas bb1a9a2src/middlewared/middlewared/plugins/vm capabilities.py

Annotate supported_archs to fix mypy inference with VMGuestArch keys
DeltaFile
+1-1src/middlewared/middlewared/plugins/vm/capabilities.py
+1-11 files

FreeNAS/freenas 70056efsrc/middlewared/middlewared/plugins/cloud_backup crud.py __init__.py

Convert cloud_backup plugin to the typesafe pattern

This commit adds changes to convert the cloud_backup plugin to the typesafe service/part pattern, so query and get_instance return Pydantic models, public methods use @api_method(check_annotations=True), and same-process calls go through call2/call_sync2.

The shared CloudTaskServiceMixin is left untyped since cloud_sync still depends on it, with a single sibling-safe edit to its zvol validation path. All in-process consumers were updated for model access: the cloud_sync credential delete check, the cron.d mako, and the path-resolution migration. Since the password is a Secret field, the create/update and restic paths dump with expose_secrets so an unchanged password isn't written back as the redaction string.
DeltaFile
+88-150src/middlewared/middlewared/plugins/cloud_backup/crud.py
+174-0src/middlewared/middlewared/plugins/cloud_backup/__init__.py
+73-82src/middlewared/middlewared/plugins/cloud_backup/sync.py
+65-83src/middlewared/middlewared/plugins/cloud_backup/snapshot.py
+60-63src/middlewared/middlewared/plugins/cloud_backup/init.py
+38-37src/middlewared/middlewared/plugins/cloud_backup/restore.py
+498-41510 files not shown
+572-43316 files

FreeNAS/freenas 8a247adsrc/middlewared/middlewared/plugins/vm info.py capabilities.py

Fix mypy breakage
DeltaFile
+2-1src/middlewared/middlewared/plugins/vm/info.py
+1-1src/middlewared/middlewared/plugins/vm/capabilities.py
+3-22 files

FreeNAS/freenas ce8b3e1src/middlewared/middlewared/api/v27_0_0 vm.py, src/middlewared/middlewared/plugins/vm crud.py capabilities.py

NAS-141465 / 27.0.0-BETA.1 / Adds ARM64 guest VM support (#19167)

Adds aarch64 guest VM support to TrueNAS. Users can now create and run
ARM64 VMs on either x86_64 or aarch64 hosts, and x86_64 VMs on aarch64
hosts. Same-architecture guests use KVM acceleration; cross-architecture
guests fall back to QEMU software emulation.

Packaging, firmware, XML generation, CPU model selection, and
create-time validation are all updated to handle both architectures
correctly.
DeltaFile
+166-76tests/vm/test_vm.py
+180-0src/middlewared/middlewared/pytest/unit/plugins/vm/test_arch_validation.py
+91-24src/middlewared/middlewared/plugins/vm/crud.py
+28-0src/middlewared/middlewared/pytest/unit/plugins/vm/test_secboot_firmware.py
+19-4src/middlewared/middlewared/plugins/vm/capabilities.py
+15-2src/middlewared/middlewared/api/v27_0_0/vm.py
+499-1065 files not shown
+532-11411 files

FreeNAS/freenas 8a40c98src/middlewared/middlewared/plugins/vm crud.py capabilities.py

Address review: add VMGuestArch StrEnum
DeltaFile
+9-8src/middlewared/middlewared/plugins/vm/crud.py
+8-6src/middlewared/middlewared/plugins/vm/capabilities.py
+7-0src/middlewared/middlewared/plugins/vm/constants.py
+3-1src/middlewared/middlewared/plugins/vm/info.py
+2-1src/middlewared/middlewared/plugins/vm/__init__.py
+29-165 files

FreeNAS/freenas 7a7d844src/middlewared/middlewared/plugins/vm crud.py capabilities.py

Address review: add VMGuestArch StrEnum
DeltaFile
+9-8src/middlewared/middlewared/plugins/vm/crud.py
+8-6src/middlewared/middlewared/plugins/vm/capabilities.py
+7-0src/middlewared/middlewared/plugins/vm/constants.py
+3-1src/middlewared/middlewared/plugins/vm/info.py
+2-1src/middlewared/middlewared/plugins/vm/__init__.py
+29-165 files

FreeNAS/freenas e339648src/middlewared/middlewared/pytest/unit/plugins/vm test_arch_validation.py

Add unit tests for aarch64 VM validation rules

15 parametrized cases covering the three x86-only flag rejections
(UEFI_CSM, hyperv_enlightenments, hide_from_msr) on aarch64 guests,
their acceptance on x86 guests, and the cross-arch HOST-PASSTHROUGH /
HOST-MODEL cpu_mode guard including the i686-on-x86_64 family exception.
DeltaFile
+174-0src/middlewared/middlewared/pytest/unit/plugins/vm/test_arch_validation.py
+174-01 files

FreeNAS/freenas 0dd86cesrc/middlewared/middlewared/plugins/vm crud.py, src/middlewared/middlewared/pytest/unit/plugins/vm test_secboot_firmware.py

Make VM secboot block arch-aware and tighten arch-compat validation

The secure boot path in do_create() previously assumed an x86 guest and
rejected aarch64 + secure_boot configurations with confusing errors. It
now picks arch-appropriate machine and firmware defaults and accepts
AAVMF secure-boot variants.

Also reject combinations that can never produce a working VM: x86-only
features (UEFI_CSM, Hyper-V enlightenments, hide_from_msr) on aarch64,
and KVM-only CPU modes (HOST-PASSTHROUGH, HOST-MODEL) when the guest
architecture doesn't match the host. These previously slipped through
schema validation and failed later at libvirt define-time; catching them
up front yields a useful error message.

Includes a unit test for the secboot firmware-name detection helper.
DeltaFile
+78-17src/middlewared/middlewared/plugins/vm/crud.py
+25-0src/middlewared/middlewared/pytest/unit/plugins/vm/test_secboot_firmware.py
+103-172 files

FreeNAS/freenas de18377src/middlewared/middlewared/pytest/unit/plugins/vm test_arch_validation.py test_secboot_firmware.py, tests/vm test_vm.py

ruff format

Also add some ssh robustness
DeltaFile
+111-105src/middlewared/middlewared/pytest/unit/plugins/vm/test_arch_validation.py
+87-39tests/vm/test_vm.py
+21-18src/middlewared/middlewared/pytest/unit/plugins/vm/test_secboot_firmware.py
+219-1623 files