- definitions.md: formal term disambiguation for overloaded concepts (service, interface, token, identity, domain) with cross-domain mapping tables (alknet ↔ Keystone, distributed git, rustfs) and 8 open questions - references/rustfs/: research on rustfs S3 store, Keystone/OIDC integration, and credential mapping to CredentialSet - references/gitserver/: research on gitserver library architecture and integration paths as HTTP MessageInterface and SSH adapter - references/openstack-keystone/: research on Keystone identity concepts (tokens, scoping, service catalog, RBAC, trust delegation, federation) and what alknet should adopt vs skip - references/distributed-identity/: research on decentralized git, smart contract ACL, on-chain identity, and Radicle comparison
32 KiB
Gitserver Reference Document
Source: https://github.com/WJQSERVER/gitserver (cloned at
/workspace/gitserver/) Version: 0.0.3 (workspace Cargo.toml) License: MPL-2.0 (primary); upstream portions MIT (preserved in UPSTREAM-LICENSE) Upstream origin: https://github.com/ggueret/git-server Date researched: 2026-06-08 Purpose: Evaluate gitserver as a basis for a git service adapter within alknet
1. Architecture Overview
1.1 What is gitserver?
Gitserver is a Rust-native Git Smart HTTP server that does not require an installed git binary at runtime. All Git operations (ref advertisement, pack generation, receive-pack) are implemented via the gitoxide (gix) crate. It supports both Git protocol v1 and v2, including shallow clones and multi-ack negotiation.
The project follows a library-first design: gitserver-core and gitserver-http are reusable libraries, while the gitserver binary is a thin CLI wrapper for standalone deployment.
1.2 Crate Structure
crates/
├── gitserver-core/ # Git protocol operations (no HTTP dependency)
│ ├── backend.rs # GitBackend: unified interface for refs/pack/receive-pack
│ ├── discovery.rs # RepoStore: filesystem-based repo discovery
│ ├── dynamic_registry.rs # DynamicRepoRegistry, RepoResolver, MutableRepoRegistry traits
│ ├── error.rs # Error types (RepoNotFound, PathTraversal, Protocol, Git, Io)
│ ├── pack.rs # UploadPackRequest parsing, pack generation with side-band-64k
│ ├── path.rs # Path safety: resolve_repo_path (normalize + canonicalize)
│ ├── pktline.rs # pkt-line encoding/decoding utilities
│ ├── protocol_v2.rs # Git protocol v2: ls-refs, fetch, shallow, stateless-rpc
│ ├── receive_pack.rs # receive-pack: ref advertisement, pack reception, fast-forward validation
│ └── refs.rs # Protocol v1 ref advertisement
├── gitserver-http/ # Axum HTTP layer
│ ├── error.rs # AppError enum → HTTP status codes
│ ├── handlers.rs # Route handlers: info_refs, upload_pack, receive_pack, healthz, list
│ ├── lib.rs # router() function + public re-exports
│ └── state.rs # SharedState (RepoMode, AuthConfig, ServicePolicy, draining flag)
├── gitserver/ # CLI binary (thin wrapper)
│ └── main.rs # CLI args, RepoStore discovery, Axum server, graceful shutdown
└── gitserver-bench/ # Performance benchmarks (not published)
1.3 Key Dependencies
| Dependency | Version | Purpose |
|---|---|---|
gix |
0.80.0 | Native Git repository operations (open refs, object store, rev-walk) |
gix-pack |
0.67.0 | Pack file writing (receive-pack) |
axum |
0.8.8 | HTTP routing and handlers |
tokio |
1.50.0 | Async runtime, channels, IO |
miniz_oxide |
0.8 | Zlib compression for pack objects |
sha1 |
0.10 | Pack checksum |
flate2 |
1 | Gzip response compression |
zstd |
0.13 | Zstd response compression |
base64 |
0.22 | HTTP Basic auth decoding |
subtle |
2 | Constant-time comparison (auth) |
clap |
4.6.0 | CLI argument parsing |
1.4 Request Flow
Clone/Fetch (Protocol v1)
Client → GET /{repo}/info/refs?service=git-upload-pack
→ Server: resolve repo, verify auth, advertise_refs()
← Ref advertisement response
Client → POST /{repo}/git-upload-pack
→ Server: parse UploadPackRequest, generate_pack()
← Streamed side-band-64k pack response
Clone/Fetch (Protocol v2)
Client → GET /{repo}/info/refs (git-protocol: version=2)
← Capabilities advertisement
Client → POST /{repo}/git-upload-pack (git-protocol: version=2)
→ Server: parse_command_request() → ls-refs or fetch
← ls-refs result or streamed packfile
Push (receive-pack, must be enabled)
Client → GET /{repo}/info/refs?service=git-receive-pack
← Ref advertisement
Client → POST /{repo}/git-receive-pack
→ Server: parse commands, write pack, validate fast-forward, update refs
← Status report (ok/ng per ref)
2. Protocol Support
2.1 Smart HTTP Git Protocol
Gitserver implements the Git Smart HTTP protocol (RFC-like, de facto standard). This is the standard protocol used by git clone http://..., git fetch, and git push over HTTP.
Supported endpoints:
| Method | Endpoint | Protocol Version | Description |
|---|---|---|---|
| GET | /healthz |
— | Health check (no auth) |
| GET | / |
— | JSON repository listing (auth required if configured) |
| GET | /{repo}/info/refs?service=git-upload-pack |
v1 | Ref advertisement for clone/fetch |
| GET | /{repo}/info/refs?service=git-receive-pack |
v1 | Ref advertisement for push (disabled by default) |
| POST | /{repo}/git-upload-pack |
v1 | Pack negotiation and transfer |
| POST | /{repo}/git-receive-pack |
v1 | Push operations (disabled by default) |
| GET | /{repo}/info/refs with git-protocol: version=2 |
v2 | Capabilities advertisement |
| POST | /{repo}/git-upload-pack with git-protocol: version=2 |
v2 | ls-refs and fetch commands |
2.2 Git Operations
| Operation | Supported | Notes |
|---|---|---|
git clone |
✓ | Both v1 and v2 |
git fetch |
✓ | Multi-ack, multi-ack-detailed negotiation |
git push |
✓ (opt-in) | Via --enable-receive-pack or ServicePolicy.receive_pack: true |
| Shallow clone | ✓ | Protocol v2 fetch with deepen |
| OFS_DELTA | ✓ | Offset delta compression in packs |
| Side-band-64k | ✓ | Multiplexed progress/pack data |
| Response compression | ✓ | Gzip and Zstd on ref advertisement |
2.3 Push Restrictions
When receive-pack is enabled, the following restrictions apply:
- Fast-forward only: Branch updates under
refs/heads/*must be fast-forward (old commit is ancestor of new) - No ref deletion: New OID cannot be the zero OID
- No tag overwrite: Updating an existing tag is rejected
- Commits only: Branch tips must point to commit objects
- Timeouts: 300s total, 30s idle
2.4 SSH Git Protocol
Gitserver does not support SSH Git protocol. It is HTTP-only. SSH git access would require a separate implementation or integration layer (see Section 6).
3. Interface Pattern Analysis
3.1 HTTP Handler Architecture
Gitserver's HTTP layer follows a clean handler pattern:
// gitserver-http/src/lib.rs
pub fn router(state: SharedState) -> Router {
Router::new()
.route("/healthz", get(handlers::healthz))
.route("/", get(handlers::list_repos))
.route("/{*path}", get(handlers::info_refs_dispatch))
.route("/{*path}", post(handlers::rpc_dispatch))
.with_state(state)
}
The SharedState is an Axum state object containing:
RepoMode— eitherDiscovered(Arc<RwLock<RepoStore>>)orDynamic { resolver, registry }AuthConfig— optional Basic and/or Bearer authenticationServicePolicy— toggle for upload_pack, upload_pack_v2, receive_packdraining: Arc<AtomicBool>— graceful shutdown flag
Each handler follows this pattern:
- Check
drainingflag → 503 if shutting down - Check
ServicePolicy→ 404 if service disabled - Authenticate request via
require_auth()→ 401 if credentials missing/invalid - Resolve repository via
SharedState::resolve()→ 404 if not found - Execute git operation via
GitBackend - Return streaming or buffered response
3.2 Mapping to alknet's MessageInterface
Gitserver's SharedState + handler pattern maps closely to alknet's proposed MessageInterface trait:
// alknet's proposed MessageInterface
async fn handle_request(&self, request: InterfaceRequest) -> Result<InterfaceResponse>;
Gitserver's handler flow is essentially:
- Receive HTTP request (analogous to
InterfaceRequest) - Extract operation path, auth, and body
- Dispatch to the appropriate Git operation
- Return HTTP response (analogous to
InterfaceResponse)
3.3 Low-Level Handler API
Gitserver also exposes handler functions that can be called directly without going through the Axum router:
use gitserver_http::handlers::{info_refs_endpoint, ServiceKind};
let response = info_refs_endpoint(
&state,
"my-project.git",
ServiceKind::UploadPack,
HeaderMap::new(),
).await?;
This is significant for alknet integration — it means the git logic can be invoked programmatically without HTTP routing.
4. Authentication
4.1 Current Auth Model
Gitserver supports two HTTP authentication mechanisms, both optional:
pub struct AuthConfig {
pub basic: Option<BasicAuthConfig>,
pub bearer_token: Option<String>,
}
pub struct BasicAuthConfig {
pub username: String,
pub password: String,
}
Key characteristics:
- Both can be configured simultaneously; either one passing is sufficient
- Basic auth uses constant-time comparison (
subtlecrate) to prevent timing attacks - Bearer token is compared directly (suitable for generated tokens)
- Failed auth returns
401 UnauthorizedwithWWW-Authenticate: Basic realm="gitserver", Bearer GET /healthzis unauthenticated (always accessible)- Auth is global (same credentials for all repositories) — no per-repo or per-user ACL
4.2 Auth Flow in Handlers
fn require_auth(store: &SharedState, headers: &HeaderMap) -> Result<(), AppError> {
let auth = store.auth();
if auth.basic.is_none() && auth.bearer_token.is_none() {
return Ok(()); // No auth configured → allow all
}
let value = headers.get(AUTHORIZATION)...;
// Try Bearer first, then Basic
// Constant-time comparison for Basic
}
4.3 Mapping to alknet Identity
alknet's IdentityProvider resolves credentials to an Identity. The mapping would be:
| gitserver auth | alknet equivalent | Resolution path |
|---|---|---|
| No auth | Identity::anonymous() or reject |
Configurable policy |
| Basic auth (username/password) | IdentityProvider::resolve_from_token() |
Map to AuthToken or direct lookup |
| Bearer token | IdentityProvider::resolve_from_token() |
Token is already in the right format |
The key gap is that gitserver's auth is single-credential, global, while alknet needs per-identity, per-repository access control. Integration would require:
- Replacing
AuthConfigwith alknet'sIdentityProvider - Extracting identity from the
Authorizationheader - Checking per-repo ACL based on resolved
Identity
5. Storage
5.1 Filesystem-Based Storage
Gitserver currently stores repositories as bare Git repositories on the local filesystem. The storage model is:
ROOT/
├── project-a.git/ # bare repository
│ ├── HEAD
│ ├── objects/
│ ├── refs/
│ └── description
├── org/
│ └── project-b.git/ # nested repository (up to max_depth)
└── ...
The RepoStore::discover(root, max_depth) function:
- Canonicalizes the root path
- Recursively walks subdirectories up to
max_depth - Attempts
gix::open(path)on each directory - If
repo.is_bare(), adds it as aRepoInfo - Path traversal protection via lexical normalization +
canonicalize()double-check
The DynamicRepoRegistry allows programmatic registration/unregistration of repos at runtime, validated by gix::open() confirming the path is a bare repo.
5.2 Storage Abstraction Points
The key storage interaction points in the codebase are:
| Component | Storage Pattern |
|---|---|
RepoStore::discover() |
Filesystem scan (local directory tree) |
DynamicRepoRegistry |
In-memory registry with filesystem-backed paths |
GitBackend::new(repo_path) |
Opens a local bare repo via gix::open() |
receive_pack::write_pack() |
Writes pack to objects/pack/ via gix_pack::Bundle::write_to_directory() |
path::resolve_repo_path() |
Canonical path resolution + traversal protection |
All storage operations assume a local filesystem path. There is no abstraction for remote or object storage backends.
5.3 Rustfs (S3-Compatible) Integration Feasibility
Git operations fundamentally require a local filesystem — gix::open() expects a directory with the standard .git layout (objects, refs, HEAD, etc.). Rustfs (S3-compatible) cannot serve as a direct storage backend for gitoxide's repository operations because:
gix::open()requires a local path — it readsHEAD, refs, and object packs from the filesystem- Pack generation (
generate_pack()) streams objects from the local ODB - Receive-pack writes pack files to the local
objects/pack/directory - Reference updates use
gix::Repository::edit_references()which operates on the local refstore
However, rustfs could be used in several supporting roles:
| Integration Approach | Description | Feasibility |
|---|---|---|
| Repo sync backend | Store bare repo tarballs in rustfs; sync to local disk on demand | High — sync from S3 to local FS before serving |
| Backup/archive | Push repo backups to rustfs buckets | High — out-of-band backup |
| Git LFS storage | Store large file objects in rustfs via Git LFS | Medium — requires LFS server implementation |
| Object store proxy | Cache layer: serve from local FS, sync to/from rustfs | Medium — needs repo lifecycle management |
| Direct S3 repo | Custom gix object backend reading from S3 |
Low — would require deep gitoxide customization |
The most practical approach: use rustfs as a backing store for repository synchronization. Gitserver would always operate on local filesystem paths, but a separate component would manage syncing repos to/from rustfs buckets.
6. SSH Support
6.1 Current State
Gitserver has no SSH transport capability. It only implements the HTTP Smart Git protocol. Adding SSH support would require implementing the Git SSH protocol, which is a different wire format:
| Aspect | Smart HTTP | SSH |
|---|---|---|
| Transport | HTTP (request/response) | Persistent SSH channel |
| Service discovery | GET /info/refs?service=git-upload-pack |
ssh://host/git-upload-pack 'repo' |
| Protocol framing | pkt-line over HTTP | pkt-line over SSH channel |
| Authentication | HTTP Authorization header | SSH key-based |
| Multiplexing | HTTP/2 or separate connections | Multiple SSH channels |
6.2 How Git over SSH Works
The Git SSH protocol uses SSH as a transport for the same git-upload-pack and git-receive-pack commands:
Client connects via SSH → server executes git-upload-pack or git-receive-pack
Client ← SSH channel → Server (bidirectional pkt-line stream)
6.3 Integration with alknet's SSH Interface
alknet's SSH interface (SshInterface) is a StreamInterface — it accepts a persistent byte stream and multiplexes it into channels. This maps naturally to Git over SSH:
Approach: Git as an alknet operation over SSH
alknet SSH session
│
├─ Channel: call protocol (operations)
│
└─ Channel: git-upload-pack
OR git-receive-pack
│
▼
gitserver-core protocol logic
(ref advertisement, pack generation, receive-pack)
This would work by:
- The SSH interface receives a connection with a request like
git-upload-pack '/repos/project.git' - alknet resolves the identity from the SSH key fingerprint
- Checks ACL: does this identity have read/write access to this repo?
- Invokes
gitserver-corefunctions directly (no HTTP needed):refs::advertise_refs()→ send over SSH channelpack::generate_pack()→ stream over SSH channelreceive_pack::receive_pack()→ read/write over SSH channel
Key advantage: Since gitserver-core has no HTTP dependency, it can be used directly over SSH channels without the HTTP overhead. The GitBackend API is transport-agnostic.
6.4 Alternative: Dedicated Git SSH Adapter
A simpler approach that doesn't require modifying the SSH channel multiplexing:
alknet SSH session → call protocol → operation "git/upload-pack" →
→ GitAdapter::upload_pack(repo, wants, haves) → streaming response
This treats Git operations as alknet call operations, where the SSH interface is the transport but Git operations are invoked via the call protocol rather than raw SSH channels. This is more aligned with alknet's architecture but requires adapting the Git protocol to the call protocol's request/response model (potentially with streaming).
7. Relevance to alknet
7.1 Mapping to alknet's Interface Model
Gitserver is a textbook MessageInterface implementation:
| alknet MessageInterface | Gitserver Equivalent |
|---|---|
handle_request(InterfaceRequest) |
info_refs_dispatch() / rpc_dispatch() |
InterfaceRequest.operation_path |
URL path (/{repo}/info/refs, /{repo}/git-upload-pack) |
InterfaceRequest.auth_token |
Authorization header → require_auth() |
InterfaceRequest.input |
Request body (pack negotiation data) |
InterfaceResponse.result |
HTTP response body (ref advertisement, pack data) |
InterfaceResponse.status |
HTTP status code |
InterfaceResponse.headers |
Content-Type, Cache-Control, etc. |
However, gitserver manages its own transport (Axum HTTP server), which is exactly the MessageInterface pattern described in alknet's interface model: "MessageInterface implementations manage their own transport. They don't need the Transport trait because they're not wrapping a generic byte stream — they ARE the transport+interface combined."
7.2 Git as an alknet Operation
Git operations could be mapped to alknet's call protocol namespace:
Namespace: "git"
Operations:
- git/list → List available repositories
- git/info-refs → Get ref advertisement for a repo
- git/upload-pack → Clone/fetch (streaming response)
- git/receive-pack → Push (streaming request+response)
- git/ls-refs → Protocol v2 ls-refs
- git/fetch → Protocol v2 fetch
Challenge: Git operations are streaming and bidirectional (especially fetch negotiation and receive-pack), while alknet's call protocol is currently defined as request→response. This needs design consideration:
| Operation | Direction | Stream Duration | alknet Fit |
|---|---|---|---|
git/list |
Request → Response | Short | Direct fit |
git/info-refs |
Request → Response | Short | Direct fit |
git/upload-pack |
Request → Streaming Response | Long | Needs streaming response support |
git/receive-pack |
Streaming Request → Streaming Response | Long | Needs bidirectional streaming |
7.3 Proposed GitAdapter Architecture
┌─────────────────────────────────────────────────────────┐
│ alknet node │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ HttpInterface│ │ SshInterface │ │ DNS/other │ │
│ │ (Message) │ │ (Stream) │ │ (Message) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ OperationRegistry │ │
│ │ "git/list" → GitAdapter::list_repos() │ │
│ │ "git/upload-pack" → GitAdapter::upload_pack() │ │
│ │ "git/receive-pack" → GitAdapter::receive_pack() │ │
│ └──────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ GitAdapter │ │
│ │ - SharedState (repos, auth) │ │
│ │ - GitBackend (protocol ops) │ │
│ │ - IdentityProvider (auth) │ │
│ │ - RepoResolver (filesystem) │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ ┌────────────────┐ │
│ │ Local filesystem │ │ Rustfs sync │ │
│ │ (bare git repos) │ │ (S3 backend) │ │
│ └──────────────────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
7.4 Auth Integration: alknet Identity → Gitserver Auth
Current gitserver auth (single global credential):
AuthConfig {
basic: Option<BasicAuthConfig>, // one username/password
bearer_token: Option<String>, // one token
}
Proposed alknet integration (per-identity, per-repo):
struct GitAdapter {
identity_provider: Arc<dyn IdentityProvider>,
repo_resolver: Arc<dyn RepoResolver>,
backend_factory: Arc<dyn GitBackendFactory>,
acl: Arc<dyn GitAcl>,
}
impl GitAdapter {
async fn handle_request(
&self,
request: InterfaceRequest,
) -> Result<InterfaceResponse> {
// 1. Resolve identity from auth token
let identity = self.identity_provider
.resolve_from_token(request.auth_token)?;
// 2. Parse git operation from path
let operation = parse_git_operation(&request.operation_path)?;
// 3. Check ACL
self.acl.check_access(&identity, &operation.repo, operation.access_type)?;
// 4. Dispatch to gitserver-core logic
// ...
}
}
ACL design (per-repo, per-operation):
enum GitAccess {
Read, // clone, fetch
Write, // push
}
trait GitAcl: Send + Sync {
fn check_access(
&self,
identity: &Identity,
repo: &str,
access: GitAccess,
) -> Result<()>;
}
7.5 Storage Integration with Rustfs
Recommended approach: Rustfs as a sync backend:
trait RepoStorage: Send + Sync {
/// Ensure a local working copy exists for the given repo.
/// May involve syncing from S3 (rustfs) to local disk.
async fn ensure_local(&self, repo: &str) -> Result<PathBuf>;
/// Sync local changes back to S3 (rustfs) after a push.
async fn sync_to_remote(&self, repo: &str) -> Result<()>;
/// List available repos (may consult S3 bucket listing).
async fn list_repos(&self) -> Result<Vec<RepoInfo>>;
}
The flow would be:
GitAdapterreceives a request for repoXRepoStorage::ensure_local("X")checks if the repo exists on local disk; if not, syncs from rustfs- Git operations run on the local filesystem (using
gitserver-coredirectly) - After push operations,
RepoStorage::sync_to_remote("X")pushes updates to rustfs
This maintains gitserver's requirement for a local filesystem while leveraging rustfs for durability and distribution.
7.6 Operation Mapping
| Git Operation | alknet Namespace | alknet Op | Input | Output | Stream? |
|---|---|---|---|---|---|
| List repos | git |
list |
{} |
[RepoInfo] |
No |
| Ref advertisement (v1) | git |
info-refs |
{repo, service: "upload-pack" | "receive-pack"} |
Binary ref advertisement | No |
| Ref capabilities (v2) | git |
capabilities |
{repo} |
Binary capabilities | No |
| Ls-refs (v2) | git |
ls-refs |
{repo, peel, symrefs, ref_prefixes} |
Binary ref listing | No |
| Clone/Fetch | git |
upload-pack |
{repo, wants, haves, done, ...} |
Streamed pack data | Yes (response) |
| Push | git |
receive-pack |
{repo, commands, pack_data} |
Status report | Yes (both) |
7.7 What gitserver-core Provides Directly
The most valuable integration point is gitserver-core — the HTTP-free protocol library:
// Direct usage without HTTP
use gitserver_core::backend::GitBackend;
use gitserver_core::discovery::RepoStore;
use gitserver_core::pack::{UploadPackRequest, UploadPackCapabilities, ShallowRequest};
use gitserver_core::protocol_v2;
// Repository discovery
let store = RepoStore::discover("./repos".into(), 3)?;
let repo = store.resolve("my-project.git")?;
// Protocol v1 ref advertisement
let backend = GitBackend::new(repo.absolute_path.clone());
let refs = backend.advertise_refs()?;
// Pack generation (streaming)
let request = UploadPackRequest { wants, haves, done, ... };
let pack_stream = backend.upload_pack(&request).await?;
// Receive-pack (push)
let result = backend.receive_pack(request_stream).await?;
// Protocol v2
let capabilities = protocol_v2::advertise_capabilities();
let ls_refs_output = protocol_v2::ls_refs(&repo_path, &ls_refs_request)?;
let fetch_output = backend.upload_pack(&fetch_request.upload_request).await?;
These functions can be called from any async context — SSH channel handler, alknet operation handler, HTTP handler — without going through the Axum HTTP layer.
8. Integration Recommendations
8.1 Recommended Integration Strategy
Phase 1: HTTP Gateway (MessageInterface)
Embed gitserver-http's Axum router into alknet's HTTP interface. This provides immediate Git-over-HTTP capability:
// In alknet's HttpInterface::handle_request()
// Route: /git/* → gitserver router
let git_app = gitserver_http::router(git_state);
let app = Router::new()
.nest("/git", git_app) // Mount git under /git
.route("/v1/{namespace}/{op}", post(operation_handler));
This works because gitserver is designed to be nested into existing Axum apps. Auth integration would replace AuthConfig with alknet's IdentityProvider.
Phase 2: SSH Git Adapter (StreamInterface)
Use gitserver-core directly within alknet's SSH interface for Git-over-SSH:
// In alknet's SshInterface channel handler
// SSH channel request: "git-upload-pack '/repos/project.git'"
let backend = GitBackend::new(repo_path);
let refs = backend.advertise_refs()?;
// Send refs over SSH channel
// Stream pack data over SSH channel
Phase 3: Call Protocol Operations (OperationRegistry)
Register Git operations in the operation registry for access via any interface:
registry.register(GitListRepos::new(adapter.clone()));
registry.register(GitUploadPack::new(adapter.clone()));
registry.register(GitReceivePack::new(adapter.clone()));
8.2 Key Modifications Needed
- Auth replacement: Replace
AuthConfigwithIdentityProvider-based auth inhandlers.rs'srequire_auth()function - ACL addition: Add per-repo, per-identity access control (gitserver currently has none)
- RepoResolver abstraction: Replace
RepoStore/DynamicRepoRegistrywith alknet'sRepoResolverthat integrates with rustfs sync - Streaming response support: Adapt alknet's call protocol for streaming (large pack files)
- Bidirectional streaming: For receive-pack, the call protocol needs to support bidirectional streaming
8.3 Risks and Mitigations
| Risk | Mitigation |
|---|---|
| gitserver requires local filesystem | Use rustfs as sync backend; maintain local working copies |
| Auth is global (single credential) | Fork/modify require_auth() to use IdentityProvider |
| No per-repo ACL | Add GitAcl trait in the adapter layer |
| MPL-2.0 license requires modifications to be under MPL-2.0 | Acceptable for alknet (MPL-2.0 is file-level copyleft) |
| Large pack files may not fit alknet's message size limits | Implement streaming response in the call protocol |
| gitoxide version coupling | Pin gix = "0.80.0" as gitserver does |
8.4 License Considerations
- Primary license: MPL-2.0 (file-level copyleft)
- Upstream portions: MIT (preserved in UPSTREAM-LICENSE)
- Implication: Modifications to gitserver's
.rsfiles must remain under MPL-2.0. Linking from alknet code is unrestricted. - Recommendation: Use gitserver as a library dependency. If alknet-specific auth/ACL modifications are needed, contribute them upstream or maintain them as separate files under MPL-2.0.
9. Summary
9.1 Key Findings
- gitserver is a well-structured, library-first Rust Git Smart HTTP server with clean separation between protocol logic (
gitserver-core) and HTTP transport (gitserver-http). - Protocol support is comprehensive: Git Smart HTTP v1 and v2, clone, fetch, push (opt-in), shallow clones, delta compression, streaming pack generation.
- No SSH support exists, but
gitserver-coreis transport-agnostic and can serve Git operations over any channel. - Auth is simple but limited: single global Basic/Bearer credential, no per-repo or per-user ACL.
- Storage is local-filesystem only:
gix::open()requires a local path. S3/rustfs integration requires a sync-to-local approach. - The library design enables direct integration:
GitBackendand protocol functions can be called without HTTP.
9.2 Recommendation
Use gitserver-core as alknet's Git protocol engine. The core crate provides all Git protocol operations (ref advertisement, pack generation, receive-pack, protocol v2) without any HTTP dependency. This allows alknet to expose Git services through any interface (HTTP, SSH, call protocol) while maintaining a single protocol implementation.
Use gitserver-http as alknet's Git HTTP interface by nesting its Axum router under alknet's HTTP interface, with auth replaced by IdentityProvider.
Design a GitAdapter that wraps gitserver-core and integrates with alknet's OperationRegistry, IdentityProvider, and rustfs-backed storage.
9.3 Next Steps
- Fork or vendor
gitserver-coreandgitserver-httpinto alknet's dependency tree - Design the
GitAdaptertrait withIdentityProviderauth andGitAclaccess control - Implement Phase 1: HTTP gateway with nested Axum router and
IdentityProviderauth - Implement
RepoStoragetrait with rustfs sync-to-local strategy - Design streaming extensions to alknet's call protocol for pack file transfer
- Evaluate Phase 2: SSH Git adapter using
gitserver-coredirectly over SSH channels
References
- gitserver README — project overview, quick start, CLI usage
- gitserver Architecture docs — crate responsibilities, request flows
- gitserver Library docs — embedding, dynamic registration, auth config
- gitserver API Reference — REST endpoints, protocol details, error codes
- alknet Interface Model — StreamInterface/MessageInterface design
- gitoxide — underlying Git implementation library