Configure the remote CI runner

Guide to using the script that provisions a remote machine to run gitrust CI/CD pipelines via SSH + rsync + Dagger.


1. Role of the script

deployment/setup-remote-ci.sh (available in the gitrust source repository) prepares a remote build server used by the gitrust CI worker. It automates 6 steps:

  1. Checks SSH connectivity to the runner
  2. Install Docker (curl https://get.docker.com | sh) if it is absent
  3. Installs Dagger CLI (curl https://dl.dagger.io/dagger/install.sh | sh) if absent
  4. Creates the remote working directory (CI_REMOTE_PATH)
  5. Synchronize the deployment/ci-engine/ module (CI Easy mode) via rsync -az --delete
  6. Smoke test: displays docker --version and dagger version

The script is idempotent: replays without damage, skips what already exists.


2. When to use it

Scénario Besoin de setup-remote-ci.sh ?
Dev local, CI sur la même machine que gitrust (CI_REMOTE_HOST=localhost) Non — Docker et Dagger déjà installés localement, le ci-engine/ est lu depuis deployment/ci-engine/ directement
Prod on-premise, un seul serveur (gitrust + CI colocalisés) Non — idem, un simple curl get.docker.com \| sh suffit sur le serveur gitrust
Prod avec runner CI dédié (machine séparée) OUI — c'est le cas cible de ce script
Prod avec plusieurs instances gitrust partageant un runner OUI — le runner est provisionné une fois, chaque instance pointe dessus
CI dans Kubernetes / Nomad / runner managé cloud Non — ce script est pensé pour une VM Debian/Ubuntu classique

3. Target architecture

+---------------------+        SSH + rsync        +-----------------------+
|  Gitrust (web+SSH)  |---------------------------|  CI Runner (distant)  |
|  <your-server-ip>   |  CI_REMOTE_HOST/USER/KEY  |  <ci-runner-ip>       |
|                     |                           |                       |
|  Worker CI Tokio    |   push .gitrust-ci.yml    |  /opt/gitrust-ci/     |
|  (dans gitrust.bin) |   + code source checkout  |   ├── ci-engine/      |
|                     |                           |   └── workspaces/     |
|                     |   dagger call ...         |                       |
|                     |                           |  Docker + Dagger CLI  |
+---------------------+                           +-----------------------+

The gitrust CI worker (Tokio task in the main binary) does not run Docker locally — it delegates to the runner via SSH. This makes it possible to isolate greedy CPU/RAM workloads (Rust, Node, Docker builds) from the gitrust web process.


4. Prerequisites

On the execution machine (where we launch the script)

Outil Rôle
bash ≥ 4 Interpréteur
ssh, rsync Transport et synchro
ssh-agent chargé ou CI_REMOTE_SSH_KEY défini Auth SSH sans mot de passe interactif (BatchMode=yes)
Fichier .env avec les variables CI_REMOTE_* Config (voir section 5)

On the distant runner

Pré-requis Pourquoi
OS Linux récent (Debian 12+, Ubuntu 22.04+) Docker install script compatible
User avec sudo passwordless ou droits docker get.docker.com fait sudo en interne
Clé SSH publique du user local dans ~/.ssh/authorized_keys Auth SSH non-interactive
Réseau sortant autorisé vers get.docker.com et dl.dagger.io Installation des binaires
Au minimum ~5 Go libres dans CI_REMOTE_PATH Images Docker + workspaces

5. Expected environment variables (the .env)

The script sources a .env file whose path is passed as an argument, or by default ../.env (relative to the script, therefore <project_root>/.env).

Table of variables

Variable Obligatoire Défaut Description
CI_REMOTE_HOST OUI Hostname ou IP du runner (ex: ci-runner.internal)
CI_REMOTE_USER non $(whoami) (user courant) Compte SSH sur le runner
CI_REMOTE_SSH_PORT non 22 Port SSH du runner
CI_REMOTE_PATH non /opt/gitrust-ci Répertoire de travail distant (créé par le script)
CI_REMOTE_SSH_KEY non — (ssh-agent) Chemin d'une clé privée SSH spécifique

These variables are the same as those read by gitrust at runtime. Centralizing in .env avoids discrepancies between provisioning and runtime.

Minimal example of .env (dedicated runner)

# --- CI runner distant ---
CI_REMOTE_HOST=<ci-runner-ip>
CI_REMOTE_USER=ci-runner
CI_REMOTE_SSH_PORT=22
CI_REMOTE_PATH=/opt/gitrust-ci
CI_REMOTE_SSH_KEY=/home/gitrust/.ssh/ci_runner_ed25519

Complete .env example coexisting with gitrust config

DATABASE_URL=postgres://...
JWT_SECRET=...
# ... (voir .env.example pour le reste)

# --- CI/CD (lu par gitrust ET par setup-remote-ci.sh) ---
CI_ENABLED=true
CI_MAX_CONCURRENT=4
CI_REMOTE_HOST=ci-runner.internal
CI_REMOTE_USER=ci-runner
CI_REMOTE_PATH=/opt/gitrust-ci
CI_REMOTE_SSH_KEY=/opt/gitrust/data/ci_runner_key

“Local runner” case (no need for the script)

CI_REMOTE_HOST=localhost
# Les autres CI_REMOTE_* sont ignorés

In this case, install Docker and Dagger directly on the gitrust machine, copy deployment/ci-engine/ to CI_ENGINE_PATH (default /opt/gitrust/ci-engine) and move on. Do not run setup-remote-ci.sh with CI_REMOTE_HOST=localhost.


6. Use

From the project root (default)

cd /chemin/vers/gitrust

# .env a la racine (défaut) :
./deployment/setup-remote-ci.sh

With an explicit .env

./deployment/setup-remote-ci.sh /chemin/vers/mon.env
# Utile si vous avez .env.production, .env.staging, etc.
./deployment/setup-remote-ci.sh .env.production

Expected output

==> Configuration :
    Serveur  : ci-runner@<ci-runner-ip>
    Port SSH : 22
    Chemin   : /opt/gitrust-ci

==> [1/6] Vérification de la connectivité SSH...
SSH OK
    OK
==> [2/6] Vérification de Docker...
    Docker déjà installé
==> [3/6] Vérification de Dagger CLI...
    Installation de Dagger CLI...
    Dagger installé
==> [4/6] Création du répertoire distant...
    /opt/gitrust-ci créé
==> [5/6] Synchronisation du ci-engine...
sending incremental file list
ci-engine/
ci-engine/profiles/
...
    ci-engine synchronisé vers /opt/gitrust-ci/ci-engine/
==> [6/6] Smoke test...
    Versions distantes :
Docker version 28.0.1, build abcd123
dagger v0.19.2 (linux/amd64)

==> Setup terminé. Le serveur ci-runner@<ci-runner-ip> est prêt pour l'exécution CI.
    Pensez à configurer CI_EXECUTION_MODE=remote dans votre .env

7. What is found on the runner

After successful execution:

/opt/gitrust-ci/                    <- CI_REMOTE_PATH
└── ci-engine/                      <- synchronisé depuis deployment/ci-engine/
    ├── README.md
    └── profiles/                   <- templates par stack (Python, Node, Rust, ...)

Plus, installés globalement : - /usr/bin/docker (or equivalent) + socket /var/run/docker.sock - /usr/local/bin/dagger

Pipeline workspaces (temporary checkouts) are created by the gitrust CI worker at runtime under CI_WORKSPACE_PATH (default /tmp/gitrust-ci) — not by this script.


8. Post-setup checks

From the gitrust machine

# Source le .env
set -a; source .env.production; set +a

# 1. SSH direct (doit passer sans prompt)
ssh -p ${CI_REMOTE_SSH_PORT:-22} \
    ${CI_REMOTE_SSH_KEY:+-i $CI_REMOTE_SSH_KEY} \
    $CI_REMOTE_USER@$CI_REMOTE_HOST 'docker info && dagger version'

# 2. Rsync round-trip (lecture/écriture sur CI_REMOTE_PATH)
echo "test" | ssh $CI_REMOTE_USER@$CI_REMOTE_HOST \
    "cat > $CI_REMOTE_PATH/.gitrust-test && cat $CI_REMOTE_PATH/.gitrust-test && rm $CI_REMOTE_PATH/.gitrust-test"
# Attendu : "test"

Test pipeline via gitrust

Pushing a repository with a minimal .gitrust-ci.yml:

# .gitrust-ci.yml
version: 1
pipeline:
  - name: smoke
    image: alpine:3
    run: echo "CI runner OK"

Then in the gitrust UI: Pipelines tab → the pipeline must go to success status.


9. Update after modification of ci-engine/

The synchronized ci-engine/ is not “live-linked”: replay the script after modification on the source side.

# Modifier deployment/ci-engine/profiles/*.py ou similaire
./deployment/setup-remote-ci.sh
# Les étapes 2-3 sont skip (déjà installés), seule l'étape 5 refait le rsync

rsync -az --delete deletes runner-side files that no longer exist locally — ensures consistency.


10. Troubleshooting

Symptôme Cause probable Fix
ERREUR: fichier .env introuvable .env absent ou chemin incorrect cp .env.example .env && $EDITOR .env ou passer le chemin : ./setup-remote-ci.sh /path/to/.env
ERREUR: CI_REMOTE_HOST non défini Variable commentée ou absente Décommenter CI_REMOTE_HOST=... dans le .env
ERREUR: impossible de se connecter SSH bloqué, mauvais user, clé non autorisée Tester manuellement : ssh -v -p X user@host ; vérifier ~/.ssh/authorized_keys sur le runner
Permission denied (publickey) BatchMode=yes interdit les prompts, clé non chargée ssh-add ~/.ssh/ci_runner_ed25519 ou définir CI_REMOTE_SSH_KEY=/path/to/key
sudo: a password is required pendant l'install Docker User sans NOPASSWD sudo Ajouter le user au sudoers : ci-runner ALL=(ALL) NOPASSWD:ALL (runner uniquement)
curl: (7) Failed to connect to get.docker.com Réseau sortant du runner bloqué Whitelist get.docker.com et dl.dagger.io, ou pré-installer Docker + Dagger manuellement
ATTENTION: ... ci-engine introuvable Lancé hors du repo gitrust cd dans la racine du projet avant de lancer
Pipeline reste queued indéfiniment Worker CI ne trouve pas le runner Vérifier CI_EXECUTION_MODE=remote dans .env gitrust + logs : journalctl -u gitrust \| grep -i 'ci\|dagger'
dagger: command not found au smoke test $PATH du user SSH ne contient pas /usr/local/bin echo 'export PATH=$PATH:/usr/local/bin' >> ~/.bashrc sur le runner, ou CI_DAGGER_BIN=/usr/local/bin/dagger côté gitrust

11. Security

  • The CI runner executes arbitrary code coming from hosted repositories. Never collocate it with sensitive secrets (prod PG, prod keys).
  • Isolate network: block outgoing access from the runner to the private LAN (only Internet access for docker pull is necessary).
  • Limit sudo NOPASSWD to the strict minimum on the runner (ideally: just for the docker and apt commands).
  • Regular rotation of the SSH key CI_REMOTE_SSH_KEY. Revoke it in ~/.ssh/authorized_keys on the runner side in case of suspicion.
  • The runner must not be able to connect via SSH to the gitrust machine (unidirectional).