Skip to content

fix(factory_reset): adapt result parsing to omnect-os-init JSON contract - #207

Merged
JanZachmann merged 21 commits into
omnect:mainfrom
JanZachmann:jz-2026-07-28-factory-reset-json-contract
Nov 4, 2026
Merged

JanZachmann merged 21 commits into
omnect:mainfrom
JanZachmann:jz-2026-07-28-factory-reset-json-contract

Conversation

@JanZachmann

@JanZachmann JanZachmann commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes #205

Summary

omnect-os moves from the scripted initramfs to omnect-os-init, an init written in Rust (omnect/meta-omnect#697). The init writes the factory-reset result to /run/omnect-device-service/omnect-os-initramfs.json in a new JSON format. This PR makes omnect-device-service read this format and report it to the twin.

  • Reads the key factory_reset. A result under the old key factory-reset is unusable: it is logged as an error and reported as null. A null under the old key is a normal boot.
  • The status codes have the omnect-os-init names: 0 success, 1 invalid, 2 error, 3 config error, and the new 4 warning (the reset worked, but a partition needed a second mkfs). The status is a u32, as omnect-os-init writes it. A status code this version does not know is reported as it is, and the raw result is logged as a warning.
  • error and context are optional, paths defaults to an empty list. data_wiped is required: a result without it is unusable and reported as null, because a missing value must not read as a safe abort.
  • Reports data_wiped: on error it tells a safe abort from a failure after data was already wiped.
  • Reports error and context as null instead of leaving them out: twin reports are merge patches, so a missing key would keep the value of a previous result.
  • The factory_reset feature version is 4, because the reported result changed. The crate version is 0.46.1.
  • Mode 4 is no longer accepted: the init does not support it, so a request with mode 4 would come back as Invalid, a reset that never runs.
  • The test files have the real omnect-os-init output. testfiles/positive/contract holds copies of the status files that omnect-os-init writes for every factory reset result (its tests/contract, fix: keep the behaviour of the scripted initramfs, remove a stale update validation config omnect-os-init#40); a test parses each one and compares every field.
  • The README says that a result from before feature version 4 stays in the twin until the next reset, and that preserve accepts only the reported keys.
  • README and update_validation.md link the initramfs docs in omnect-os-init (docs/usage.md) instead of meta-omnect and the legacy scripts. The README documents the result fields, and that preserve is mandatory, as the parser already requires.
  • update_validation.md says that omnect-os-init deletes update_validation_conf.json on every boot without update validation (fix: keep the behaviour of the scripted initramfs, remove a stale update validation config omnect-os-init#40), so a config written by hand is gone after the next reboot.

Reason

The parser did not match the omnect-os-init format: it read the key factory-reset and required an error string. So the result of the init was unusable and never reached the twin. See #205 for the full list of differences.

omnect-os-init tests that it writes exactly the contract files, and this repo tests that it parses them. So a format change on one side fails a test once the copies are updated.

omnect-ui reads the published result and must follow the new format and the removed mode 4: omnect/omnect-ui#132. The local web service keeps the channel FactoryResetV1 and the route /factory-reset/v1, although the payload changed and mode 4 is rejected. An omnect-ui without omnect/omnect-ui#132 gets no signal about this. The only safeguard is that this PR, omnect/meta-omnect#697 and omnect/omnect-ui#132 reach a release together; omnect-ui#132 also requires ods >=0.46.1.

@JanZachmann

Copy link
Copy Markdown
Contributor Author

FYI @mlilien

@JanZachmann
JanZachmann marked this pull request as draft August 10, 2026 13:54
JanZachmann added a commit to omnect/omnect-os-init that referenced this pull request Sep 24, 2026
## Summary

Adds the factory-reset wipe modes 2 and 3. Mode 2 overwrites `etc` and
`data` with data from `/dev/urandom`, mode 3 discards every block of
both with the `BLKDISCARD` ioctl. The wipe runs between the backup and
the reformat and never aborts the reset: a failure on one partition does
not skip the other, reformat and restore still run, and the outcome is
reported as `Error` with the reason in `error`. Mode 4 is no longer an
accepted mode.

It also closes what the on-device tests found: a trigger the init cannot
parse is answered instead of ignored, and a preserve configuration that
says nothing is rejected instead of skipped. A blank trigger is not one
of those — it is no reset request at all.

Design:
`docs/superpowers/specs/2026-08-11-factory-reset-modes-2-3-4-design.md`,
with a correction of what it claimed about rejected triggers.

## Reason

The legacy shell init implemented the wipe with external tools. Both are
replaced by native code, so the behaviour differs in ways worth stating.

**Mode 2** covers the whole partition from byte 0. The legacy `dd`
started at offset 2048 so an interrupted wipe kept the volume label
readable; the init finds partitions by number, never by label, and
re-creates the filesystem anyway. Privacy is the same or better. The
legacy `dd` also used `oflag=direct`; the write loop goes through the
page cache instead and flushes every gigabyte and again at the end, so
after a power loss at most one gigabyte of already overwritten blocks
can still hold the old content.

**Mode 3** issues one `BLKDISCARD` over the whole partition. The legacy
path mounted it, removed the files and ran `fstrim`, which trims only
the blocks the remove had freed and leaves journal remnants — and
counted a failed mount as a successful wipe. Without discard support the
ioctl now fails cleanly and the reset reports it. Discard stays a hint
on some disks, so the "no total privacy guarantee" note in the layer
README remains true.

Both modes open the device with `O_EXCL`, so a partition that is still
mounted fails with `EBUSY` rather than being overwritten under a live
filesystem.

**Mode 4 is dropped, not ported.** It handed the wipe — the one step the
caller asked for — to a customer-supplied hook that is not built or
tested here, and modes 2 and 3 cover rotating disks and flash. The
removal has to land in the same release wherever the mode is known: the
mode table in the meta layer README, and the mode type in
omnect/omnect-device-service#207 and omnect/omnect-ui#128.

A failed wipe reports `Error`, not `Warning`: the device is usable, but
the operation the caller asked for did not happen. For modes 2 and 3 the
destructive boundary moves to the wipe, so every outcome from there on
reports `data_wiped` — a partly written overwrite destroys the data even
when the reformat never runs.

**A trigger the init cannot use is now answered.** It used to produce a
kmsg warning only: the boot continued as Normal, no `factory_reset`
object was written, and the trigger kept its value, so the warning
repeated on every boot while the caller waited for a result. Such a
trigger is now cleared like any other and reported with the status the
shell implementation used — anything about `mode` is `Invalid`, anything
about the preserve configuration is `ConfigError`. That also makes
`preserve` mandatory again, and a file in `factory-reset.d` without a
usable `paths` array an error rather than something to skip, since
skipping it wipes the paths the caller asked to keep and still reports
success.

**A blank trigger is not a reset request.** GRUB reports a key set to an
empty value as present where U-Boot reports it as unset, and the shell
init cleared the key by writing an empty string. A device coming from it
would otherwise be answered with one "reset failed" nobody asked for.

The statuses a configuration problem reports are now the same wherever
it occurs: a `factory-reset.json` that does not exist reports
`ConfigError` like a key missing inside it, which is what the shell
implementation did too. Only an absent file counts — a path that exists
but cannot be read stays an I/O error and reports `Error`, because the
request was usable and the storage was not, which is worth retrying. The
`error` field itself has one shape everywhere now: it sits inside the
factory-reset result, so no writer repeats the outer `Factory reset
error:` prefix.

**Known gap.** Nothing pins that the wipe runs after the backup and
after the unmount — moving `wipe_and_rebuild` above `backup_all` passes
the whole suite. The `wipe_and_rebuild` seam starts after both steps, so
covering the order against them needs a mount/backup seam as well.
Tracked separately, together with the `data_dev` / `etc_dev` argument
swap, which has the same shape.

The result schema is unchanged and the initramfs image needs no new
packages: the wipe uses one ioctl and `/dev/urandom` instead of
`fstrim`, `blkdiscard` or `dd`.

---------

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
omnect-os-init writes the factory-reset result with key "factory_reset",
status codes 0..=4 and optional error/context/paths fields. The old
parser expected key "factory-reset" and required error/paths, so the
result was never reported.

- read key "factory_reset"
- add status 4 (warning: reset ok, but a partition needed a second mkfs)
- error, context and paths are optional with defaults
- a future unknown status code parses as Unknown instead of failing ODS
  startup; the raw result is logged
- status is u32 to match the omnect-os-init serialization
- bump feature version to 4 because the reported result shape changed

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Twin reports are merge patches: a key omitted from a new result keeps
the value of a previous one. Report error and context as null instead
of omitting them, so a successful reset clears the error of an earlier
failed one.

- report data_wiped: on error it distinguishes a safe abort from a
  failure after data was already wiped
- rename status variants to the omnect-os-init names (wire format
  unchanged; the old names described the codes wrongly in logs)
- align warning testfile with real omnect-os-init output (warning
  carries a context note, no error)
- test explicit "factory_reset": null and that FactoryReset::new()
  succeeds with an unknown status code
- document the result fields in the README

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Mode 4 ran a customer-supplied wipe hook in the initramfs. The hook is gone
and the initramfs no longer accepts the mode: a trigger carrying it fails to
parse, the device boots normally and no result is reported at all, so a
request sent from the cloud or the local UI would never be answered.

Rejecting the value here keeps that request from being sent in the first
place.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Version set to 0.46.0, above the released 0.45.5, because this branch
changes the factory-reset result contract.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The last upstream merge took the lock entry of upstream main. With a
lock file that does not match Cargo.toml, a --frozen build fails.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The initramfs docs moved from meta-omnect to omnect-os-init docs/usage.md.
The README also called `preserve` optional, but a payload without it is
rejected.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…ry-reset-json-contract

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>

# Conflicts:
#	Cargo.lock
#	Cargo.toml
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
A result without data_wiped is now unusable, so it no longer reads as a
safe abort. Log a warning for the legacy factory-reset key, test unknown,
negative and out-of-range status codes, test that mode 4 and a missing
preserve list are rejected, and align the README and test files with the
init output.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The files in testfiles/positive/contract are copies of the factory reset results that omnect-os-init writes for every outcome. omnect-os-init checks that it writes exactly these files, and this test checks that ods parses each of them. A change on one side then fails a test when the copies are updated.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
…config

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The error-after-wipe and warning files now hold values the init writes:
a Warning has no error, and a failed restore is an Error.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
JanZachmann added a commit to omnect/omnect-os-init that referenced this pull request Oct 28, 2026
…ate validation config (#40)

## Summary

Keeps behaviours of the scripted initramfs that the init did not keep,
and removes a stale update validation config:

- `/run` is mounted with `nosuid`, `nodev`, `strictatime` and mode 0755.
- The links in `/etc/omnect/create-fs-links.txt` (one `<target> <link>`
pair per line) are created once, then the file is deleted.
- With `persistent-var-log`, `/var/log` is `/mnt/data/log`, the
directory of the scripted initramfs. The fsck logs are written to
`/mnt/data/log/fsck`.
- After `switch_root` the initramfs files are deleted, so their RAM is
freed. The delete does not cross into mounted filesystems, and the
number of deleted entries is logged. When `/` is not a ramfs or tmpfs,
the files are kept and the reason is logged.
- A factory reset restore creates missing parent directories with the
owner and mode from the backup.
- The fsck logs and the flash mode logs are readable only by root: files
0600, the fsck log directory 0700. The scripted initramfs kept fsck
output only in the status file, which has mode 0600.
- `parted` runs in script mode, and a failed `parted print` is an error.
- An empty `bootpart_fsuuid=` fails at once with a clear error instead
of waiting for the timeout.
- An fsck record in the env that does not unpack or has no exit code
line (the format of the scripted initramfs) is deleted instead of
staying there on every later boot. The text of a record without exit
code is logged first.
- On a boot without `omnect_validate_update`,
`/mnt/data/var/lib/omnect-device-service/update_validation_conf.json` is
deleted. A missing file is not an error, a failed delete is logged as a
warning. On a validation boot the file is kept.
- `tests/contract` holds the status file for every factory reset result,
and a test checks that the init writes exactly these files.
- `docs/usage.md` describes the symlinks, the removal of the update
validation config, and that a failed EFI boot entry ends a flash mode
with a fatal error.
- `docs/usage.md` and the README warn that flash mode 3 does not check
TLS certificates on a machine without `rtc`.
- Two wrong comments are fixed: the init does not create `/mnt/cert/ca`
and `/mnt/cert/priv`, and GRUB probes the boot partition of its own
disk.

## Reason

These are behaviours of the scripted initramfs that the init did not
keep, or kept only partly. A device updated from the scripted initramfs
would lose its log history and its one-time links, and run with a `/run`
that allows suid binaries and device files. omnect/meta-omnect#697
builds the init into omnect-os and needs these fixes.

omnect-device-service writes the update validation config before the
reboot into the validation boot of an update it runs itself
(`set_boot_partition`), and removes it after a successful validation. A
failed validation reboots without removing it. An ADU update does not go
through omnect-device-service: the swupdate handler only sets
`omnect_validate_update_part` and does not write the file, but the
validation reads it. So after a failed local validation, the next ADU
update is validated as a local one: no IoT Hub connection check, and
deviceupdate-agent is not started by omnect-device-service. A config
written by hand is also deleted on every boot without validation;
omnect/omnect-device-service#207 adds this to the update validation
docs.

The contract files close a gap that no device test can see: the status
file is the interface to omnect-device-service.
omnect/omnect-device-service#207 adds a copy of these files and a test
that parses each one. The error-after-wipe and warning cases are built
by the factory reset status code, so they hold values the init really
writes.

---------

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
@JanZachmann
JanZachmann removed the request for review from HarryWaschkeit October 28, 2026 10:45

@mlilien mlilien left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review notes; inline comments below. Two points that cannot be anchored inline:

  • src/web_service.rs (FactoryResetV1): the published FactoryResetResult payload changes incompatibly (error can be null, new data_wiped, status 4 / 4294967295), but the publish channel stays FactoryResetV1, and /factory-reset/v1 now rejects mode 4. An omnect-ui build without omnect/omnect-ui#132 can't detect the change and breaks on error: null / status: 4. The twin feature version was bumped to 4; should the local channel/endpoint get a V2 as well?
  • Commit 827a8c2: the Signed-off-by trailer has no angle brackets around the email (Signed-off-by: Jan Zachmann 50990105+...@users.noreply.github.com), so a DCO check will treat it as unsigned.

Comment thread src/twin/factory_reset.rs Resolved
Comment thread src/twin/factory_reset.rs Resolved
Comment thread src/twin/factory_reset.rs Resolved
Comment thread README.md Resolved
Comment thread src/twin/factory_reset.rs Resolved
Comment thread src/twin/factory_reset.rs Resolved
An unknown status code is reported as it is, so the cloud can tell one
future code from another. A non-null result under the legacy key
'factory-reset' is reported as unusable instead of as no reset, and a
null legacy key is a normal boot without a warning. The README notes
that a result from before feature version 4 can stay in the twin.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…and configuration

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann

Copy link
Copy Markdown
Contributor Author

@mlilien on the two points in the review summary:

@JanZachmann
JanZachmann requested a review from mlilien October 28, 2026 18:29

@mlilien mlilien left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local web service still publishes on FactoryResetV1 ("FactoryResetResult") and serves /factory-reset/v1, but the payload changed: error can be null, data_wiped is new, status 4 now means Warning, and mode 4 is rejected. Only the twin feature version was bumped. An omnect-ui without #132 gets no signal that the format changed. If the joint release is the only safeguard, could the description say so explicitly? Otherwise, should the channel or route get a new version?

Comment thread src/twin/factory_reset.rs Resolved
Comment thread src/twin/factory_reset.rs Resolved
Comment thread src/twin/factory_reset.rs Resolved
Comment thread src/twin/factory_reset.rs Resolved
Comment thread README.md Resolved
ods checks preserve against the keys of factory-reset.json on the device
and applications, not against the omnect-os-init topic list.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann

Copy link
Copy Markdown
Contributor Author

@mlilien on the review summary: the description now says it explicitly. The channel FactoryResetV1 and the route /factory-reset/v1 stay, and the only safeguard is that this PR, omnect/meta-omnect#697 and omnect/omnect-ui#132 reach a release together; omnect-ui#132 also requires ods >=0.46.1.

@JanZachmann
JanZachmann requested a review from mlilien October 29, 2026 12:19
Comment thread src/twin/factory_reset.rs Resolved
…usable

A top level of [], a string or null read as no reset, so the twin kept
the previous result.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann
JanZachmann requested a review from mlilien October 29, 2026 13:31

@JoergZeidler JoergZeidler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the factory-reset result contract change. Findings below reference file:line; none land on a line this diff touches (web_service.rs is unchanged and connect_web_service is outside the diff), so they are in the body rather than inline.

Contract verified against omnect-os-init at 9083856 (#40): key factory_reset, status codes 0..=4 as u32, field shapes (error/context optional, paths default, data_wiped required), and the six testfiles/positive/contract/ files are byte-for-byte copies of its tests/contract/. README claims about paths, the mandatory /etc/omnect/factory-reset.d/, and data_wiped match the producer. Mode 4 removal is consistent.

Machine checks on the PR head: cargo fmt -- --check passes; cargo audit passes (only pre-existing allowed warnings). clippy and the test suite could not run locally — azure-iot-sdk-sys needs the azure-iot-sdk-dev pkg-config library, absent here; they run in Concourse.


Medium (decision) — the web-service factory-reset surface changed its contract, but the README "Local web service" section was not updated.
The published channel FactoryResetResult (PublishChannel::FactoryResetV1, src/web_service.rs:58,70) now carries the new result shape, and the route /factory-reset/v1 (src/web_service.rs:184) now rejects mode 4. The README diff updated only the twin "Report factory reset result" section; the "Local web service" section is unchanged. The v1/V1 label stays on an incompatible payload, so an omnect-ui without omnect/omnect-ui#132 gets no signal and the only safeguard is a coordinated release (as the PR body notes).

  • Decision: accept the coordinated release as the safeguard, or move the channel and route to v2 so an old consumer fails loudly instead of misreading v1?
  • At minimum, update the README "Local web service" section for the new payload and the rejected mode 4.

Low (decision) — web service drops the last reset result on the next boot; the twin keeps it.
src/twin/factory_reset.rs:149 (connect_web_service) publishes the full self.report, so on a boot with no reset it sends {keys} with no result and the consumer sees nothing. connect_twin instead omits the key, so the twin's merge patch keeps the last result (as newly documented). After a reset and a later unrelated reboot, the twin still shows the result but omnect-ui no longer does. The publish logic is pre-existing, but the PR documents the twin-keeps behaviour without noting the web service differs — please confirm it is intended and consider a one-line note in the "Local web service" section.

Low — new tests use unwrap() where the convention prefers expect("reason").
e.g. src/twin/factory_reset.rs:408,416,421 and the loops in factory_reset_status_test. The new new_factory_reset helper already uses expect(...), and sibling tests use unwrap() throughout, so this only matches the existing style — raise only if the team wants to tighten test style here.

Minor — test coverage gap. No test exercises connect_web_service for the unusable result, which is exactly where the web-service/twin asymmetry (Low finding above) lives. Otherwise the parser error paths, no-reset/legacy cases, unknown-status round-trip, twin_report null-vs-omit, and the mode-4/missing-preserve rejections are all covered.

The published factory reset status has the twin result format, and has
no result on a later boot while the twin keeps the last one. The
factory reset route checks mode and preserve as the direct method does.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann

JanZachmann commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

@JoergZeidler on your review:

  • v2 for channel and route: We keep v1 and accept the joint release as the safeguard; the description says so. omnect-ui#132 also requires ods >=0.46.1. With a v2 channel an old omnect-ui would not fail loudly: it would get no factory reset status at all, so v2 only changes a parse error into a missing result.
  • README "Local web service": updated in 2c472d0. The factory reset route links to Trigger factory reset for mode and preserve, and the status entry links to the twin result format.
  • Web service drops the last result: intended. omnect-ui shows the result dialog whenever the published status has a result and the browser session has not acknowledged it yet (App.vue, sessionStorage); the ack marker of the backend is not used for this. If ods published the last result on every boot, every new browser session would show the dialog for an old reset again. The twin keeps the last one for the cloud. The status entry in the README now says that no result is published on a later boot while the twin keeps it (2c472d0).
  • unwrap() in tests: replaced by expect in this file (733e3f6).
  • Test for connect_web_service: I did not add one. In test builds web_service::publish does nothing, and the payload is serde_json::to_value(&self.report), so there is nothing to check beyond what twin_report_test covers for an absent result. A test would need a test hook in publish first.

Your review is set to "changes requested" with an empty body. If anything beyond these points is open, please say which.

@JoergZeidler JoergZeidler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round-2 review after the feedback commits. All substantive findings from the first review are resolved: the README "Local web service" section now documents the published payload and the web-service/twin result asymmetry, and the tests use expect(...) instead of unwrap(). Contract still matches omnect-os-init #40. cargo fmt -- --check passes; clippy and tests run in Concourse. Only an optional minor test-coverage item remains (connect_web_service with an unusable result).

@JanZachmann
JanZachmann merged commit 37f8172 into omnect:main Nov 4, 2026
2 checks passed
JanZachmann added a commit to omnect/omnect-ui that referenced this pull request Nov 4, 2026
…eset mode 4 (#132)

Closes #125

## Summary

omnect-os moves from the scripted initramfs to
[omnect-os-init](https://github.com/omnect/omnect-os-init), an init
written in Rust (omnect/meta-omnect#697). The new init writes a
different factory-reset result and does not support reset mode 4;
omnect/omnect-device-service#207 publishes this result as
`factory_reset` feature version 4. This PR makes omnect-ui follow both
changes. It also changes the probe of wifi-commissioning-service.

### Factory reset result of feature version 4
- The factory-reset result types follow the format omnect-device-service
publishes with feature version 4: `status` is a `u32` with the codes
`success`, `invalid`, `error`, `configError` and `warning` plus a
catch-all, `error` and `context` are `null` when nothing went wrong, and
the new `data_wiped` flag tells whether the reset had already started to
change data. The required omnect-device-service version is `>=0.46.1`.
- The domain status enum has two variants that are not a real result.
`Unknown` is the default and means "no result received yet", so the
result dialog stays hidden. `Unrecognized` is the catch-all, so a status
code that a later omnect-device-service adds still reaches the user.
- `warning` counts as success in the UI: the reset completed, but a
partition needed a second format attempt. `data_wiped` is shown only on
a failed result, where the device may be partly wiped and needs another
reset.

### wifi-commissioning-service probe via service-info
- `GET /api/v1/service-info` (wifi-commissioning-service 0.2.0,
omnect/wifi-commissioning-service#3) is the only availability probe: it
returns version, interface name and BLE state in one call. The probe no
longer calls `/api/v1/status` and `/api/v1/version`.
- The version requirement works as for omnect-device-service: a semver
requirement string (`>=0.2.1`) and the existing `VersionInfo { required,
current, mismatch }`. `WifiAvailability::Unavailable` carries
`version_info: Option<VersionInfo>`. `None` means the probe failed or
the version does not parse; the device info shows `unknown`. An empty
interface name also makes the service unavailable.
- An old wifi-commissioning-service does not block the UI: WiFi is
optional, so the mismatch shows only in the WiFi line of the device
info. `ble_enabled` is only logged.

### Factory reset mode 4 removed
- The web API no longer accepts factory-reset mode 4; a request with it
is rejected when the body is deserialized. Modes 1 to 3 are unchanged.

## Reason

With omnect/omnect-device-service#207, `error` is `null` after a
successful reset, but omnect-ui requires a `String`. So every
factory-reset result fails to deserialize, and the UI shows a parse
error instead of the result. The status codes were renamed and extended
in the same change, so the old `repr(u8)` enum with four variants cannot
name what the device reports.

The service-info probe needs one request instead of two. With the same
requirement string and `VersionInfo` as omnect-device-service, both
services report an unsupported version in the same way. The required
version `>=0.2.1` is the released wifi-commissioning-service tag.

Mode 4 ran a customer wipe script inside the scripted initramfs.
omnect-os-init has no such script and answers mode 4 with `Invalid`, a
reset that never runs. Rejecting the value here keeps the request from
being sent at all.

This PR, omnect/omnect-device-service#207 and omnect/meta-omnect#697
have to reach a release together, so no component offers a mode the
device refuses, and the UI can read the result the device reports.

---------

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
JanZachmann added a commit to omnect/meta-omnect that referenced this pull request Nov 4, 2026
…697)

## Summary

omnect-os moves from the scripted initramfs to
[omnect-os-init](https://github.com/omnect/omnect-os-init), an init
written in Rust. This PR is the main integration step: it builds the
init, installs it as `/init` and removes the scripted initramfs
(`init.d/*` from `omnect-os-initramfs-scripts`).

- `omnect-os-init`: Cargo features follow the machine and distro (`grub`
or `uboot`, the partition table from `OMNECT_PART_TABLE`,
`factory-reset`, `resize-data`, `persistent-var-log`, `release-image`,
`flash-mode-2`, `flash-mode-2-direct` when
`OMNECT_FLASH_MODE_2_DIRECT_FLASHING` is `1`, `flash-mode-3`, `rtc` from
the machine feature, and `early-modules` when `OMNECT_EARLY_MODULES` is
set). The partition and bootloader constants that flash mode 1 needs are
exported into the build; for flash mode 2 also the boot partition offset
and size and `OMNECT_USER_ID`.
- `OMNECT_PART_TABLE` (`gpt` or `dos`, default `gpt`) selects the
partition table the init is built for; a machine whose wks file uses
`--ptable msdos` sets `dos`.
- `OMNECT_USER_ID` (uid and gid of the omnect user) is a distro
variable, so the init recipe and `omnect_user.bbclass` share one value.
- `omnect-os-initramfs`: installs the tools the init calls (`bash` for
the debug shell on non-release images, `e2fsprogs`, `dosfstools`,
`util-linux-sfdisk`, `util-linux-blkid` on GRUB machines, `efibootmgr`
on EFI machines, `dhcpcd` and `base-passwd` for flash modes 2 and 3,
`dropbear` and the omnect user for flash mode 2, `curl` and
`ca-certificates` for flash mode 3). It drops the legacy script
packages, the initramfs-framework modules, `os-release`, `jq`, `kmod`,
`util-linux-uuidgen`, `bmaptool` and `xz`. `dhcpcd` is built without
udev: it runs only in the initramfs, where udevd does not run, so it no
longer pulls udev into the initramfs. The `fsck.fat` links in `/sbin`
are created only when `/sbin` is not merged into `/usr/sbin`; on a
merged image they replaced `fsck.fat` with a link to itself, so the boot
partition was never checked.
- GRUB passes `bootpart_fsuuid` instead of `rootblk`, so the init finds
its disk by the filesystem UUID of the boot partition (partition 1, read
with `probe`). A flash mode 1 clone gets a new boot volume ID
([omnect-os-init#38](omnect/omnect-os-init#38));
the rootfs UUID would be the same on the clone and its source.
- GRUB boots from the disk it was loaded from (taken from `$cmdpath`,
which needs the `regexp` module), not from `hd0`. `hd0` is the first
disk of the firmware: with a flash mode 1 clone and its source both
attached, GRUB read the grubenv of the clone but loaded kernel and
rootfs from the source.
- U-Boot declares every environment variable the OS writes at runtime as
writable (`omnect_first_boot_done`, `omnect_fsck_*`,
`omnect_factory_reset_last_error`, `flash-mode-url`,
`flash-mode-url-sha256`), so a `saveenv` in `omnect_update_flow` keeps
them. `data-mount-options` and `resized-data` are no longer listed,
because the init reads neither.
- `u-boot-scr`: the boot script no longer appends the literal text
`None` to `bootargs` when `OMNECT_APPEND_1ST_BOOT` is unset, as on
developer images.
- `OMNECT_EARLY_MODULES` lists the kernel modules the init loads before
`switch_root` (`imx_sdma` on phytec-imx8mm). It selects the
`early-modules` feature, and the new package
`omnect-os-init-early-modules` installs the list into the rootfs as
`/etc/omnect/early-modules.d/omnect.conf`. The `omnect-os-init` recipe
is built per machine, because its features and `build.rs` read machine
settings. rpi kernels build compressed modules, so they get
`CONFIG_MODULE_DECOMPRESS` when the variable is set.
- `omnect-base-files` adds the mount point `/mnt/rootCurrentPrivate`,
where the init bind-mounts the root filesystem. Its tmpfiles config
creates `/mnt/cert/ca` and `/mnt/cert/priv` (`d` instead of `z`),
because the init does not create them.
- The busybox `poweroff` config is removed; the init powers off itself.
- Removed with the scripted initramfs:
- The recipe `omnect-os-initramfs-scripts`. A layer with a `.bbappend`
for it no longer parses, and own `init.d` scripts are no longer
possible.
- Factory reset wipe mode 4, a custom wipe added by such a `.bbappend`.
A request with `mode` 4 now gets status 1.
- The bootloader variable `data-mount-options`; the data partition is
always mounted with the default options.
- An optional `preserve`: the factory-reset request must now contain it,
else it gets status 3.
- `omnect-device-service` reads the factory-reset result the init writes
([omnect-device-service#207](omnect/omnect-device-service#207)).
- The expected bootloader checksums of genericx86-64, rpi and
phytec-imx8mm follow the changed bootloader inputs.
- README and `doc/`: keep the build settings and link to the
[omnect-os-init
docs](https://github.com/omnect/omnect-os-init/blob/main/docs/usage.md)
for the behaviour of flash modes, factory reset, filesystem check and
the status file.

## Reason

The initramfs moves from shell scripts to one Rust binary, for typed
error handling, a defined result contract towards omnect-device-service,
and unit tests for the boot, factory-reset and flash paths.

- The `omnect-os-init` recipe builds the released version 0.1.0. It
contains
[omnect-os-init#40](omnect/omnect-os-init#40),
which keeps what the scripted initramfs did: `/run` without suid and
device files, the links of `create-fs-links.txt`, the persistent logs in
`/mnt/data/log`, and the restored directories with their owner and mode.
- The `omnect-device-service` recipe builds the released version 0.46.1,
which contains
[omnect-device-service#207](omnect/omnect-device-service#207).
- [omnect-ui#132](omnect/omnect-ui#132) reads
the new factory-reset result and no longer offers mode 4. The three PRs
have to reach a release together.
- The bootloader version changes on every machine, because
`omnect_env.h` and the GRUB config are bootloader inputs (on
phytec-imx8mm the boot script is one too); devices get a bootloader
update with this image. A layer whose bootloader checksum covers
`recipes-bsp/u-boot/u-boot/*` needs a new expected value at the same
time.

---------

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adapt factory-reset result parsing to the omnect-os-init JSON contract

3 participants