Hi folks,
The 2026.07 tag has been pushed to master on gitlab.com/lava/lava. .deb packages have been built in GitLab CI and are published at
https://apt.lavasoftware.org/release
Docker images for amd64 and arm64 have been built in GitLab CI and are available from
and
https://hub.docker.com/u/lavasoftware
Changes in this release ==================
# Database migation
3 database migrations are applied by this release:
* lava_scheduler_app.0065_alter_devicetype_health_frequency_default * lava_scheduler_app.0066_blacklist_to_jsonfield * lava_scheduler_app.0067_testjob_requested_device_and_worker
These migrations should not create any downtime, as they can run while the instance is running. However, if you are running a large LAVA deployment, be aware that 0066 rewrites the notification table in place, so it needs temporary disk space of about that table's size and the table is unavailable while it runs. 0067 may briefly stall the scheduler. The new columns themselves are added instantly, but the indexes that come with them block writes to the job table until the migration commits. Generally this is pretty quick, a few seconds even on millions of jobs.
# Device-types
## New device-types
* stm32wba65i-dk1 * imx952-frdm
## QEMU: overlay drive interface
The `qemu` and `kvm` templates were attaching the `lava-guest.qcow2` test overlay to an IDE drive (`if=ide`), which only works on x86 machines. On other architectures QEMU rejected the drive, so only boot-only tests could run.
The interface is now selected from the architecture:
* `ide` is kept on x86 for backward compatibility * `virtio` is used everywhere else (aarch64, riscv, ppc, ...).
# Breaking changes
## qdl: `overlay_path` removed
`overlay_path` became a no-op in the `qdl` deploy action after the `CompressOverlay` rework, and has been removed from the deploy schema. Jobs still setting it will now fail validation and must simply drop the key:
```yaml - deploy: to: qdl rootfs_image: "disk-sdcard.img2" qcomflash: url: 'https://example.com/qcomflash.tar.gz' apply-overlay: true ```
# Server
## Target a specific device or worker
A job can now request one exact device or restrict scheduling to the devices attached to one worker. The device must belong to the requested `device_type`, and the worker must exist on the instance:
```yaml device_type: qemu device: qemu-01 worker: worker-01 ```
When `device` is used the scheduler will only run the job on that device, and will not fall back to another device of the same type. Likewise `worker` restricts the job to devices of the requested type attached to that worker. If both are given, the selected device must also be attached to the selected worker.
For multinode jobs, `device` and `worker` are specified inside each `lava-multinode` role. A role-level `device` constraint requires that role to use `count: 1`.
## Require login on specific paths
Allow requiring login for specific URL paths.
`REQUIRE_LOGIN` gates a whole instance, which is too broad for otherwise public instances. The query UI, for example, is open to anonymous users and can trigger expensive database queries, making large instances easy to overload with indiscriminate crawlers.
The new `REQUIRE_LOGIN_PATHS` setting takes a list of URL path prefixes, relative to the mount point. Each prefix covers itself and everything below it:
```yaml REQUIRE_LOGIN_PATHS: ["results/query", "results/chart"] ```
Requests under a configured prefix get the same enforcement as `REQUIRE_LOGIN`, including the standard exemptions and API token passthrough; everything else stays open. An invalid configuration raises `ImproperlyConfigured` at startup.
## Custom default health-check frequency
Device-types were always created with a routine health-check frequency of 24 hours. Admins can now change that default for an instance:
```yaml HEALTH_FREQUENCY_HOURS: 168 ```
The value is validated when the application starts: non-integer or non-positive values are rejected with `ImproperlyConfigured`.
## Secrets in multinode jobs
A top level `secrets` section was silently dropped when submitting a multinode job. It is now copied to every child job of the multinode job, like the other shared sections.
## Notification blacklist stored as JSON
`Notification.blacklist` moved from a PostgreSQL-only `ArrayField` to a `JSONField` so that it works on every database backend. A data migration converts the existing `varchar[]` column to `jsonb` in place, preserving the stored values. The conversion only runs on PostgreSQL instances where the column is still an array, and is a no-op on fresh installs and on SQLite.
## Bug fixes
* **Login redirects**: `LOGIN_URL` is now computed with the mount point prefix, and `LOGIN_PATH` was replaced by a `login_path()` classmethod reading the current setting at call time. With `MOUNT_POINT=lava/`, the login page exemption check was failing and redirects were landing outside the instance. * **Compressed logs**: gunicorn used the `sendfile` kernel call when the file object of a `FileResponse` exposes `fileno()`. For LZMA files this returned the underlying file descriptor, so responses contained truncated, still compressed data. The LZMA `fileno()` is now overridden to raise `io.UnsupportedOperation`. * **Empty settings file**: `yaml.safe_load()` returns `None` for an empty or comment-only file, and calling `.items()` on it crashed LAVA at startup. An empty `settings.conf`, `settings.yaml` or `settings.d/*.yaml` now simply contributes no setting. * **ConfigFile**: the parser has been reworked on top of `shlex` instead of regexes, fixing a file handle leak, restoring key validation and reporting line numbers starting at 1.
# Dispatcher
## user_commands: lists of commands
`do` and `undo` in `user_commands` accepted a single command only, unlike `power_on_command` and `power_off_command`. Both keys now also accept a list of commands, run in the order they are listed:
```jinja {% set user_commands = {'set_boot_to_qspi': {'do': ['webrelay --host 192.168.0.15 --port 1234 --relay 1 on', 'webrelay --host 192.168.0.15 --port 1234 --relay 2 on'], 'undo': ['webrelay --host 192.168.0.15 --port 1234 --relay 2 off', 'webrelay --host 192.168.0.15 --port 1234 --relay 1 off']}} %} ```
Empty commands (and lists containing an empty command) are rejected at validation time instead of silently running nothing. The device schema has been aligned with the implementation.
## Devices always powered off
The power cleanup now runs even when `CLEANUP_TIMEOUT` has already been used up by the previous cleanup steps, so boards are no longer left powered on after a job.
## Faster ext4 overlay
Two improvements to the `debugfs` based overlay introduced in 2026.05:
* Only the parent directory is resolved for non-directory tar members, the basename being appended afterwards. Directory components are cached while each file leaf is unique, so every file previously cost two uncacheable `debugfs` stat calls. Building a large overlay took ~40 seconds for ~11 seconds of actual writes. * Partition copies use a large `dd` block size.
Behaviour change: a file whose destination is an existing symlink in the image now replaces it instead of writing through it, which matches `tar` and `libguestfs` `tar_in` semantics.
## Bug fixes
* **vexpress**: most exceptions raised while deploying are now reported as `InfrastructureError` rather than job failures. * **Overlay environment**: a debug message is logged when `LAVA_DEVICE_HOSTNAME` or `LAVA_DEVICE_TYPE` are not exported because the device configuration lacks the `hostname` or `devicetype` field. * **Device-type templates**: variables unused by any base template have been removed.
# Packaging
## sysusers.d
The Debian packages now ship `sysusers.d` configuration files instead of creating users from hand-written maintainer scripts. This declarative configuration also allows `/usr`-only image based systems to be created, and `/etc` to be recreated on boot after a factory reset. It does not add a hard dependency on systemd: debhelper generates the dependencies so that non-systemd and non-Linux Debian builds keep working.
## SQLite for local development
`DATABASE_URL` is now supported in the development settings and the model no longer uses PostgreSQL-specific fields, so a development instance can run on SQLite. The uv based development workflow is documented.
# CI
## pre-commit
The CI now runs a `pre-commit` job in the analyze stage, and most of the checks have been moved to hooks: `ruff` (check and format), `codespell`, `pyupgrade`, `django-upgrade`, `bandit`, `pylint`, `mypy` and the standard file checks (large files, merge conflicts, private keys, TOML and YAML syntax, ...). The `pyrefly` and `ty` hooks are enabled experimentally.
The now redundant `codespell`, `pylint`, `ruff` and SAST/`semgrep` CI jobs and their runner scripts have been removed, and `black` and `isort` have been dropped from the development dependencies in favour of `ruff`.
## Supply chain
An SBOM is generated and scanned for vulnerabilities in CI, and the `uv.lock` file has been refreshed (closes CVE-2026-32274).
## Type checking
`lava_dispatcher/action.py` is now type checked. This required using `isinstance` checks in `Action.parsed_command`, waiting on the `subprocess.Popen` object rather than on `PexpectPopenSpawn`, and using `subprocess.run` in `Action.run_command`. The asymmetric `errors` getter/setter of `Action` and `Protocol` has been replaced by an `errors_add` method, since type checkers require a getter and a setter to use the same type.
Rgds