CI / validate (pull_request) Successful in 1m33s
CI / molecule-tests (6) (pull_request) Successful in 4m12s
CI / molecule-tests (4) (pull_request) Successful in 4m23s
CI / molecule-tests (2) (pull_request) Successful in 4m25s
CI / molecule-tests (5) (pull_request) Successful in 6m17s
CI / molecule-tests (3) (pull_request) Successful in 7m7s
CI / molecule-tests (1) (pull_request) Successful in 7m47s
CI / auto-merge (pull_request) Successful in 55s
Docker 28.x vendors containerd v2.1.x internally. containerd.io >= 2.3 ships a shim that returns a protobuf BootstrapResult which the vendored 2.1.x code cannot parse, causing every container start to fail with: "failed to create TTRPC connection: unsupported protocol" The role now: - Detects the installed Docker and containerd.io versions - If Docker < 29 and containerd.io >= 2.3, downgrades to the latest compatible 2.2.x from the Docker APT repository - Holds the containerd.io package to prevent future auto-upgrades This was triggered by containerd.io 2.2.6 → 2.3.3 auto-upgrading via apt, which broke all CI runners simultaneously. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
394 lines
16 KiB
YAML
394 lines
16 KiB
YAML
---
|
|
- name: Ensure keyrings directory exists (Debian/Ubuntu)
|
|
ansible.builtin.file:
|
|
path: "/etc/apt/keyrings"
|
|
state: directory
|
|
mode: "0755"
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Download and dearmor Docker GPG key (Debian/Ubuntu)
|
|
ansible.builtin.shell: |
|
|
set -o pipefail
|
|
curl -fsSL "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg" \
|
|
| gpg --dearmor --yes -o {{ gitea_runner_docker_gpg_key_path }}
|
|
args:
|
|
creates: "{{ gitea_runner_docker_gpg_key_path }}"
|
|
executable: /bin/bash
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Add Docker APT repository (Debian/Ubuntu)
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/docker.list
|
|
content: "{{ gitea_runner_docker_apt_source_line }}\n"
|
|
mode: "0644"
|
|
register: gitea_runner_docker_apt_repo
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Update apt cache after adding Docker repo (Debian/Ubuntu)
|
|
ansible.builtin.apt:
|
|
update_cache: true
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- gitea_runner_docker_apt_repo is changed
|
|
|
|
# Install Docker packages from the upstream Docker APT repository.
|
|
# We do NOT pin to 28.x because recent Ubuntu releases (e.g. 26.04/plucky)
|
|
# may not have 28.x packages in the Docker repo, and Docker 29 is safe
|
|
# for rootless mode when the daemon.json disables the containerd snapshotter
|
|
# and sets a conservative default nofile ulimit (see daemon.json tasks below).
|
|
- name: Install rootless Docker dependencies (Debian/Ubuntu)
|
|
ansible.builtin.apt:
|
|
name:
|
|
- uidmap
|
|
- slirp4netns
|
|
- passt
|
|
- fuse-overlayfs
|
|
- docker-ce
|
|
- docker-ce-cli
|
|
- docker-ce-rootless-extras
|
|
- containerd.io
|
|
- docker-compose-plugin
|
|
- rsync
|
|
state: present
|
|
register: gitea_runner_docker_install
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
# Docker 28.x vendors containerd v2.1.x. containerd.io >= 2.3 ships a shim that
|
|
# returns a protobuf BootstrapResult the vendored 2.1.x code cannot parse, causing
|
|
# "failed to create TTRPC connection: unsupported protocol" on every container start.
|
|
# Detect the mismatch and downgrade containerd.io to the latest compatible 2.2.x.
|
|
- name: Check installed Docker CE version (Debian/Ubuntu)
|
|
ansible.builtin.command: dpkg-query -W -f='${Version}' docker-ce
|
|
register: gitea_runner_docker_version_check
|
|
changed_when: false
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Check installed containerd.io version (Debian/Ubuntu)
|
|
ansible.builtin.shell: "set -o pipefail; dpkg-query -W -f='${Version}' containerd.io 2>/dev/null | cut -d: -f2 | cut -d- -f1"
|
|
args:
|
|
executable: /bin/bash
|
|
register: gitea_runner_containerd_version_check
|
|
changed_when: false
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Determine if containerd.io is incompatible with installed Docker
|
|
ansible.builtin.set_fact:
|
|
gitea_runner_containerd_needs_downgrade: >-
|
|
{{
|
|
gitea_runner_containerd_version_check.stdout.split('.')[0] | int > gitea_runner_containerd_max_compatible_major
|
|
or (
|
|
gitea_runner_containerd_version_check.stdout.split('.')[0] | int == gitea_runner_containerd_max_compatible_major
|
|
and gitea_runner_containerd_version_check.stdout.split('.')[1] | int > gitea_runner_containerd_max_compatible_minor
|
|
)
|
|
}}
|
|
gitea_runner_docker_major: "{{ gitea_runner_docker_version_check.stdout.split('.')[0] | default('0') | int }}"
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Find latest compatible containerd.io version (Debian/Ubuntu)
|
|
ansible.builtin.shell: |
|
|
set -o pipefail
|
|
apt-cache madison containerd.io \
|
|
| awk -F'|' '{print $2}' \
|
|
| tr -d ' ' \
|
|
| grep -E '^{{ gitea_runner_containerd_max_compatible_major }}\.{{ gitea_runner_containerd_max_compatible_minor }}\.' \
|
|
| head -1
|
|
args:
|
|
executable: /bin/bash
|
|
register: gitea_runner_containerd_compatible_version
|
|
changed_when: false
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- gitea_runner_containerd_needs_downgrade | default(false)
|
|
- gitea_runner_docker_major | int < 29
|
|
|
|
- name: Downgrade containerd.io to compatible version (Debian/Ubuntu)
|
|
ansible.builtin.apt:
|
|
name: "containerd.io={{ gitea_runner_containerd_compatible_version.stdout }}"
|
|
state: present
|
|
allow_downgrades: true
|
|
register: gitea_runner_containerd_downgrade
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- gitea_runner_containerd_needs_downgrade | default(false)
|
|
- gitea_runner_docker_major | int < 29
|
|
- gitea_runner_containerd_compatible_version.stdout | length > 0
|
|
|
|
- name: Hold containerd.io package to prevent auto-upgrade (Debian/Ubuntu)
|
|
ansible.builtin.dpkg_selections:
|
|
name: containerd.io
|
|
selection: hold
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Update pacman cache (Arch Linux)
|
|
community.general.pacman:
|
|
update_cache: true
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
changed_when: false
|
|
|
|
- name: Install rootless Docker dependencies (Arch Linux)
|
|
community.general.pacman:
|
|
name:
|
|
- docker
|
|
- docker-compose
|
|
- slirp4netns
|
|
- passt
|
|
- fuse-overlayfs
|
|
- rsync
|
|
# rootlesskit is the userspace networking/namespace driver for rootless Docker.
|
|
# It is NOT a dependency of the "docker" package on Arch and must be installed explicitly.
|
|
- rootlesskit
|
|
state: present
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
# The tun kernel module is required by rootlesskit (both slirp4netns and pasta
|
|
# drivers create a tap device inside a user namespace). On Arch Linux, CONFIG_TUN=m
|
|
# so the module must be loaded. If the running kernel doesn't match the installed
|
|
# kernel (e.g. after a pacman -Syu that upgraded linux but didn't reboot), modprobe
|
|
# will fail — in that case we warn but don't fail, as a reboot will fix it.
|
|
- name: Load tun kernel module for rootless networking
|
|
community.general.modprobe:
|
|
name: tun
|
|
state: present
|
|
ignore_errors: true
|
|
register: gitea_runner_tun_module
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
- name: Warn if tun module could not be loaded (kernel mismatch — reboot needed)
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
WARNING: Could not load the tun kernel module. This is likely because the
|
|
running kernel ({{ ansible_facts['kernel'] }}) does not match the installed
|
|
kernel modules. A reboot is required before rootless Docker can start.
|
|
when:
|
|
- ansible_facts['os_family'] == 'Archlinux'
|
|
- gitea_runner_tun_module is failed
|
|
|
|
# Arch Linux's "docker" package does not ship the rootless setup scripts (dockerd-rootless-setuptool.sh
|
|
# and dockerd-rootless.sh), unlike Debian/Ubuntu's docker-ce-rootless-extras. No official Arch package
|
|
# provides them, so fetch them from the upstream moby/moby contrib/ directory. They are installed
|
|
# alongside the docker binaries (/usr/bin) because dockerd-rootless-setuptool.sh derives its BIN
|
|
# directory from its own location and expects docker/dockerd/rootlesskit to be co-located there.
|
|
- name: Fetch rootless Docker setup scripts (Arch Linux)
|
|
ansible.builtin.get_url:
|
|
url: "https://raw.githubusercontent.com/moby/moby/{{ gitea_runner_rootless_scripts_ref }}/contrib/{{ item.name }}"
|
|
dest: "{{ gitea_runner_rootless_scripts_install_dir }}/{{ item.name }}"
|
|
mode: "0755"
|
|
owner: root
|
|
group: root
|
|
loop:
|
|
- name: dockerd-rootless-setuptool.sh
|
|
- name: dockerd-rootless.sh
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
- name: Check if rootless Docker is already set up
|
|
ansible.builtin.stat:
|
|
path: "{{ gitea_runner_home }}/.config/systemd/user/docker.service"
|
|
register: gitea_runner_rootless_docker_check
|
|
|
|
# Create the systemd override BEFORE running the setuptool so that when
|
|
# the setuptool starts docker.service, it picks up the pasta network driver
|
|
# instead of the default slirp4netns (which may fail on some kernels).
|
|
- name: Ensure systemd user override directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gitea_runner_home }}/.config/systemd/user/docker.service.d"
|
|
state: directory
|
|
mode: "0755"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- not gitea_runner_rootless_docker_check.stat.exists
|
|
|
|
- name: Pre-configure rootless Docker network driver override
|
|
ansible.builtin.copy:
|
|
dest: "{{ gitea_runner_home }}/.config/systemd/user/docker.service.d/override.conf"
|
|
content: |
|
|
[Service]
|
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET={{ gitea_runner_docker_rootless_net_driver }}"
|
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER={{ 'implicit' if gitea_runner_docker_rootless_net_driver == 'pasta' else 'builtin' }}"
|
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
|
{% endif %}
|
|
mode: "0644"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- not gitea_runner_rootless_docker_check.stat.exists
|
|
|
|
# Write daemon.json BEFORE the setuptool starts dockerd, so Docker 29
|
|
# starts with containerd snapshotter disabled from the very first boot.
|
|
# Without this, Docker 29 uses containerd snapshots by default, which
|
|
# causes instability in rootless mode.
|
|
- name: Ensure Docker config directory exists (pre-setup)
|
|
ansible.builtin.file:
|
|
path: "{{ gitea_runner_home }}/.config/docker"
|
|
state: directory
|
|
mode: "0755"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- not gitea_runner_rootless_docker_check.stat.exists
|
|
|
|
- name: Pre-configure rootless Docker daemon.json (disable containerd snapshotter)
|
|
ansible.builtin.copy:
|
|
dest: "{{ gitea_runner_home }}/.config/docker/daemon.json"
|
|
content: |
|
|
{
|
|
"live-restore": {{ gitea_runner_docker_live_restore | to_json }},
|
|
"shutdown-timeout": {{ gitea_runner_docker_shutdown_timeout }},
|
|
"max-concurrent-downloads": {{ gitea_runner_docker_max_concurrent_downloads }},
|
|
"max-concurrent-uploads": {{ gitea_runner_docker_max_concurrent_uploads }},
|
|
"default-ulimits": {
|
|
"nofile": {"Name": "nofile", "Hard": {{ gitea_runner_docker_default_nofile }}, "Soft": {{ gitea_runner_docker_default_nofile }}}
|
|
},
|
|
"features": {
|
|
"containerd-snapshotter": false
|
|
},
|
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
|
"ipv6": true,
|
|
"ip6tables": true,
|
|
"fixed-cidr-v6": "{{ gitea_runner_docker_ipv6_cidr }}",
|
|
"dns": ["10.0.2.3", "8.8.8.8"]
|
|
{% else %}
|
|
"ipv6": false,
|
|
"dns": ["8.8.8.8", "1.1.1.1"]
|
|
{% endif %}
|
|
}
|
|
mode: "0644"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- not gitea_runner_rootless_docker_check.stat.exists
|
|
|
|
- name: Set up rootless Docker for runner user
|
|
ansible.builtin.command: dockerd-rootless-setuptool.sh install
|
|
args:
|
|
creates: "{{ gitea_runner_home }}/.config/systemd/user/docker.service"
|
|
become: true
|
|
become_user: "{{ gitea_runner_service_user }}"
|
|
environment:
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
|
DOCKERD_ROOTLESS_ROOTLESSKIT_NET: "{{ gitea_runner_docker_rootless_net_driver }}"
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- not gitea_runner_rootless_docker_check.stat.exists
|
|
|
|
- name: Start rootless Docker daemon (systemd user service)
|
|
ansible.builtin.command: systemctl --user start docker
|
|
become: true
|
|
become_user: "{{ gitea_runner_service_user }}"
|
|
environment:
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
|
changed_when: true
|
|
when: gitea_runner_docker_rootless_setup
|
|
|
|
- name: Enable rootless Docker daemon (systemd user service)
|
|
ansible.builtin.command: systemctl --user enable docker
|
|
become: true
|
|
become_user: "{{ gitea_runner_service_user }}"
|
|
environment:
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
|
changed_when: true
|
|
when: gitea_runner_docker_rootless_setup
|
|
|
|
- name: Ensure Docker config directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gitea_runner_home }}/.config/docker"
|
|
state: directory
|
|
mode: "0755"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
when: gitea_runner_docker_rootless_setup
|
|
|
|
- name: Configure rootless Docker network driver
|
|
ansible.builtin.copy:
|
|
dest: "{{ gitea_runner_home }}/.config/systemd/user/docker.service.d/override.conf"
|
|
content: |
|
|
[Service]
|
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET={{ gitea_runner_docker_rootless_net_driver }}"
|
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER={{ 'implicit' if gitea_runner_docker_rootless_net_driver == 'pasta' else 'builtin' }}"
|
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
|
{% endif %}
|
|
mode: "0644"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
register: gitea_runner_docker_network_override
|
|
when: gitea_runner_docker_rootless_setup
|
|
|
|
- name: Reload systemd user daemon if network config changed
|
|
ansible.builtin.command: systemctl --user daemon-reload
|
|
become: true
|
|
become_user: "{{ gitea_runner_service_user }}"
|
|
environment:
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
|
changed_when: true
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- gitea_runner_docker_network_override is changed
|
|
|
|
- name: Configure rootless Docker daemon
|
|
ansible.builtin.copy:
|
|
dest: "{{ gitea_runner_home }}/.config/docker/daemon.json"
|
|
content: |
|
|
{
|
|
"live-restore": {{ gitea_runner_docker_live_restore | to_json }},
|
|
"shutdown-timeout": {{ gitea_runner_docker_shutdown_timeout }},
|
|
"max-concurrent-downloads": {{ gitea_runner_docker_max_concurrent_downloads }},
|
|
"max-concurrent-uploads": {{ gitea_runner_docker_max_concurrent_uploads }},
|
|
"default-ulimits": {
|
|
"nofile": {"Name": "nofile", "Hard": {{ gitea_runner_docker_default_nofile }}, "Soft": {{ gitea_runner_docker_default_nofile }}}
|
|
},
|
|
"features": {
|
|
"containerd-snapshotter": false
|
|
},
|
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
|
"ipv6": true,
|
|
"ip6tables": true,
|
|
"fixed-cidr-v6": "{{ gitea_runner_docker_ipv6_cidr }}",
|
|
"dns": ["10.0.2.3", "8.8.8.8"]
|
|
{% else %}
|
|
"ipv6": false,
|
|
"dns": ["8.8.8.8", "1.1.1.1"]
|
|
{% endif %}
|
|
}
|
|
mode: "0644"
|
|
owner: "{{ gitea_runner_service_user }}"
|
|
group: "{{ gitea_runner_service_user }}"
|
|
register: gitea_runner_docker_ipv6_config
|
|
when: gitea_runner_docker_rootless_setup
|
|
|
|
- name: Restart rootless Docker if config changed
|
|
ansible.builtin.command: systemctl --user restart docker
|
|
become: true
|
|
become_user: "{{ gitea_runner_service_user }}"
|
|
environment:
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
|
changed_when: true
|
|
when:
|
|
- gitea_runner_docker_rootless_setup
|
|
- gitea_runner_docker_ipv6_config is changed or gitea_runner_docker_network_override is changed
|
|
|
|
- name: Wait for rootless Docker daemon to be ready
|
|
ansible.builtin.command: docker version
|
|
become: true
|
|
become_user: "{{ gitea_runner_service_user }}"
|
|
environment:
|
|
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
|
register: gitea_runner_docker_ready
|
|
until: gitea_runner_docker_ready.rc == 0
|
|
retries: 10
|
|
delay: 2
|
|
changed_when: false
|
|
when: gitea_runner_docker_rootless_setup
|