Skip to content

Profile install scripts run before the guest user is created #381

Description

@hbrodin

Summary

Profile pre_install / post_install scripts run before the guest user is created, so a profile that writes into the guest user's home directory (e.g. installing dotfiles) either fails or writes into the wrong place. The files land in the base CI rootfs's leftover /home/ubuntu, which is then orphaned when guest-config.sh replaces the uid-1000 user.

Root cause

compose_recipe in src/setup.rs assembles the chroot provisioning script in this order:

  1. preamble + export GUEST_USER=...
  2. base packages
  3. profile pre-install scripts (src/setup.rs:664)
  4. third-party repos + packages
  5. profile post-install scripts (src/setup.rs:704)
  6. guest-config.shthis is where the guest user is created (src/setup.rs:720, script at scripts/guest/guest-config.sh:61-95)
  7. claude-code / codex

So at steps 3 and 5 the guest user and its /home/<user> do not exist yet. The comment at src/setup.rs:719 shows the placement was only chosen to be "before claude-code" — profile scripts that depend on the user were never accounted for.

Symptoms

Two users / home directories are visible in the guest:

  • <guest_user> / /home/<guest_user> — the configured guest user, created by guest-config.sh at step 6.
  • ubuntu / /home/ubuntu — ships with the base CI rootfs image (not created by coop at launch).

When a custom guest user is configured, guest-config.sh evicts the pre-existing uid-1000 user:

# scripts/guest/guest-config.sh:69-75
EXISTING=$(getent passwd 1000 | cut -d: -f1) || true
if [[ -n "$EXISTING" ]]; then
    userdel "$EXISTING"          # no -r → /home/ubuntu is left on disk
fi
useradd -m -s /bin/bash --uid 1000 -G sudo,docker "${GUEST_USER}"

Two leaks:

  1. Only the uid-1000 occupant is removed. If the base image's ubuntu is not at uid 1000, it survives as a full account alongside <guest_user>.
  2. userdel is called without -r, so /home/ubuntu is left behind as an orphan even when the account is removed.

A profile installing dotfiles at step 5 finds no /home/<guest_user>. If it falls back to (or hardcodes) /home/ubuntu, the files land there — and are then stranded when step 6 deletes/replaces the uid-1000 user, leaving the real guest home empty.

Fix

Create the guest user before profile scripts run. guest-config.sh cannot simply be moved to the top, because it also symlinks the claude binary that is not installed until step 7. Split it instead:

  1. Extract the user + home creation block (scripts/guest/guest-config.sh:61-95: useradd/usermod, sudoers, /home/<user> ownership, .local tree, .ssh keys) into its own script segment.
  2. Run that segment in compose_recipe right after base packages and before pre_installs (src/setup.rs:663).
  3. Leave the rest of guest-config.sh (networking, docker daemon, claude symlink, services, sshd) where it is at src/setup.rs:720.
  4. Add -r to the userdel so the orphan /home/ubuntu does not linger.
  5. Add a unit test asserting the useradd line precedes profile pre/post-install in the composed recipe (guards against regressing the ordering).

Acceptance criteria

  • A profile post_install (or pre_install) script can write to /home/<guest_user> and have it persist.
  • No orphaned /home/ubuntu remains after provisioning with a custom guest user.
  • A test pins the ordering: guest-user creation before profile scripts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions