From ee9d5b8498c5b12fd1e7b1ad690f94aa2c152a3b Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 9 Oct 2026 12:23:46 +0000 Subject: [PATCH 1/3] fix(image): restore login-shell PATH and SHELL for GARM runner jobs --- app/pyproject.toml | 2 +- .../templates/cloud-init.sh.j2 | 8 ++++++-- app/tests/integration/commands.py | 12 ++++++++++++ app/tests/unit/test_openstack_builder.py | 8 ++++++-- docs/changelog.md | 4 ++++ 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/pyproject.toml b/app/pyproject.toml index 85eae8cc..da0b4386 100644 --- a/app/pyproject.toml +++ b/app/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "github-runner-image-builder" -version = "0.14.3" +version = "0.14.4" authors = [ { name = "Canonical IS DevOps", email = "is-devops-team@canonical.com" }, ] diff --git a/app/src/github_runner_image_builder/templates/cloud-init.sh.j2 b/app/src/github_runner_image_builder/templates/cloud-init.sh.j2 index e9aebdda..12255dda 100644 --- a/app/src/github_runner_image_builder/templates/cloud-init.sh.j2 +++ b/app/src/github_runner_image_builder/templates/cloud-init.sh.j2 @@ -204,6 +204,9 @@ function configure_system_users() { [Service] PAMName=su-l EOF + # That PAM session takes PATH from /etc/environment and runs no login shell, so ~/.profile + # never adds the user-local bin dir that pipx and `pip install --user` install into. + /usr/bin/sed -i 's|^PATH="|PATH="/home/ubuntu/.local/bin:|' /etc/environment # Create runner user as alias to ubuntu for GARM compatibility. # GARM expects a runner user with /home/runner/actions-runner path. @@ -213,6 +216,7 @@ EOF # --non-unique: allow reusing ubuntu's UID (duplicate UIDs are rejected by default). # --uid/--gid: share ubuntu's UID/GID so both users have identical file permissions. # --no-create-home: skip creating /home/runner; runner's home is set to /home/ubuntu instead. + # --shell: jobs get SHELL from this account, and useradd defaults to /bin/sh. if /usr/bin/getent group runner >/dev/null 2>&1; then echo "runner group already exists; ensuring GID matches ubuntu" /usr/sbin/groupmod --non-unique --gid "$UBUNTU_GID" runner @@ -221,9 +225,9 @@ EOF fi if /usr/bin/id -u runner >/dev/null 2>&1; then echo "runner user already exists; ensuring UID/GID/home match ubuntu" - /usr/sbin/usermod --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --home /home/ubuntu runner + /usr/sbin/usermod --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --home /home/ubuntu --shell /bin/bash runner else - /usr/sbin/useradd --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --no-create-home --home-dir /home/ubuntu runner + /usr/sbin/useradd --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --no-create-home --home-dir /home/ubuntu --shell /bin/bash runner fi /usr/sbin/usermod --append --groups docker,microk8s,lxd,sudo runner # Symlink /home/runner -> /home/ubuntu so GARM's hardcoded /home/runner/actions-runner path resolves correctly. diff --git a/app/tests/integration/commands.py b/app/tests/integration/commands.py index 860cd8ee..e860e308 100644 --- a/app/tests/integration/commands.py +++ b/app/tests/integration/commands.py @@ -35,6 +35,18 @@ class Commands: name="check garm-agent login session drop-in", command="grep -x PAMName=su-l /etc/systemd/system/garm-agent.service.d/login-session.conf", ), + # garm-agent.service runs the runner as `runner` in a PAM session without a login shell; + # systemd-run reproduces that environment. + Commands( + name="check runner job PATH includes user-local bin", + command="sudo systemd-run --quiet --wait --pipe -p User=runner -p PAMName=su-l " + "printenv PATH | grep -q '^/home/ubuntu/.local/bin:'", + ), + Commands( + name="check runner job SHELL is bash", + command="sudo systemd-run --quiet --wait --pipe -p User=runner -p PAMName=su-l " + "printenv SHELL | grep -qx /bin/bash", + ), Commands(name="file permission to /usr/local/bin", command="ls -ld /usr/local/bin"), Commands( name="file permission to /usr/local/bin (create)", command="touch /usr/local/bin/test_file" diff --git a/app/tests/unit/test_openstack_builder.py b/app/tests/unit/test_openstack_builder.py index bc7e2b64..b19d3717 100644 --- a/app/tests/unit/test_openstack_builder.py +++ b/app/tests/unit/test_openstack_builder.py @@ -938,6 +938,9 @@ def test__generate_cloud_init_script( [Service] PAMName=su-l EOF + # That PAM session takes PATH from /etc/environment and runs no login shell, so ~/.profile + # never adds the user-local bin dir that pipx and `pip install --user` install into. + /usr/bin/sed -i 's|^PATH="|PATH="/home/ubuntu/.local/bin:|' /etc/environment # Create runner user as alias to ubuntu for GARM compatibility. # GARM expects a runner user with /home/runner/actions-runner path. @@ -947,6 +950,7 @@ def test__generate_cloud_init_script( # --non-unique: allow reusing ubuntu's UID (duplicate UIDs are rejected by default). # --uid/--gid: share ubuntu's UID/GID so both users have identical file permissions. # --no-create-home: skip creating /home/runner; runner's home is set to /home/ubuntu instead. + # --shell: jobs get SHELL from this account, and useradd defaults to /bin/sh. if /usr/bin/getent group runner >/dev/null 2>&1; then echo "runner group already exists; ensuring GID matches ubuntu" /usr/sbin/groupmod --non-unique --gid "$UBUNTU_GID" runner @@ -955,9 +959,9 @@ def test__generate_cloud_init_script( fi if /usr/bin/id -u runner >/dev/null 2>&1; then echo "runner user already exists; ensuring UID/GID/home match ubuntu" - /usr/sbin/usermod --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --home /home/ubuntu runner + /usr/sbin/usermod --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --home /home/ubuntu --shell /bin/bash runner else - /usr/sbin/useradd --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --no-create-home --home-dir /home/ubuntu runner + /usr/sbin/useradd --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --no-create-home --home-dir /home/ubuntu --shell /bin/bash runner fi /usr/sbin/usermod --append --groups docker,microk8s,lxd,sudo runner # Symlink /home/runner -> /home/ubuntu so GARM's hardcoded /home/runner/actions-runner path resolves correctly. diff --git a/docs/changelog.md b/docs/changelog.md index 97289961..85d2b479 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,9 @@ +## [Restore the login-shell PATH and SHELL for GARM runner jobs](https://github.com/canonical/github-runner-image-builder-operator/pulls) (2026-10-09) + +- Put `/home/ubuntu/.local/bin` on `PATH` and set `SHELL=/bin/bash` for jobs started by the GARM agent, as on the previous runner manager. The agent's PAM session doesn't run a login shell, so commands installed with `pipx` or `pip install --user` failed with `command not found`, and `SHELL` was `/bin/sh`. + ## [#245 Run the GARM agent in a login session](https://github.com/canonical/github-runner-image-builder-operator/pull/245) (2026-10-08) - Run the runner started by the GARM agent in a PAM login session, as the previous runner manager did with `su -`. The agent runs as a system service, so jobs had no `XDG_RUNTIME_DIR` or user D-Bus bus, and tools that keep per-user state there, such as `skopeo login`, `podman` and `buildah`, failed with `mkdir /run/containers: permission denied`. From ce33796ea25893998f3894f8414656623e2c0dfc Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 9 Oct 2026 12:23:56 +0000 Subject: [PATCH 2/3] docs(changelog): link #246 --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 85d2b479..8cf7c0ef 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,6 @@ -## [Restore the login-shell PATH and SHELL for GARM runner jobs](https://github.com/canonical/github-runner-image-builder-operator/pulls) (2026-10-09) +## [#246 Restore the login-shell PATH and SHELL for GARM runner jobs](https://github.com/canonical/github-runner-image-builder-operator/pull/246) (2026-10-09) - Put `/home/ubuntu/.local/bin` on `PATH` and set `SHELL=/bin/bash` for jobs started by the GARM agent, as on the previous runner manager. The agent's PAM session doesn't run a login shell, so commands installed with `pipx` or `pip install --user` failed with `command not found`, and `SHELL` was `/bin/sh`. From 6eaa8aa82ecd93483fc8b6e50166fd9146a45783 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Tue, 13 Oct 2026 13:15:53 +0000 Subject: [PATCH 3/3] ci: put pipx bin dir on PATH for later steps --- .github/workflows/integration_test_app.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_app.yaml b/.github/workflows/integration_test_app.yaml index 6dd41f33..dde93f15 100644 --- a/.github/workflows/integration_test_app.yaml +++ b/.github/workflows/integration_test_app.yaml @@ -47,7 +47,7 @@ jobs: run: | sudo apt-get update sudo apt-get install pipx -y - pipx ensurepath + echo "$HOME/.local/bin" >> "$GITHUB_PATH" pipx install tox - name: Run integration tests (amd64) if: matrix.arch == 'amd64'