| 1 | # Git transport (SSH) |
| 2 | |
| 3 | Forge stores **bare repositories** under `FORGE_REPOS_PATH` (default `./data/repos`, Docker volume `/data/repos`). |
| 4 | |
| 5 | ## Docker Compose |
| 6 | |
| 7 | `docker compose up` starts: |
| 8 | |
| 9 | | Service | Port | Role | |
| 10 | |---------|------|------| |
| 11 | | `forge` | 8770 | HTTP API + WitDB | |
| 12 | | `git-ssh` | 2222 | OpenSSH `git` user, repos via symlinks | |
| 13 | |
| 14 | ### Required env (forge) |
| 15 | |
| 16 | ```bash |
| 17 | FORGE_PUBLIC_BASE_URL=http://127.0.0.1:8770 |
| 18 | FORGE_GIT_BASE_URL=ssh://git@127.0.0.1 |
| 19 | FORGE_GIT_SSH_PORT=2222 |
| 20 | FORGE_GIT_SSH_SYMLINK_ROOT=/data/git-home |
| 21 | ``` |
| 22 | |
| 23 | `cloneUrl` is **scp-style** (`git@host:repo.git`), not `ssh://` — git-shell rejects absolute paths from `ssh://` URLs on push. |
| 24 | |
| 25 | `FORGE_GIT_SSH_PORT` (default `2222` in Compose) is **not** embedded in `cloneUrl`. Use one of: |
| 26 | |
| 27 | ```sshconfig |
| 28 | Host 127.0.0.1 |
| 29 | Port 2222 |
| 30 | ``` |
| 31 | |
| 32 | ```bash |
| 33 | export GIT_SSH_COMMAND="ssh -p 2222" |
| 34 | git clone git@127.0.0.1:my-repo.git |
| 35 | ``` |
| 36 | |
| 37 | ### Authorized keys (git-ssh) |
| 38 | |
| 39 | Inject deploy keys for the pilot: |
| 40 | |
| 41 | ```bash |
| 42 | export FORGE_GIT_AUTHORIZED_KEYS="$(cat ~/.ssh/id_ed25519.pub)" |
| 43 | docker compose up -d --build |
| 44 | ``` |
| 45 | |
| 46 | Or mount `authorized_keys` at `/etc/forge/authorized_keys` in the `git-ssh` container. |
| 47 | |
| 48 | ### SSH host keys (persist across redeploy) |
| 49 | |
| 50 | Git-ssh stores its **RSA host key** on the shared Docker volume, not in the container layer: |
| 51 | |
| 52 | | Path | Role | |
| 53 | |------|------| |
| 54 | | `/data/git-ssh/hostkeys/ssh_host_rsa_key` | Stable fingerprint after first `docker compose up` | |
| 55 | | `/data/repos` | Bare repositories (unchanged) | |
| 56 | |
| 57 | Override with `FORGE_GIT_SSH_HOSTKEYS_PATH` on the `git-ssh` service. |
| 58 | |
| 59 | After a **first** deploy on a fresh volume, clients add the fingerprint once (`ssh-keyscan -p 2222 …`). Later `docker compose up -d --build` redeploys **reuse** the same key — no `REMOTE HOST IDENTIFICATION HAS CHANGED` from routine deploys. |
| 60 | |
| 61 | If you intentionally wipe the volume, expect a new host key and update `known_hosts`. |
| 62 | |
| 63 | ## Local dev (no Docker) |
| 64 | |
| 65 | Forge creates bare repos and optional symlinks under `GitSshSymlinkRoot` when creating a repo. Point your SSH `git` user at the same directory layout, or clone via filesystem path for tests. |
| 66 | |
| 67 | ## Smoke test |
| 68 | |
| 69 | ```powershell |
| 70 | .\scripts\smoke-git-transport.ps1 |
| 71 | .\scripts\smoke-git-transport.ps1 -SshKeyPath "$env:USERPROFILE\.ssh\id_ed25519.pub" |
| 72 | ``` |
| 73 | |
| 74 | ## Push hooks |
| 75 | |
| 76 | Each bare repo gets a `post-receive` hook that POSTs to `/api/v1/git/push`. Set `FORGE_GIT_HOOK_SECRET` on both `forge` and `git-ssh` when `FORGE_REQUIRE_GIT_HOOK_SECRET=true` or `FORGE_REQUIRE_AUTH=true` (public AMS). |
| 77 | |