RAUC Install-Check Hooks: Preserve Compatibility Rejection When Replacing the Built-In Check

A RAUC install-check hook takes responsibility for compatibility acceptance. It does not simply add another condition after the ordinary compatible-string check. If the hook returns success without enforcing the required compatibility policy, a guard that the product previously relied on can disappear.

This guide is pinned to RAUC v1.14 source and documentation. It qualifies a read-only compatibility predicate and its reporting behavior. The included synthetic shell fixture was tested on the host; RAUC, bundles, signatures, installation and hardware were not exercised.

1. Identify the behavior being replaced

The RAUC 1.14 install-hook guide states that install-check runs instead of the normal compatible check. Hooks are bundle-contained executables selected by the manifest, and the common hook executable receives the hook name as its argument.

Before adding a hook, write the acceptance predicate in plain language. For example: both compatibility inputs must be present and nonempty, their exact values must match, and any additional product restriction must pass. If the real product intentionally accepts several combinations, use an explicit reviewed matrix rather than deleting the original check by accident.

Keep authorization separate. The installation path calls normal bundle checking before reaching this hook; the pinned bundle implementation retains signature verification under the default check mode. A hook’s approval is not a replacement for bundle trust, secure boot or the product’s other security controls.

RAUC v1.14 performs normal verification and earlier setup before choosing either the custom install-check hook or the built-in compatible check.
Original source-based ordering map. Failure exits are omitted for clarity; hook rejection cannot prove earlier handlers had no side effects.

2. Validate the actual hook inputs

The 1.14 hook interface supplies RAUC_SYSTEM_COMPATIBLE from system configuration and RAUC_MF_COMPATIBLE from the current bundle. It also supplies fields such as system variant and manifest version. Their presence does not define a complete product compatibility policy.

Input condition Predicate decision Reason to retain
Both required strings present and equal. Pass this comparison only. Additional product checks may still reject.
Different strings. Reject. Incompatible combination.
One or both missing or empty. Reject under this fixture’s policy. Required evidence unavailable.
Unexpected invocation argument. Execution/configuration failure. The intended predicate was not invoked.

Do not compare two missing variables and conclude that equality proves compatibility. Quote shell expansions, avoid evaluating data as code, and treat whitespace or case normalization as part of the approved format rather than an ad hoc repair.

3. Start with a non-writing predicate

The following original fragment deliberately checks only nonempty string equality. It reads environment values, emits bounded diagnostics and exits. It does not access storage, contact a server or perform installation.

#!/bin/sh
if [ "${1-}" != "install-check" ]; then
    printf '%s\n' 'unsupported hook invocation' >&2
    exit 1
fi
if [ -z "${RAUC_SYSTEM_COMPATIBLE-}" ] || [ -z "${RAUC_MF_COMPATIBLE-}" ]; then
    printf '%s\n' 'compatibility input unavailable' >&2
    exit 10
fi
if [ "$RAUC_SYSTEM_COMPATIBLE" != "$RAUC_MF_COMPATIBLE" ]; then
    printf '%s\n' 'compatibility mismatch' >&2
    exit 10
fi
exit 0

Nine synthetic cases passed using Python 3.12.14 to invoke the host’s /bin/sh. They covered equality, mismatch, each missing input, both missing, both empty, the wrong invocation, literal shell metacharacters and trailing-space mismatch. The delivered runner checks exit status, empty standard output and the exact diagnostic. The shell binary hash is recorded because its package version was unavailable.

This proves only the supplied predicate’s behavior for those cases. It does not validate the shell or dependencies installed in a target image, and it does not test RAUC’s processing of hook results.

4. Separate rejection from execution failure

The guide specifies an exit code of at least 10 for rejection and uses the last standard-error line as the reason when available. Keep that final line concise, nonsecret and stable enough for support interpretation.

The v1.14 run_bundle_hook() implementation adds an important reporting distinction:

Observed hook outcome Pinned result interpretation
Exit 0. Hook succeeds; later checks still apply.
Exit 1–9. Hook failure aborts installation.
Exit at least 10, with at least one stderr line, including an empty line. Structured rejection using the final line; the reason can be empty.
Exit at least 10, with no stderr line/output. Aborts, but preserves a process-exit error rather than the structured rejection category.
Cannot start or terminates abnormally. Failure; never treat it as compatibility approval.

That distinction matters to telemetry: a broken script and an intentionally incompatible bundle should not be grouped as the same policy outcome. Preserve the exit behavior and bounded reason rather than inferring acceptance from the presence of a familiar log line.

5. Do not overstate the rejection boundary

The pinned installation sequence mounts the checked bundle, determines the target group and runs the system pre-install handler before install-check. Version-limit checks and the installation handler follow it.

Consequently, a rejected compatibility hook does not prove that no earlier customization ran or changed anything. Review the complete integration before making a “no side effects” claim. Keep the compatibility predicate itself read-only, and qualify any other handler’s behavior separately.

Also review failure duration. A predicate that waits indefinitely for an unavailable dependency can block a useful rejection result. Define a bounded acquisition rule for any additional evidence; if that evidence is unavailable, report the intended hold or failure rather than silently approving.

6. Extend qualification to the exact RAUC build

  1. Preserve the actual hook artifact, interpreter, dependencies and policy revision.
  2. On a recoverable test system, use approved development bundles for matching and mismatching compatibility cases.
  3. Exercise the exit categories above, including rejection with no stderr output, one empty line and a nonempty final line.
  4. Verify hook absence retains the intended normal compatibility behavior.
  5. Verify untrusted or damaged bundles fail through the normal verification path before the predicate can approve them.
  6. Confirm later product/version restrictions remain effective after a successful hook.

These integration tests are proposed, not completed. They require disposable target destinations and the product’s approved recovery procedure; this article provides no live installation commands.

Hypothetical diagnostic-only hook

A team adds an install-check hook that prints the current version and always exits zero. A bundle with the wrong compatible value now passes that hook because the original check was replaced. The corrected design explicitly restores the required comparison and adds missing-input and execution-failure fixtures.

Release with the predicate, input matrix and observed reporting evidence. A custom acceptance point should make the policy more explicit, not rely on a built-in check that is no longer on that path.

Similar Posts