Fix rootless Docker HOME for runner #45

Merged
admin merged 3 commits from fix/rootless-docker-home into main 2026-10-14 14:45:57 +00:00
6 changed files with 68 additions and 7 deletions

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
## Не выпущено
- Runner installer сохраняет `HOME` и XDG-пути в user-unit `docker.service`, перезапускает активный daemon и проверяет окружение его процесса.
- Runner installer защищает image и mountpoint root-owned home-каталогом, оставляет регистрацию и workspace в runner-owned каталоге, безопасно форматирует образ по открытому FD и проверяет `nosuid,nodev`.
- Backend `0.11.3`: ключ подписи поисковых снимков читается из Docker secret-файла, а не из аргументов процесса. Добавлена базовая инфраструктура VPS, Caddy HTTPS proxy с разделёнными Compose-сетями и CI-проверка конфигурации; публикация образов и production-деплой остаются выключены до проверки runner, registry, backup/restore и ограниченного deploy-helper.
- Frontend `0.1.2`: на планшетной ширине экран поиска переходит в одну колонку, чтобы фильтры, результаты и документ помещались в окно.
+5
View File
@@ -20,6 +20,11 @@ dedicated ext4 loop-backed Docker data filesystem. The runner account's home
directory is root-owned mode `0711`, so CI cannot rename the image or mountpoint
entries. Docker config remains under `.config`; registration state and the
runner workspace live in `.config/act-runner`, which is runner-owned. The
rootless Docker setup and user service use `.config/act-runner` as writable
`HOME`, `.config` as `XDG_CONFIG_HOME`, and
`.config/act-runner/.local/share` as `XDG_DATA_HOME`. This keeps Docker config
and its systemd user unit in the existing `.config` tree without granting
write access to the protected home.
rootless Docker daemon
and runner user are separate from production Docker; `codex` is not added to
sudo or Docker groups. Jobs do not receive the rootless Docker socket, the
+6
View File
@@ -95,6 +95,12 @@ UFW rule details, and protected file metadata remain unverified.
and runtime state remain in runner-owned subdirectories. It reuses legacy
`0755` config directories, creates the image through a pinned file descriptor,
rejects path replacement, and requires mount options `nosuid,nodev`.
The reported installation blocker is that rootless setup receives this
protected directory as `HOME`; the installer assigns writable runner-owned
HOME/XDG paths, persists them in the `docker.service` user-unit drop-in, and
restarts an active daemon and checks the running process environment. Local
runner guards pass;
the change has not been run on the VPS.
- Gitea repository runner inventory could not be read by the current API
principal (403). The owner has not supplied runner state, and no runner
Online status or runner-hosted workflow is claimed. The manual
+4 -1
View File
@@ -2,6 +2,9 @@
set -Eeuo pipefail
systemctl is-active --quiet akyldash-act-runner.service
runner_uid=$(id -u akyldash-runner)
runuser -u akyldash-runner -- env HOME=/var/lib/akyldash-runner \
runuser -u akyldash-runner -- env \
HOME=/var/lib/akyldash-runner/.config/act-runner \
XDG_CONFIG_HOME=/var/lib/akyldash-runner/.config \
XDG_DATA_HOME=/var/lib/akyldash-runner/.config/act-runner/.local/share \
DOCKER_HOST="unix:///run/user/$runner_uid/docker.sock" \
/usr/bin/docker info --format '{{.ServerVersion}}' >/dev/null
+30 -5
View File
@@ -4,6 +4,8 @@ set -Eeuo pipefail
readonly runner_user=akyldash-runner
readonly runner_home=/var/lib/akyldash-runner
readonly runner_runtime_home="$runner_home/.config/act-runner"
readonly runner_xdg_config_home="$runner_home/.config"
readonly runner_xdg_data_home="$runner_runtime_home/.local/share"
readonly runner_store="$runner_home/docker-data"
readonly runner_image="$runner_home/docker-store.img"
readonly runner_mount_unit_name="$(systemd-escape --path --suffix=mount /var/lib/akyldash-runner/docker-data)"
@@ -92,6 +94,8 @@ runner_ensure_private_directory "$runner_home/.config/systemd" "$runner_user" "$
runner_ensure_private_directory "$runner_home/.config/systemd/user" "$runner_user" "$runner_user" || die 'runner user systemd directory failed validation'
runner_ensure_private_directory "$runner_home/.config/systemd/user/docker.service.d" "$runner_user" "$runner_user" || die 'runner Docker override directory failed validation'
runner_ensure_private_directory "$runner_runtime_home" "$runner_user" "$runner_user" || die 'runner runtime home failed directory validation'
runner_ensure_private_directory "$runner_runtime_home/.local" "$runner_user" "$runner_user" || die 'runner XDG data parent failed directory validation'
runner_ensure_private_directory "$runner_xdg_data_home" "$runner_user" "$runner_user" || die 'runner XDG data directory failed directory validation'
if [[ -e $runner_home/.runner || -L $runner_home/.runner ]]; then
[[ -f $runner_home/.runner && ! -L $runner_home/.runner ]] || die 'legacy runner registration state is not a regular file'
if [[ ! -e $runner_runtime_home/.runner && ! -L $runner_runtime_home/.runner ]]; then
@@ -141,6 +145,12 @@ TasksMax=1024
Restart=always
RestartSec=5
EOF
runner_install_user_content "$runner_home/.config/systemd/user/docker.service.d/environment.conf" "$runner_user" "$runner_user" 0644 <<EOF
[Service]
Environment=HOME=$runner_runtime_home
Environment=XDG_CONFIG_HOME=$runner_xdg_config_home
Environment=XDG_DATA_HOME=$runner_xdg_data_home
EOF
runner_install_content /etc/systemd/system/akyldash-act-runner.service root root 0644 <<EOF
[Unit]
Description=AkylDash repository Gitea Actions runner
@@ -154,6 +164,8 @@ Slice=user-$runner_uid.slice
User=$runner_user
Group=$runner_user
Environment=HOME=$runner_runtime_home
Environment=XDG_CONFIG_HOME=$runner_xdg_config_home
Environment=XDG_DATA_HOME=$runner_xdg_data_home
WorkingDirectory=$runner_runtime_home
Environment=XDG_RUNTIME_DIR=/run/user/$runner_uid
Environment=DOCKER_HOST=unix:///run/user/$runner_uid/docker.sock
@@ -172,7 +184,7 @@ ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
RestrictSUIDSGID=yes
ReadWritePaths=$runner_runtime_home /var/log/akyldash-runner /run/user/$runner_uid
ReadWritePaths=$runner_runtime_home $runner_xdg_config_home /var/log/akyldash-runner /run/user/$runner_uid
StandardOutput=append:/var/log/akyldash-runner/act_runner.log
StandardError=append:/var/log/akyldash-runner/act_runner.log
@@ -205,14 +217,27 @@ runner_install_file "$(dirname "$0")/akyldash-runner-restart.service" /etc/syste
loginctl enable-linger "$runner_user"
systemctl daemon-reload
systemctl start "user@$runner_uid.service"
rootless_docker_unit="$runner_home/.config/systemd/user/docker.service"
rootless_docker_unit="$runner_xdg_config_home/systemd/user/docker.service"
rootless_env=(HOME="$runner_runtime_home" XDG_CONFIG_HOME="$runner_xdg_config_home" XDG_DATA_HOME="$runner_xdg_data_home" XDG_RUNTIME_DIR="/run/user/$runner_uid" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$runner_uid/bus")
runuser -u "$runner_user" -- env "${rootless_env[@]}" sh -c 'test -w "$HOME" && test -w "$XDG_CONFIG_HOME" && test -w "$XDG_DATA_HOME"' || die 'rootless Docker HOME and XDG directories must be writable by the runner'
if [[ -e $rootless_docker_unit || -L $rootless_docker_unit ]]; then
[[ -f $rootless_docker_unit && ! -L $rootless_docker_unit && $(stat -c %U "$rootless_docker_unit") == "$runner_user" ]] || die 'existing rootless Docker unit has an unexpected type or owner'
grep -Fq 'dockerd-rootless.sh' "$rootless_docker_unit" || die 'existing rootless Docker unit differs from the expected service'
else
runuser -u "$runner_user" -- env HOME="$runner_home" XDG_RUNTIME_DIR="/run/user/$runner_uid" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$runner_uid/bus" dockerd-rootless-setuptool.sh install
runuser -u "$runner_user" -- env "${rootless_env[@]}" dockerd-rootless-setuptool.sh install
fi
runuser -u "$runner_user" -- env HOME="$runner_home" XDG_RUNTIME_DIR="/run/user/$runner_uid" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$runner_uid/bus" systemctl --user daemon-reload
runuser -u "$runner_user" -- env HOME="$runner_home" XDG_RUNTIME_DIR="/run/user/$runner_uid" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$runner_uid/bus" systemctl --user enable --now docker.service
runuser -u "$runner_user" -- env "${rootless_env[@]}" systemctl --user daemon-reload
runuser -u "$runner_user" -- env "${rootless_env[@]}" systemctl --user enable docker.service
if runuser -u "$runner_user" -- env "${rootless_env[@]}" systemctl --user is-active --quiet docker.service; then
runuser -u "$runner_user" -- env "${rootless_env[@]}" systemctl --user restart docker.service
else
runuser -u "$runner_user" -- env "${rootless_env[@]}" systemctl --user start docker.service
fi
docker_pid=$(runuser -u "$runner_user" -- env "${rootless_env[@]}" systemctl --user show docker.service --property=MainPID --value)
[[ $docker_pid =~ ^[1-9][0-9]*$ && -r /proc/$docker_pid/environ ]] || die 'rootless Docker process environment is unavailable'
docker_environment=$(tr '\0' '\n' <"/proc/$docker_pid/environ") || die 'could not read rootless Docker process environment'
for expected in "HOME=$runner_runtime_home" "XDG_CONFIG_HOME=$runner_xdg_config_home" "XDG_DATA_HOME=$runner_xdg_data_home"; do
grep -Fqx -- "$expected" <<<"$docker_environment" || die "running rootless Docker process is missing $expected"
done
printf '\nRunner runtime installed. Register it interactively, then enable its services as described in docs/operations/CI_RUNNER.md.\n'
+22 -1
View File
@@ -19,8 +19,29 @@ assert_absent 'var/run/docker.sock|metrics:|health_check:|production.env|review_
grep -Fq 'docker-ce-rootless-extras' "$installer"
grep -Fq 'runner_ensure_image "$runner_image" "$runner_image_bytes" 0 711' "$installer"
grep -Fq 'runner_lock_home "$runner_home"' "$installer"
grep -Fq 'test -w "$HOME" && test -w "$XDG_CONFIG_HOME" && test -w "$XDG_DATA_HOME"' "$installer"
grep -Fq 'rootless_env=(HOME="$runner_runtime_home" XDG_CONFIG_HOME="$runner_xdg_config_home" XDG_DATA_HOME="$runner_xdg_data_home"' "$installer"
grep -Fq 'Environment=XDG_CONFIG_HOME=$runner_xdg_config_home' "$installer"
grep -Fq 'Environment=XDG_DATA_HOME=$runner_xdg_data_home' "$installer"
grep -Fq 'runner_home/.config/systemd/user/docker.service.d/environment.conf' "$installer"
grep -Fq 'Environment=HOME=$runner_runtime_home' "$installer"
grep -Fq 'systemctl --user restart docker.service' "$installer"
grep -Fq 'systemctl --user show docker.service --property=MainPID --value' "$installer"
grep -Fq 'tr '\''\0'\'' '\''\n'\'' <"/proc/$docker_pid/environ"' "$installer"
grep -Fq 'grep -Fqx -- "$expected"' "$installer"
if grep -Fq 'systemctl --user set-environment' "$installer"; then
echo 'Rootless Docker environment must persist in its user unit drop-in' >&2
exit 1
fi
grep -Fq 'HOME=/var/lib/akyldash-runner/.config/act-runner' infra/runner/act-runner-healthcheck.sh
grep -Fq 'XDG_CONFIG_HOME=/var/lib/akyldash-runner/.config' infra/runner/act-runner-healthcheck.sh
grep -Fq 'XDG_DATA_HOME=/var/lib/akyldash-runner/.config/act-runner/.local/share' infra/runner/act-runner-healthcheck.sh
if grep -Fq 'env HOME="$runner_home"' "$installer"; then
echo 'Protected runner home must not be passed as rootless Docker HOME' >&2
exit 1
fi
grep -Fq 'die '\''stop akyldash-act-runner.service after all jobs finish before rerunning this installer' "$installer"
grep -Fq 'ReadWritePaths=$runner_runtime_home /var/log/akyldash-runner /run/user/$runner_uid' "$installer"
grep -Fq 'ReadWritePaths=$runner_runtime_home $runner_xdg_config_home /var/log/akyldash-runner /run/user/$runner_uid' "$installer"
grep -Fq 'os.O_NOFOLLOW | os.O_CLOEXEC' infra/runner/secure-runner-image.py
grep -Fq 'os.posix_fallocate(image_fd, 0, int(bytes_text))' infra/runner/secure-runner-image.py
grep -Fq 'pass_fds=(image_fd,)' infra/runner/secure-runner-image.py