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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions