gitoriaLog in with ident

gitoria

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit8d9450fd8d9450fdantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre8d9450fd/docs/git-backend.md

10.3 KB

  1. # How Gitoria uses git — research
  2. Research for ticket [gitoria.worldapi.org#5](https://tickets.worldapi.org/projects/gitoria.worldapi.org/5).
  3. Blocks #7 (push and pull) and feeds #9 (browse code), #11 (pulls), #12 (releases).
  4. Checked 2026-09-24 on Loreana (reworked 2026-09-25 after hybriel#42, stdin write and raw bytes in `hl:proc`, was fixed) with git 2.55.0, the vendored `bin/hybriel` and the plugin sources in
  5. `hybriel/plugins/`.
  6. ## Verdict
  7. **Use the `git` binary. Do not build an `hl:git` plugin on a git library.**
  8. - Every hub feature (clone/push over HTTPS and SSH, log, diff, blame, merge, archive, gc, grep) already exists in the
  9. binary, is stable and is what every developer's client speaks. A library re-implements a subset.
  10. - **libgit2** (installed on Loreana, 1.9) is a client/object library. It has **no server side**: no `upload-pack` /
  11. `receive-pack`, so it cannot answer `git clone` / `git push`. That was the same gap as with nodegitserver.
  12. Writing our own pack negotiation in Hybriel is a large, security-relevant project with no gain.
  13. - Split by job:
  14. 1. **Reading for the web UI** (#8 #9 #10 #11 #12): Hybriel spawns `git -C <repo>.git …` with `hl:proc` (argv list,
  15. no shell). Works today, see "What hl:proc can and cannot do".
  16. 2. **Transport (clone / fetch / push)**: **HTTPS is served by Hybriel itself**: the smart-HTTP endpoints spawn
  17. `git upload-pack` / `git receive-pack --stateless-rpc` with `hl:proc` (`stdin = 'pipe'`, `binary = true`) and
  18. pipe the request body in and the packfile out through `hl:http1` (byte-safe). **SSH** needs `sshd` (a Hybriel
  19. process cannot be an ssh server): a small sshd container whose forced command is `git-upload-pack` /
  20. `git-receive-pack`. Hybriel decides *who may*, git moves the bytes.
  21. ## What was tested
  22. | Claim | Result |
  23. |---|---|
  24. | (2026-09-24, kept as proof the protocol works) `git http-backend` as CGI behind a Basic-auth check serves clone **and push** to a bare repo (`http.receivepack=true`) | works: clone, push, second clone, wrong password → `Authentifizierung fehlgeschlagen` (test wrapper in Python, 30 lines) |
  25. | `hl:http1` request and response bodies are byte-safe (packfiles) | works: 3000 random bytes POSTed and echoed, `cmp` identical |
  26. | `hl:proc` output is text lines only (2026-09-24) | **was lossy; fixed by hybriel#42.** Retested 2026-09-25: `run([...'cat-file','blob'…], { binary = true })` returns `ff 00 0d 0a 65 6e 64` for a file `\xff \0 \r\n end` — byte exact, `.data` is a Bytes |
  27. | `hl:proc` can write to the child's stdin | **yes now** (`{ stdin = 'pipe' }`, `write()` String or Bytes, `end()`). Retested: `git upload-pack --stateless-rpc r.git` fed a `want <sha>` + `done` request answers `0008NAK` and the packfile as Bytes, exit 0 |
  28. | `hl:proc` can set cwd / env | **yes now** (`cwd`, `env` options, hybriel#37) — `git -C <dir>` still fine |
  29. | SSH path (sshd `AuthorizedKeysCommand`) | designed, **not run** (no sshd on Loreana for this); standard OpenSSH feature |
  30. Hybriel can now pipe a packfile through a child process, so "Hybriel itself answers `git upload-pack` / `receive-pack`"
  31. works; no CGI wrapper and no separate HTTP transport container are needed. Only SSH still needs a sshd container.
  32. Points found while retesting: a child's `stderr` also arrives as `data` events — keep only `stream == 'stdout'` for the
  33. pack; `'\n'` in a string is not a newline (hybriel#44) — write a real newline when building pkt-lines.
  34. ## Architecture
  35. ```
  36. developer ── https ──> nginx (*.gitoria.worldapi.org) ──> Hybriel (one app: web UI, API and git)
  37. /repo.git/info/refs?service=… auth check, then hl:proc
  38. git upload-pack|receive-pack --stateless-rpc --advertise-refs
  39. /repo.git/git-upload-pack request body ─> child stdin, child stdout ─> response
  40. /repo.git/git-receive-pack same; repos at repos/<slug>.git
  41. developer ── ssh ──> sshd (small container, own port)
  42. AuthorizedKeysCommand ─> Hybriel /__git/keys (key → user, may they?)
  43. forced command: git-upload-pack / git-receive-pack '<slug>.git' only
  44. git hooks in every bare repo: post-receive ─> Hybriel /__git/pushed (refs, old/new ids)
  45. Hybriel ── hl:proc ──> git -C repos/<slug>.git log|show|ls-tree|diff-tree|blame|merge-tree|… (reads)
  46. ```
  47. - One volume `repos/` holds `<slug>.git` bare repositories (slug unique in the whole system, as in the concept).
  48. Hybriel and the sshd container mount it; the mpackdb store stays in `storage/mpackdb/`.
  49. - **Authorisation lives only in Hybriel** (ident login → users, repo access, keys, tokens). The sshd container
  50. asks Hybriel over an internal address with a shared secret; it keeps no user list of its own.
  51. - Hybriel reads the Basic credentials and the `service` of the request itself and answers "read" vs "write" before it
  52. spawns git. Public repos: read without credentials, write always with.
  53. - `post-receive` is how Hybriel learns about pushes: it scans new commits for `|||PR …` and `|||RL …` (#11, #12), fills
  54. caches and can create the tickets project. Do this in the hook, not by polling.
  55. - Not yet tried: a full clone and push through a running Hybriel HTTP endpoint (the child-process part is tested, the
  56. endpoint is built in #7). Watch large pushes: stream the request body into the child, don't hold it whole in memory.
  57. - Pass `Git-Protocol` to the child as env `GIT_PROTOCOL` (the `env` option) so clients use protocol v2.
  58. ## Credentials — what git accepts today
  59. - **HTTPS with user + password/token is not deprecated by git.** Git still uses HTTP Basic through its credential
  60. helpers. (GitHub removed *account passwords* for its own service; that is not a git rule.) The dev CLI cannot do
  61. an interactive ident login, so Gitoria issues **personal access tokens** (per user, named, revocable, stored hashed)
  62. that go in the password field: `git clone https://slug.gitoria.worldapi.org/repo.git` → user name + token, stored by
  63. `git credential-store`/`cache`/OS keychain. HTTPS only, never plain HTTP.
  64. - **SSH keys**: the user pastes public keys in the settings page; `AuthorizedKeysCommand` looks the key up in Hybriel,
  65. so a new key works instantly and no `authorized_keys` file is edited. Supports ed25519/rsa/ecdsa; the forced command
  66. refuses shell and any command except the two git ones.
  67. ## What hl:proc can and cannot do (for the reading side)
  68. Text output comes as lines (`run(...).lines`, `spawn` `line` events); binary output as Bytes (`binary = true`). Fine as
  69. lines, because it is text: `log`, `show --stat`, `diff`, `ls-tree`, `for-each-ref`, `rev-list`, `blame
  70. --porcelain`, `merge-tree`, `grep`, `shortlog`. Rules for the code that wraps it:
  71. - argv list only (`spawnArgs`), never a shell string with user input; slug from a whitelist `[a-z0-9-]`; refs/paths
  72. after `--`; refuse names starting with `-`.
  73. - Machine-readable formats with a separator that cannot occur (`--format='%H%x1f%an%x1f%at%x1f%s'`), one record per
  74. line; take bodies with a separate call. No `-z` (lines only).
  75. - **Blobs** (raw file view, images, downloads): `run([...'cat-file','blob', '<ref>:<path>'], { binary = true, cwd })`
  76. returns the exact bytes in `.data` (tested, incl. `\xff`, NUL and CRLF). No base64 detour. Text views can use the
  77. same call and decode with `.toString()` (a located error on invalid UTF-8: check first, show "binary" instead).
  78. - **Archives** (`git archive` zip/tar.gz for releases): the same, `binary = true`, answer the Bytes as the response.
  79. - Limit each call (timeout via `kill`, cap on lines) so one huge repo cannot stall the server.
  80. ## What else a hub needs (beyond clone/push/browse)
  81. Needed for a GitHub/GitLab-like experience, with the git command behind each. **MVP** = needed for #7–#12.
  82. | Feature | git mechanism | MVP |
  83. |---|---|---|
  84. | Create empty repo, default branch | `git init --bare -b <main>`; change via `git symbolic-ref HEAD` | yes |
  85. | Rename / delete repo | move / remove directory | yes |
  86. | Clone URLs (HTTPS, SSH) on the repo page | — | yes |
  87. | Branch and tag lists, default branch | `for-each-ref` | yes |
  88. | Commit list per branch / per file, paging | `log --format=… -n -skip <ref> -- <path>` | yes |
  89. | Commit page with unified diff + stats | `show`, `diff-tree -p --numstat` | yes |
  90. | Tree and file view, raw, README | `ls-tree`, `cat-file` | yes |
  91. | Whole project at a commit / branch | `ls-tree -r`, `archive` | yes |
  92. | Push hook → PR and release detection | `post-receive` | yes |
  93. | Compare two refs, PR diff | `diff a...b`, `rev-list a..b` | yes (#11) |
  94. | Merge a PR (fast-forward, merge commit, squash) | `merge-tree --write-tree`, `commit-tree`, `update-ref` in the bare repo, no worktree | later (merging is not decided by the creator) |
  95. | Tags for releases (`|||RL`) | annotated tag via `mktag` / `tag -a` with `GIT_*` identity (`env` option) | yes (#12) |
  96. | Release archive | `git archive --format=zip\|tar.gz <tag>` | later (content not decided) |
  97. | Blame, file history | `blame --porcelain`, `log --follow` | nice |
  98. | Code search | `git grep` on a ref | nice |
  99. | Contributors / activity | `shortlog -sne`, `rev-list --count` | nice |
  100. | SSH keys, access tokens, per-repo collaborators | Hybriel data + transport auth | yes (#7) |
  101. | Protected branches / no force push | `update` hook asks Hybriel, or `receive.denyNonFastForwards`, `receive.denyDeletes` | later |
  102. | Size limits, garbage collection | `receive.maxInputSize`, scheduled `git gc --auto` | later |
  103. | Webhooks / CI triggers | fan-out from `post-receive` | later |
  104. | Commit signatures ("Verified") | `%G?` in log format | later |
  105. | Forks | `git clone --bare` + alternates | later |
  106. | Git LFS | separate protocol | not planned |
  107. Nothing needs a working tree on the server: keep bare repos only.
  108. ## Issues found (Hybriel)
  109. - hybriel#42 (stdin write, raw bytes) and #37 (cwd, env): fixed and merged; the workarounds (shell + base64 for blobs,
  110. transport container for HTTPS) are removed from this document.
  111. - hybriel#44: `'\n'` is not a newline escape — matters when writing pkt-lines.
  112. ## Small choices made here
  113. - Clone address: `https://<slug>.gitoria.worldapi.org/repo.git` (path `/repo.git/…` goes to the transport, no clash with
  114. `/code`, `/commit`, …); SSH: `[email protected]:<slug>.git` on a separate port.
  115. - Repos are stored as `repos/<slug>.git`, bare only.
  116. - HTTPS git is served by Hybriel; only SSH gets a container.
  117. - Tokens, not account passwords, go in the HTTPS password field.

Branches

Latest commits

  • 8d9450fdantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 205d5fe4gitoria: Hybriel master ff51cf46; ssh keys/tokens no double rows (session sync); gates follow #20mre
  • 9b27cb26gitoria#21: installable app (manifest, service worker, offline start page), own iconmre
  • 68dcb603deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • e2deed6dgitoria#20: "Add code" only on the Code page of an empty repository, no collapsiblemre
  • 8bb97ffddeploy.sh: never send .git or .gitignore to Byrodinmre
  • fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mre
  • 4a2d7125initial commitmre