Skip to content

Continuous Integration – Gitea Actions

Machine translation of the French original.

The park executes its workflows using act_runner, on vm-compute-01, in mode dind: the jobs run within the Docker runner's daemon, not the host's.

The flow of a push

flowchart LR
    dev["Poste<br/>git push"]
    gitea["Gitea<br/>vm-docker:3303"]
    runner["act_runner<br/>vm-compute-01"]
    job["Job dans<br/>node:20-bookworm"]
    hote["vm-docker<br/>deploy-hook.sh"]

    dev --> gitea
    gitea -.->|"le runner interroge,<br/>Gitea ne pousse pas"| runner
    runner --> job
    job -->|"ssh, clé verrouillée<br/>par command="| hote
    hote --> ok["git pull + build<br/>+ vérification"]

The runner pulls the job by querying: Gitea never contacts it. This results in a direct IP URL in its configuration – via Caddy, this query It would resemble a highlighting technique and would likely be flagged by CrowdSec.

The most expensive trap: the scope of a runner

A runner act_runner has a unique scope, fixed to a specific record – instance, organization, user, or a single repository. It does not change after the fact.

flowchart TB
    subgraph hote["vm-compute-01"]
        r1["rtx-runner<br/>portée : dépôt homelab-vault"]
        r2["docs-runner<br/>portée : dépôt homelab-docs"]
    end

    v["Dépôt homelab-vault"]
    d["Dépôt homelab-docs"]
    t["Un troisième dépôt"]

    r1 --> v
    r2 --> d
    t -.->|"aucun runner éligible"| queued["runs « queued »<br/>indéfiniment"]

    classDef bad stroke-width:3px,stroke-dasharray: 5 5
    class queued bad

Runs queued that last forever do not resemble an error

When no runner is eligible, the executions appear normally within the interface and remain queued. There are no messages, no alerts, nothing that indicates that someone will not come to take them. This reads as "the CI is not yet connected."

The build process, which breaks down into a single request:

curl -s -H "Authorization: token $TOKEN" \
  https://gitea.savethemall.eu/api/v1/repos/<owner>/<repo>/actions/runners \
  | jq '.runners[].name'
# tableau vide → ces jobs ne partiront jamais

Adding a repository to the CI means either adding a runner or migrating an existing runner to a wider scope – the latter option stops the CI from functioning during the operation, effectively freezing it.

The registration token requires a POST request, not a GET:

POST /api/v1/repos/<owner>/<repo>/actions/runners/registration-token

What the jobs contain – and do not contain

The jobs runs-on: ubuntu-latest run in node:20-bookworm.

Present Absent
git 2.39 pip
Python 3.11 venv
Access to github.com and pypi.org SSH client

Debian delivers pip, venv, and openssh-client separately. A workflow that assumes these will succeed fails late, after the checkout, with a message that does not indicate that a package is missing.

actions/setup-python does not work on this runner

::error::The version '3.12' with architecture 'x64' was not found
for this operating system.

The action downloads binaries published for the images ubuntu-XX from GitHub. None exist for Debian bookworm. This is not a network issue: the runner is able to reach github.com and pypi.org without difficulty.

Instead, install from Debian:

- name: Installer Python et les dépendances
  run: |
    apt-get update -qq
    apt-get install -y --no-install-recommends python3-venv python3-pip
    python3 -m venv /tmp/venv
    /tmp/venv/bin/pip install --no-cache-dir -r requirements.txt

Deploying without a shell

A deployment key that opens a full shell on the production host is worth the password for that host. The form used here reduces it to a single action:

command="/opt/docker/<stack>/deploy-hook.sh",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty,restrict ssh-ed25519 AAAA…

Whatever the client sends, the server executes this script and nothing else. The proof is in the execution, rather than assuming:

ssh -i <clé> -p <port-ssh> <user>@vm-docker "cat /etc/shadow"
# doit afficher la sortie du déploiement, jamais le contenu du fichier

A corollary of the design: the script must perform its own verification, since the client cannot send a second command to check the result.

Dernière vérification de cette page : 2026-09-08