Skip to content

[architect] setup hook dispatcher is triplicated: ublue-system-setup / ublue-user-setup / ublue-privileged-setup are byte-identical apart from one key #1061

Description

@kubestellar-hive

Architecture Finding

Type: tech-debt / duplicated-abstraction
Affected area: system_files/shared/usr/bin/ublue-system-setup, system_files/shared/usr/bin/ublue-user-setup, system_files/shared/usr/bin/ublue-privileged-setup, system_files/shared/usr/lib/ublue/setup-services/

The three first-boot setup services are the same dispatcher written out three times. Each is 28–29 lines and contains its own private copy of:

  1. an 11-line get_config() jq reader over /etc/ublue-os/setup.json
  2. the HOOKS_VERBOSE / set -x handling
  3. the if [ -d ... ]; then for script in "$DIR"/*; do bash "$script"; done; fi dispatch loop

diff proves it — the only variation across all three files is one config key and one default directory:

$ diff ublue-system-setup ublue-user-setup
17c17
< SYSTEM_HOOKS_DIRECTORY="$(get_config '."system-hooks-directory"' "/usr/share/ublue-os/system-setup.hooks.d")"
---
> USER_HOOKS_DIRECTORY="$(get_config '."user-hooks-directory"' "/usr/share/ublue-os/user-setup.hooks.d")"

(plus the corresponding variable name in the loop). ublue-privileged-setup differs from ublue-system-setup by exactly the same two lines.

The repo already has the right home for this: system_files/shared/usr/lib/ublue/setup-services/libsetup.sh is the established shared library for setup services, sourced by every hook in *-setup.hooks.d/. The dispatchers never adopted it.

The test suite is triplicated to match — tests/test_setup_scripts.bats and tests/test_privileged_setup.bats each restate "get_config returns fallback", "runs hooks in hooks directory", "exits cleanly when hooks directory missing" against a different copy of the same code.

Impact

  • Defects ship three times. The duplicated dispatch loop has a latent bug that is present in all three copies: when the hooks directory exists but is empty, the unquoted glob does not expand and the loop runs bash "$DIR/*", exiting 127. Reproduced against main:
    $ SETUP_CONFIG_FILE=$W/setup.json bash system_files/shared/usr/bin/ublue-system-setup
    bash: /tmp/.../hooks/*: No such file or directory
    EXIT=127
    
    Fixing it requires the same edit in three files, and neither test file covers the empty-directory case. Filed separately as the behavioural defect.
  • Any change to hook discovery (ordering, executable-bit filtering, error isolation between hooks, config schema) must be made three times and can silently land in only one or two.
  • The three services already diverge cosmetically (a stray comment in ublue-user-setup, tab-vs-space indentation in the tests), which is how semantic divergence starts.
  • Downstream variants (bluefin, bluefin-lts, dakota, server, forks) all inherit these three scripts from system_files/shared/, so drift propagates org-wide.

Recommendation

Extract the dispatcher into system_files/shared/usr/lib/ublue/setup-services/hookrunner.sh exposing get_config() and run_setup_hooks <config-key> <default-directory>, and reduce the three /usr/bin/ublue-*-setup entry points to thin wrappers that supply only their key and default. Source it via an overridable $HOOKRUNNER, matching the existing $LIBSETUP convention in system-setup.hooks.d/10-framework.sh. Add a structural guard test so a private get_config or dispatch loop cannot be reintroduced.


Filed by architect agent (ACMM L5 — hold-gated mode)

🐝 Hive Agent: architect | Instance: hosted-projectbluefin-knuckle-gjvq | SHA: unknown

— hive: agent=architect backend=copilot model=claude-opus-5

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/architectApproved by a Hive merger/owner for auto-merge on green CIarchitectureApproved by a Hive merger/owner for auto-merge on green CIhive/hosted-projectbluefin-knuckle-gjvqApproved by a Hive merger/owner for auto-merge on green CItech-debtApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions