Files
gitmost/apps/server/src/integrations/git-sync/git-sync.module.ts
claude_code 59a6f65f77 feat(git-sync): serve spaces over smart-HTTP (gitmost as a two-way git host)
Expose each git-sync-enabled space as a clonable/pushable git repo over HTTP,
so `git clone https://<user>:<pass>@<host>/git/<spaceId>.git` works and external
pushes flow back into Docmost pages — gitmost itself acts as the git host (no
external GitHub/Gitea, no SSH).

Transport: shell out to `git http-backend` (CGI; git is already in the runtime
image) which implements the full smart-HTTP protocol (info/refs, upload-pack,
receive-pack, protocol v2). A raw Fastify route `/git/*` (mounted at the root,
outside the `/api` prefix) bridges the request/response to the CGI; passthrough
content-type parsers for the git media types stream the raw body to stdin.

Reuse the existing engine: clients push the vault's `main` branch, whose commits
beyond `refs/docmost/last-pushed` the engine already reconciles into Docmost.

- http/git-http.service.ts — auth (HTTP Basic -> AuthService.verifyUserCredentials),
  self-resolved workspace (DomainMiddleware does not run for this raw route),
  per-space gating (global + per-space gitSync flags, 404 hides existence),
  CASL authz (Read=fetch, Manage=push), dispatch.
- http/git-http-backend.service.ts — spawn `git http-backend`, binary-safe CGI
  response parsing (Status/headers/body), stream to the socket.
- http/git-http.helpers.ts — pure path parse, service->kind mapping, gate decision
  (unit-tested); rejects literal and percent-encoded path traversal.
- orchestrator: extract reusable withSpaceLock (CAS-guarded lock heartbeat so a
  long push cannot let the lock expire mid-cycle) and add ingestExternalPush
  (receive-pack + Docmost cycle under one lock; 503 on contention).
- vault-registry: ensureServable() — ensureRepo + idempotent receive.denyCurrentBranch
  =updateInstead / denyNonFastForwards / http.receivepack / http.uploadpack.
- env: GIT_SYNC_HTTP_ENABLED (defaults to GIT_SYNC_ENABLED) + validation.
- main.ts: register the /git/* route and the git content-type parsers.

Tests: pure helpers, CGI parsing, and the GitHttpService handler (auth/gate/authz
+ workspace resolution). Server tsc + git-sync/env suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 05:30:28 +03:00

61 lines
2.6 KiB
TypeScript

import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { DatabaseModule } from '@docmost/db/database.module';
import { EnvironmentModule } from '../environment/environment.module';
import { CollaborationModule } from '../../collaboration/collaboration.module';
import { PageModule } from '../../core/page/page.module';
import { AuthModule } from '../../core/auth/auth.module';
import { GitmostDataSourceService } from './services/gitmost-datasource.service';
import { GitSyncOrchestrator } from './services/git-sync.orchestrator';
import { VaultRegistryService } from './services/vault-registry.service';
import { PageChangeListener } from './listeners/page-change.listener';
import { GitSyncController } from './git-sync.controller';
import { GitHttpBackendService } from './http/git-http-backend.service';
import { GitHttpService } from './http/git-http.service';
/**
* The git-sync control plane (plan §6). Wires the native datasource, the
* orchestrator (poll + leader-lock), the per-space vault registry, the
* event-driven listener, and the admin trigger controller.
*
* Imports:
* - DatabaseModule (global) — PageRepo / SpaceRepo / KyselyDB for the
* datasource + orchestrator queries;
* - EnvironmentModule (global) — EnvironmentService config;
* - CollaborationModule — exports CollaborationGateway for native body writes;
* - PageModule — exports PageService for structural mutations;
* - ScheduleModule (NOT forRoot) — so SchedulerRegistry is injectable (the
* orchestrator registers a DYNAMIC poll interval in onModuleInit). forRoot()
* is already registered globally by TelemetryModule; importing the plain
* module here avoids a duplicate scheduler registration (plan §6 note).
*
* RedisService is provided by the global RedisModule (app.module) and CASL's
* WorkspaceAbilityFactory by the global CaslModule — both resolve without an
* explicit import here.
*/
@Module({
imports: [
DatabaseModule,
EnvironmentModule,
CollaborationModule,
PageModule,
// AuthModule exports AuthService (verifyUserCredentials for /git HTTP Basic).
AuthModule,
ScheduleModule,
],
controllers: [GitSyncController],
providers: [
GitmostDataSourceService,
GitSyncOrchestrator,
VaultRegistryService,
PageChangeListener,
// /git smart-HTTP host (the raw Fastify route in main.ts resolves these).
GitHttpBackendService,
GitHttpService,
],
// Exported so the raw Fastify route registered in main.ts can resolve the
// handler from the Nest container (app.get(GitHttpService)).
exports: [GitHttpService],
})
export class GitSyncModule {}