From c6aa75ba39022ef669ff79c2f4a9f46ca196990b Mon Sep 17 00:00:00 2001 From: Adam Belkacemi Date: Thu, 17 Sep 2026 13:30:36 +0200 Subject: [PATCH] =?UTF-8?q?CI/CD=20:=20Forgejo=20Actions=20(pas=20GitLab?= =?UTF-8?q?=20-=20le=20d=C3=A9p=C3=B4t=20vit=20sur=20Forgejo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .forgejo/workflows/ci.yml : build + test cargo (workspace), build npm (app/) sur toute PR/push ; déploiement (main uniquement) : build release, scp + redémarrage systemd sur tripoli-edge et tripoli-api. - Runner Forgejo Actions déployé en systemd (aucun n'existait avant) : socket Docker de l'hôte partagé avec les conteneurs de job plutôt qu'un docker:dind imbriqué et privilégié - plus léger sur une machine déjà partagée. - Déploiement via un compte système "deploy" sans privilège : peut seulement déposer un binaire dans /opt/deploy-staging et lancer un script sudo dédié qui n'accepte que "tripoli-edge" ou "tripoli-api" comme argument. Aucune clé root dans le pipeline. Clé SSH de déploiement générée sur le serveur, jamais passée par cette conversation - stockée directement comme secret Forgejo Actions par l'utilisateur. Co-Authored-By: Claude Sonnet 5 --- .forgejo/workflows/ci.yml | 73 +++++++++++++++++++++++++++++++++++++++ README.md | 1 + ci/README.md | 45 ++++++++++++++++++++++++ ci/forgejo-runner.service | 24 +++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 .forgejo/workflows/ci.yml create mode 100644 ci/README.md create mode 100644 ci/forgejo-runner.service diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..c5048fb --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,73 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # rustup plutôt que le paquet apt de l'image : l'image du job + # (node:20-bookworm, Debian stable) embarque un rustc trop ancien + # pour ce code (async fn dans les traits, édition 2021 récente). + - name: Installer Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Dépendances système (sqlx/rustls) + run: apt-get update && apt-get install -y pkg-config libssl-dev build-essential + + - name: cargo build (workspace) + run: cargo build --workspace + + - name: cargo test (workspace) + run: cargo test --workspace + + - name: npm build (app/) + working-directory: app + run: | + npm ci + npm run build + + deploy: + needs: build-and-test + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Installer Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Dépendances système + run: apt-get update && apt-get install -y pkg-config libssl-dev build-essential openssh-client rsync + + - name: Build release (workspace) + run: cargo build --release --workspace + + # Identité de déploiement dédiée, sans privilège : `deploy` ne peut + # que déposer un fichier dans /opt/deploy-staging et lancer le + # script sudo tripoli-deploy.sh, qui installe le binaire et + # redémarre EXACTEMENT le service nommé - rien de plus. Pas de clé + # root dans ce pipeline. + - name: Déployer tripoli-edge et tripoli-api + env: + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + DEPLOY_HOST: 65.109.142.188 + run: | + mkdir -p ~/.ssh + echo "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null + + for service in tripoli-edge tripoli-api; do + scp -i ~/.ssh/deploy_key "target/release/$service" "deploy@$DEPLOY_HOST:/opt/deploy-staging/$service" + ssh -i ~/.ssh/deploy_key "deploy@$DEPLOY_HOST" sudo /usr/local/sbin/tripoli-deploy.sh "$service" + done diff --git a/README.md b/README.md index 69e90e9..e2a6205 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ tenté, non simulé. | `tripoli-netctl` (Rust, Phase 4) | Fait — `tripoli-netctl/`, labo pédagogique (netns + veth via `rtnetlink`), testé sur le serveur (création, ping traversant la veth, destruction propre), **volontairement pas branché sur le trafic de prod** | | Observabilité (Phase 5) | Fait — pas de stack dédiée (réutilise le Prometheus/Grafana partagé existant), scrape tripoli-edge + alertes (down, disque) + dashboard versionné (`monitoring/`), vérifié via l'API Prometheus/Grafana | | Bird/BGP (Phase 6, bonus) | Fait — `bird-lab/`, BGP+BFD entre 2 namespaces via la racine en transit (AS 65000/65010/65011), vérifié (sessions Established, BFD Up, propagation de route, détection de panne sur coupure de lien), puis proprement démonté | +| CI/CD | Fait — `.forgejo/workflows/ci.yml` (pas `.gitlab-ci.yml` : le dépôt vit sur Forgejo, pas GitLab). Runner Forgejo Actions déployé en systemd, déploiement via un compte `deploy` sans privilège (`ci/README.md`) | ## Dépôt diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 0000000..91478e6 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,45 @@ +# CI/CD + +Le brief demandait un `.gitlab-ci.yml`, mais ce dépôt vit sur **Forgejo**, +pas GitLab (voir le README racine) : l'équivalent réel est un workflow +**Forgejo Actions** (`.forgejo/workflows/ci.yml`), syntaxe compatible +GitHub Actions. + +## Runner + +Aucun runner Forgejo Actions n'existait sur ce serveur avant cette +phase. Déployé en systemd (`forgejo-runner.service`), conteneur +`code.forgejo.org/forgejo/runner:13.1`, socket Docker de l'hôte partagé +avec les conteneurs de job (`docker_host: "automount"` dans sa config) +plutôt qu'un `docker:dind` imbriqué et privilégié - plus léger sur une +machine à 3.7 Go déjà partagée, au prix d'une isolation plus faible +(un job de CI a accès au docker de l'hôte). Acceptable ici : instance +Forgejo mono-utilisateur, pas un service ouvert à des tiers. + +## Déploiement : un compte dédié, pas root + +Le pipeline ne se connecte jamais en root. Un utilisateur système +`deploy` existe uniquement pour : +1. recevoir le binaire compilé par `scp` dans `/opt/deploy-staging/` ; +2. lancer `sudo /usr/local/sbin/tripoli-deploy.sh ` - un + script sudo dédié (voir `/etc/sudoers.d/deploy` sur le serveur) qui + n'accepte que `tripoli-edge` ou `tripoli-api` comme argument, installe + le binaire à l'emplacement attendu et redémarre exactement ce + service. Rien d'autre n'est accessible via ce compte. + +La clé privée correspondante est stockée comme secret Forgejo Actions +(`DEPLOY_SSH_KEY`), jamais dans ce dépôt. + +## Workflow (`.forgejo/workflows/ci.yml`) + +- `build-and-test` (toute PR et push) : installe Rust via rustup + (l'image du job, `node:20-bookworm`, a un rustc système trop ancien + pour ce code), `cargo build --workspace`, `cargo test --workspace`, + puis `npm ci && npm run build` pour le frontend. +- `deploy` (uniquement sur `main`, après succès du job précédent) : + build release, puis dépôt + redémarrage de `tripoli-edge` et + `tripoli-api` via le compte `deploy`. + +`tripoli-netctl` et le labo `bird-lab` ne sont pas déployés par ce +pipeline : ce sont des outils de labo qu'on lance à la main, pas des +services qui tournent en continu. diff --git a/ci/forgejo-runner.service b/ci/forgejo-runner.service new file mode 100644 index 0000000..15d2a15 --- /dev/null +++ b/ci/forgejo-runner.service @@ -0,0 +1,24 @@ +[Unit] +Description=forgejo-runner - runner Forgejo Actions pour tripoli.cloud +After=docker.service +Requires=docker.service + +[Service] +Type=simple +Restart=on-failure +RestartSec=5 +# --group-add : le gid du groupe "docker" sur l'hôte (docker.sock lui +# appartient) - sans ça, le processus (uid 1001, sans privilège) n'a +# pas le droit de s'en servir, même monté dans le conteneur. +ExecStart=/usr/bin/docker run --rm \ + --name forgejo-runner \ + --user 1001:1001 \ + --group-add 983 \ + -v /opt/forgejo-runner/data:/data \ + -v /var/run/docker.sock:/var/run/docker.sock \ + code.forgejo.org/forgejo/runner:13.1 \ + forgejo-runner daemon --config /data/config.yml +ExecStop=/usr/bin/docker stop forgejo-runner + +[Install] +WantedBy=multi-user.target