Skip to content

feat: support custom user accounts via ALCLESS_GROUP (macOS and Linux) - #91

Open
sonhanguyen wants to merge 5 commits into
AkihiroSuda:masterfrom
sonhanguyen:master
Open

sonhanguyen wants to merge 5 commits into
AkihiroSuda:masterfrom
sonhanguyen:master

Conversation

@sonhanguyen

Copy link
Copy Markdown

Summary

Adds a "group mode" for using pre-existing user accounts with alcless, selected by the ALCLESS_GROUP environment variable, and implements it for both macOS and Linux.

This supersedes #42, which I closed. That earlier version was macOS-only and had a few unsafe corners; this one adds the Linux implementation and fixes the issues below.

What group mode does

When ALCLESS_GROUP is set:

  • the instance name is used directly as the username, instead of alcless_${USER}_${INSTANCE}
  • instance discovery lists the members of that group
  • an existing account can be adopted for alcless use (sudoers entry plus group membership)

This lets people manage the sandbox accounts themselves (home directory, shell, group membership) rather than having alcless own them.

Platform support

  • Linux: GroupUsers reads supplementary members from getent group and also includes users whose primary group is the target group (getent passwd). GroupSetupCmds uses groupadd -f and usermod -aG, both idempotent.
  • macOS: group discovery now uses dscl . -read (the previous dscl . list /Groups/<g> GroupMembership returned empty output on my machine and never listed any members). Group setup passes the group name as a direct dscl argument instead of interpolating it into a root sh -c, and uses -merge instead of -append so a repeat create does not duplicate the membership entry.

Safety

In group mode an instance name is a real username, so delete now refuses to remove an account that is not a member of ALCLESS_GROUP, instead of running account deletion on any existing account name.

Testing

  • go test ./... on macOS and on Linux (in a golang:1.26 container).
  • CI passes on ubuntu-24.04, macos-15 and macos-26.

Add support for using pre-existing user accounts with alcless via the
ALCLESS_GROUP environment variable.

When ALCLESS_GROUP is set:
- Instance name is used as the username (instead of alcless_USER_INSTANCE)
- Discovery is done via group membership
- Pre-existing users can be set up for alcless with sudoers and group membership

Backwards compatible: without ALCLESS_GROUP, behavior is unchanged.

This commit also includes:
- Comprehensive test coverage for the new functionality
- Updated documentation in README and command help text
- Fixed assert.Error usage in store tests
- Removed inconsequential test coverage workflow step

Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
Add GroupUsers and GroupSetupCmds to the Linux userutil implementation.
These were previously implemented only on Darwin, so ALCLESS_GROUP group
mode failed to compile on Linux (pkg/store and cmd/.../create import
both functions unconditionally).

GroupUsers reads a group's supplementary members via `getent group` and
merges in every user whose primary GID matches the group, via `getent
passwd`, deduping the result. GroupSetupCmds creates the group
(idempotently, via `groupadd -f`) and adds the instance user to it
(idempotently, via `usermod -aG`).

Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
Two fixes to the Darwin group-mode implementation:

- GroupUsers used `dscl . list ... GroupMembership`, which silently
  returns empty output on this platform's dscl instead of the group's
  members. Switch to `dscl . -read`, which returns the real value, and
  extract the output parser into parseGroupMembership so it can be
  tested directly. A missing group (eDSRecordNotFound) now returns no
  members instead of an error, matching the Linux behavior.

- GroupSetupCmds interpolated groupName into a `sudo sh -c "dscl ..."`
  string, and relied on `2>/dev/null || true` for -create idempotency
  and `-append` for membership (which duplicates an entry on repeat
  invocations). Pass groupName as a direct dscl argument instead of a
  shell string, check group existence up front via the new groupExists
  helper to decide whether -create is needed, and use `-merge` (adds a
  value only if not already present) instead of `-append`.

Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
In group mode the instance name is an existing username, and delete
looked it up and ran DeleteUserCmds without checking that it was ever
a member of ALCLESS_GROUP. `alclessctl delete <name>` could therefore
delete any account on the host that happens to already exist, and
remove its /etc/sudoers.d entry, as long as the account satisfied
store.ValidateName.

Check group membership before building the delete commands, and
refuse with an error when the account is not a member.

Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
pkg/store/store_test.go and pkg/userutil/userutil_test.go (added in the
custom-account commit this branch builds on) were missing the Apache
license header every other file in the repo carries, which fails this
repo's `addlicense -check` lint job. Unrelated to the group-mode work
in this PR; fixing it here since it otherwise blocks CI on this branch.

Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
@sonhanguyen
sonhanguyen marked this pull request as ready for review September 23, 2026 13:32
@AkihiroSuda

Copy link
Copy Markdown
Owner

Thanks, but the design will need to be changed after merging #85.
With #85, sandbox users will be named like u502 (the length matters for Homebrew) and they will always belong to the alcless group.

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.

2 participants