Skip to content

Per-school LINBO hardware groups: school-qualified group ids in import-devices #206

Open

Description

@HappyBasher

This is the linuxmuster-base7 part of linuxmuster/linuxmuster-linbo7#178 (per-school hardware groups on multi-school servers). The design and cross-repo overview stay in linbo7#178. This issue tracks only the base7 work.

Background

On a multi-school server every hardware group is server-wide: two schools with a group raum101 share one start.conf.raum101, and school admins cannot safely be given control over "their" groups. As discussed in linbo7#178, the fix is a school-qualified group id that linuxmuster-import-devices generates. The LINBO client, the GRUB templates and the rsync hooks already pass the group string through unchanged, so they need no changes.

Target behaviour

  • For every school other than default-school, import-devices uses <school>+<group> in place of <group> for every LINBO artefact it derives from the group, e.g. start.conf.abc+raum101 and abc+raum101.cfg.
  • default-school keeps the current flat names exactly as they are. Single-school installations see no change.

Separator + (decided in linbo7#178): neither school names nor group names can contain +, so qualified ids cannot collide and can be parsed without the school list. A name without + belongs to default-school; a name with + splits on the first + into school and group.

Affected code (src/linuxmuster_base7/cli/import_devices.py)

The group column in devices.csv stays unqualified. The qualified id is derived, never stored.

Tasks

Each task is meant to be a separate, small PR:

  • Group id helper pair in linuxmuster_base7.functions: one function for school + group → qualified id and one for qualified id → school + group, so the naming rule lives in one place. linbo_mkgrubimg.py (linbo7) and linuxmuster-tools reuse them. For default-school the id is the plain group. Use them at all the call sites above.
  • Migration of existing installations: for every non-default school, copy each start.conf.<group> that the school uses to start.conf.<school>+<group> (a group shared by several schools gets one copy per school), rewrite the Group = line in each copy to the qualified id (clients use it as HOSTGROUP in offline mode), then re-run the import. It must be idempotent and must never overwrite an existing qualified file.
  • Changelog / release notes: document the new naming and that formerly shared groups are split per school.

Covered outside base7, see linbo7#178: the linbo_sync fallback for group-keyed Windows boot files (BCD, bsmbr, gptlabel) in existing images, and the linbo_mkgrubimg.py fixes for non-default schools.

Constraints

  • Default-school behaviour must stay byte-for-byte identical (DHCP config, file names, links).

Open questions

  1. Where the migration runs: on package upgrade or as an explicit command. To be discussed here.

Contributing

PRs are welcome. Please keep each PR small and limited to one task above, so it can be reviewed and merged on its own. PRs that reformat existing code without a functional reason will not be accepted.

Related: linuxmuster/linuxmuster-linbo7#178, linuxmuster/linuxmuster-tools#36

Activity

  1. TomlDev commented on Sep 28, 2026

    @TomlDev

    Where the migration runs: proposal

    We'd like to propose running it lazily inside import-devices, as part of the per-group step, rather than on package upgrade or as an explicit command.

    How

    In doLinboStartconf(), which after PR 1 (#207) receives the qualified id:

    • If start.conf.<school>+<group> does not exist yet but start.conf.<group> does, copy the plain file (instead of the unconfigured start.conf template), then rewrite Group = in the copy to the qualified id with the existing setGlobalStartconfOption().
    • Likewise, if <group>.cfg exists and is unmanaged (no MANAGEDSTR), copy it to <school>+<group>.cfg so doGrubCfg() keeps treating it as present. Managed cfgs need no copy because they are regenerated anyway.

    splitLinboGroupId() from PR 1 gives the plain group back from the id, so this is roughly ten lines in one function, with no new parameters.

    Why there

    • It runs exactly when it is needed. The switch to qualified names happens in import-devices (writeDhcpDevicesConfig() followed by the group step and only then the DHCP restart). So the qualified files exist before any client gets the new name. A school that is never re-imported keeps its old names and old files, which is consistent.
    • Idempotent and non-destructive. It only acts when the qualified file is missing and never overwrites. The plain files stay in place for default-school and for schools not yet re-imported.
    • No postinst logic. A postinst step would have to walk every school's devices.csv. It would also create the copies at upgrade time while DHCP still hands out the plain names until the next import. Edits made to start.conf.<group> in between would then silently not reach that school after the switch.
    • An explicit command is easy to forget. The next routine import, e.g. after adding a device in the school console, would switch the DHCP names without the files.
    • Shared groups work without extra code: each school gets its own copy on its own import, and a group shared by several schools gets one copy per school.

    Downsides we found

    1. It can't tell migration from a new group. If school abc adds a group raum101 long after the upgrade and default-school (or a not-yet-migrated file) has start.conf.raum101, abc gets a copy of that instead of the unconfigured template. If you'd rather avoid that, we can limit the copy to groups that the school's previous boot/links/<school>.csv referenced as start.conf.<group>. import-devices would read that file before doPxeGroupsBySchool() rewrites it. After the first migrated run it only references qualified names, so the copy then never fires again. The cost is a few more lines and threading that set through.
    2. Hand-edited grub cfgs are copied verbatim. We don't rewrite anything inside an unmanaged cfg. Per the templates the group only appears in echo lines there, and GRUB takes the group from DHCP, so this should be harmless. But a custom cfg that hard-codes the plain group somewhere would keep it.
    3. setGlobalStartconfOption() only matches Group = with exactly one space before the =. For Group=raum101 it inserts a second Group = line directly after [LINBO], above the old one, instead of replacing it. linbo_split_startconf keeps the line order and the client sources /conf/linbo, so the old plain value wins and offline mode keeps the unqualified group. So the regex has to become tolerant (\s*=) in the same PR, not just as a nice-to-have.
    4. Plain files remain. When no default-school device uses start.conf.<group> any more, it stays behind as an orphan and tools list it as a default-school group. Deleting it automatically seems riskier than documenting it.
    5. Old edit paths stop working for that school. After the switch, edits to start.conf.<group> no longer reach the school's clients. That goes into the changelog together with the shared-group split.
    6. The code path stays in import-devices for good. Installations may skip versions, so it can't easily be removed later. After the first run per school it is one isfile() per group.
    7. Custom postsync/prestart scripts see the new $HOSTGROUP. This is independent of where the migration runs, but it belongs in the release notes. The shipped examples/image.postsync suggests $HOSTGROUP as the name of a server-side patch directory. A script that does that looks in …/abc+raum101/ after the migration and silently skips its patches, until the directory is renamed or the script uses the part after the +.

    Group-keyed sidecars that linuxmuster-tools manages (backups, .vdi) are not touched here. They belong to the tools side.

    Release coupling

    PR 1 alone must not ship in a release. Without the migration, the next import of a non-default school finds no start.conf.<school>+<group> and copies the unconfigured template. doGrubCfg() then writes grub.cfg.forced_netboot, so every client of that school is forced into netboot until someone re-creates its start.conf. Unmanaged grub cfgs would also be bypassed. We'd suggest merging PR 1 and PR 2 into 7.4 before the next tag, together with the linbo7 linbo_sync fallback (linuxmuster/linuxmuster-linbo7#181), so migrated schools can still restore existing Windows images (BIOS and EFI).

    If this works for you, PR 2 will implement it in doLinboStartconf(), with tests for the copy, the Group = rewrite, the unmanaged cfg and the no-overwrite case.

  2. HappyBasher commented on Oct 1, 2026

    @HappyBasher
    MemberAuthor

    Release order for the migration: linuxmuster-linbo7#181 (the linbo_sync fallback to the plain group for the Windows boot files BCD, bsmbr and gptlabel in existing images) is a linbofs change. It has to reach the clients before the migration in this issue runs. Otherwise a migrated client of a non-default school syncing an existing Windows image finds only the files with the plain group name, skips the BCD, boot sector and GPT label restore, and on EFI Windows may not boot.

    So the linbo7 release with #181 comes first, and the base7 migration must not ship in the same step or earlier. This also matters for the open question about where the migration runs: if it runs on package upgrade, the linbo7 package with the new linbofs has to be installed and the linbofs rebuilt first (or the migration has to check for it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions