Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

250 changes: 202 additions & 48 deletions DOC.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions JOURNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ Alternative Rust à GitHub/Forgejo/GitLab/Coder. Objectif 22h00.
- **Réaliste pour 23h (deadline ajustée, dépassée pour ce chantier bonus)**: un MVP fonctionnel et démontrable (clone/push, CI qui tourne dans Docker, workspace dev qui boot, GraphQL+front qui marchent ensemble) — pas un remplaçant prod-ready de Forgejo+Coder. Ça, c'est plusieurs semaines/mois même en gardant ce rythme (intégration réelle, sécurité, edge cases, UI poli, tests).
- **01:17** — Bootstrap admin token implémenté (`ADMIN_BOOTSTRAP_TOKEN`, provisioning idempotent admin+PAT au démarrage, testé live) — plus aucun besoin de `register`/`login` pour un déploiement API-only pur. `.claude/` retiré du suivi git (untrack + gitignore). Profil `[profile.release]` optimisé (lto+codegen-units=1+strip, sans `panic=abort` — évite de casser l'isolation panic par requête d'axum): `server` 50.8MB→37.4MB, `runner` 5.2MB. `cargo test --workspace` 33/33 verts après rebuild. Écrit `DOC.md`: doc complète de la plateforme (architecture, déploiement, auth/bootstrap, référence complète des 54 mutations/15 queries/24 champs imbriqués GraphQL, transports git HTTP/SSH, CI/Actions compat GH, dev-workspaces, wiki, registre de paquets, webhooks, admin, tests, limitations connues). Commité+pushé (285b79f).
- **01:29** — Retrait complet du 2FA (TOTP): plus de justification une fois l'OAuth2 retiré et l'usage purement API/PAT/bootstrap-token — supprimé `crates/auth/src/totp.rs`, dep `totp-rs`, colonnes utilisées côté code (`totp_secret`/`totp_enabled` restent en DB, migration additive-only, juste plus lues/écrites), mutations `enableTwoFactor`/`confirmTwoFactor`/`disableTwoFactor`, arg `totpCode` sur `login`, page front `settings/security.vue` + lien nav. Ajouté en parallèle (agent dédié): templates de workspace nommés (`code-server`/`rust-dev`/`node-dev` → image+ports résolus, `crates/dev-env/src/templates.rs`) et proxy multi-port (`/workspaces/:id/proxy_port/:portName/*path` en plus de l'ancien single-port `/proxy/*path`, ports reconstruits depuis les labels Docker donc pas de migration DB). `cargo check/test --workspace` clean (32/32 tests, frontend build clean), pushé (5df083a).
- **(reprise de session, 15 juillet)** — Demande utilisateur: combler 5 lacunes précises listées dans `DOC.md` §14, et trouver une alternative safe au DinD. Fait dans l'ordre, chaque morceau vérifié par `cargo check/test --workspace` avant de passer au suivant (rustc mis à jour 1.94→1.97 en cours de route, hiqlite 0.14 l'exigeait) :
1. **Recherche full-text** (`search`): remplacé le `LIKE` par de la vraie recherche **SQLite FTS5** (migration `9_search_fts.sql`, tables `repositories_fts`/`issues_fts`/`users_fts` tenues à jour par triggers + backfill des lignes existantes). Bonne surprise: FTS5 est déjà compilé dans le SQLite "bundled" que hiqlite utilise (`-DSQLITE_ENABLE_FTS5` dans son build.rs) — pas besoin de bidouiller les features cargo, juste écrire le SQL. Ajouté en plus une vraie **recherche de code** (`code_search_fts`), indexée automatiquement après chaque push qui bouge la branche par défaut (`index_repo_code_on_push`, ré-indexe tout l'arbre plutôt que de diff — gère nativement force-push/rewrite), avec snippet FTS5 en résultat. Nouvelle fonction `RepoManager::list_text_blobs_at_ref` (skip binaire/fichiers >256KB, cap 2000 fichiers/repo).
2. **Rename-on-disk**: `RepoManager::rename_repo`/`rename_wiki` (nouveau) + mutation `renameRepository(repoId, newName)` — déplace le(s) dossier(s) bare sur disque et met à jour la colonne `name`, avec rollback best-effort si une étape échoue en cours de route (pas de vraie transaction FS+DB possible, donc ordre choisi pour minimiser la fenêtre d'incohérence: disque d'abord, DB ensuite, rollback disque si la DB échoue).
3. **Backfill du hook pre-receive**: `write_pre_receive_hook` était `fn` privée — exposée via `RepoManager::ensure_pre_receive_hook` (pub), + mutation admin `adminBackfillPreReceiveHooks` qui la rejoue sur tous les repos existants (écriture inconditionnelle, sûre à rejouer même sur un repo qui a déjà le hook).
4. **Marketplace GitHub Actions** (le plus gros morceau): `crates/actions/src/marketplace.rs` — parse `uses: docker://image` et `uses: owner/repo[/path]@ref`, clone (git2, pas de shallow propre possible avec un tag/branche arbitraire donc clone complet — les repos d'actions sont petits) puis lit `action.yml`. Supporte `runs.using: docker` (y compris build depuis un `Dockerfile` via `bollard::build_image`), `runs.using: composite` (un seul niveau — pas de composite imbriqué, pour éviter la récursion non bornée), et **`runs.using: node12/16/18/20`** (actions JS réellement exécutées, dans un conteneur `node:*-slim` dédié — l'image `runs-on` du job n'a aucune raison d'avoir Node). Modèle de conteneur pour docker/node: le `/workspace` du job est copié (tar `docker cp`) dans un conteneur satellite jetable, qui tourne, puis est recopié dans l'autre sens pour que les steps suivants voient les changements. Tout ce qui ne rentre pas dans ce moule (ref introuvable, `action.yml` absent, `runs.using` non reconnu, composite imbriqué) tombe dans le même chemin "not supported, skipping" qu'avant plutôt que de faire planter le job.
5. **Dev-workspace hosting par le runner standalone**: le `kind = 'dev_workspace_action'` était réservé mais mort — maintenant câblé bout en bout: `createDevWorkspace(..., onRunner: true)` pousse un `runner_jobs` (nouvelle colonne `result` pour faire remonter `container_id`/output, migration `10_dev_workspace_runner.sql`), le runner le récupère et l'exécute contre **son propre** Docker (nouveau `crates/runner/src/dev_workspace_poll.rs`, plus plausible stub), rapporte le résultat, la mutation poll la DB (pas de pub/sub, juste polling 300ms/20s timeout) et renvoie l'objet à jour. `delete`/`exec` suivent le même chemin pour un workspace déjà hébergé par un runner. Honnêteté: `start`/`stop` et surtout le **reverse-proxy HTTP live** vers un workspace hébergé sur un runner restent non supportés (le conteneur est sur un Docker daemon que `server` n'a aucun moyen réseau d'atteindre — ça demanderait un vrai tunnel inverse runner→server, hors scope ici) — documenté comme lacune connue plutôt que de faire semblant que ça marche.
6. **Alternative safe au DinD**: en creusant, `server`/`runner` ne faisaient déjà **pas** de vrai Docker-in-Docker (pas de `dockerd` imbriqué) — juste un montage du socket Docker de l'**hôte** ("Docker outside of Docker"), ce qui est en pratique un risque équivalent (accès root-like à l'hôte via `-v /:/host`). `DOCKER_SOCKET_PATH` existait déjà en config mais n'était jamais branché nulle part (juste loggé) — maintenant réellement câblé (`Executor::new_with_socket`, `WorkspaceManager::connect_socket`, via `bollard::Docker::connect_with_socket`). Recherche web: **Podman rootless** (`systemctl --user enable --now podman.socket`, API compatible Docker, isolation par user-namespace, zéro changement de code requis côté Genome) recommandé en premier, **Sysbox** (`nestybox/sysbox`) documenté pour le cas où un workflow a réellement besoin de nested Docker (ex: un step qui fait `docker build`). Tout documenté dans `DOC.md` §2a, rien d'actif par défaut (il faut un socket disponible pour un `docker compose up` zéro-config) mais le knob marche vraiment maintenant.
- Tests ajoutés: 6 nouveaux tests unitaires `marketplace` (parsing `ActionRef`, résolution d'inputs, substitution), 2 nouveaux tests `git-core` (rename repo+wiki avec rollback/collision, idempotence du backfill hook). `cargo test --workspace`: seuls les 6 tests d'intégration qui construisent un `actions::Executor`/`dev_env::WorkspaceManager` échouent, et uniquement à cause de l'absence de `/var/run/docker.sock` **dans ce bac à sable de session** (pas de démon Docker démarrable, `systemd` absent) — confirmé non lié à mes changements (ces tests appellent le même `Executor::new()`/`connect_local()` qu'avant, code non modifié sur ce chemin). Pushé (ec8ec2e).
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ Actions-compatible CI running real Docker jobs, Coder-like containerized dev
workspaces, GraphQL API, package registry. See `JOURNAL.md` for the full
build log and honest list of what is and isn't production-hardened.

**Known gaps** (see `JOURNAL.md` for detail): no artifact-of-repo-mirroring for
existing repos' pre-receive hooks (only newly-created repos get the force-push
protection hook), no code-search/full-text search (only ILIKE-ish substring
match), no rename-repo-on-disk.
**Known gaps** (see `JOURNAL.md` for detail, and `DOC.md` §14 for the current
full list): GitHub Actions marketplace support covers Docker/composite/JS
actions but not every toolkit behavior (step outputs, `GITHUB_ENV`, caching);
standalone-runner-hosted dev workspaces support create/delete/exec but not
start/stop or live port-proxying; code search only indexes each repo's
default branch; CI/dev-workspace execution needs a Docker socket, and the
shipped `docker-compose.yml` bind-mounts the host's own one (a real privilege
surface) rather than defaulting to a safer rootless engine.

## Architecture

Expand All @@ -36,7 +40,7 @@ Rust workspace, one crate per concern:
| `webhooks` | HMAC-signed webhook dispatch |
| `graphql-api` | The GraphQL schema (async-graphql) wiring everything together |
| `server` | axum binary: HTTP router (GraphQL, git smart-HTTP, packages, artifacts), spawns the SSH server, embeds the Hiqlite database node, and runs background loops (mirror sync, workspace auto-stop) |
| `runner` | **Optional**, standalone poll-based binary. Polls the main `server`'s `/runner/claim` HTTP route for queued CI jobs (`runner_jobs` table) and executes them locally via the same `actions::Executor`/`actions::Workflow` logic the server uses in-process, reporting results back to `/runner/jobs/:id/complete`. The `server` binary keeps running every CI job in-process exactly as before regardless of whether any `runner` is connected — the two paths are additive, not a replacement (see the `// DUAL-PATH:` comments in `crates/server/src/main.rs` and `crates/graphql-api/src/mutation.rs`). Also carries a `dev_env::WorkspaceManager` dependency as a forward-compat stub for eventual dev-workspace-hosting polling (not implemented yet). |
| `runner` | **Optional**, standalone poll-based binary. Polls the main `server`'s `/runner/claim` HTTP route for queued CI jobs (`runner_jobs` table) and executes them locally via the same `actions::Executor`/`actions::Workflow` logic the server uses in-process, reporting results back to `/runner/jobs/:id/complete`. The `server` binary keeps running every CI job in-process exactly as before regardless of whether any `runner` is connected — the two paths are additive, not a replacement (see the `// DUAL-PATH:` comments in `crates/server/src/main.rs` and `crates/graphql-api/src/mutation.rs`). Also claims and executes `dev_workspace_action` jobs against its own local Docker daemon (`dev_env::WorkspaceManager`), letting a dev workspace be hosted on the runner instead of `server` — create/delete/exec only, see `DOC.md` §7 for the gaps. |

`frontend/` is an independent Nuxt 3 + Vue 3 app for browsing/testing against
the GraphQL API — not required for production use.
Expand Down
2 changes: 2 additions & 0 deletions crates/actions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ tar.workspace = true
aes-gcm.workspace = true
base64.workspace = true
rand.workspace = true
git2.workspace = true
tempfile = "3"
Loading
Loading