Skip to content

Commit 4af47a6

Browse files
authored
Merge pull request #104 from Payel-git-ol/fix/federation-v2
Fix/federation v2
2 parents 1ad4161 + 8540f05 commit 4af47a6

12 files changed

Lines changed: 9637 additions & 9241 deletions

File tree

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.sh text eol=lf
2+
Dockerfile text eol=lf
3+
Dockerfile* text eol=lf

.knowledge/.codex/sessions/2026/01/07/rollout-2026-01-07T16-47-40-019b987f-a226-75d2-af20-ecc14061fc70.jsonl

Lines changed: 9215 additions & 9215 deletions
Large diffs are not rendered by default.

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ RUN printf "%s\n" \
2727
RUN mkdir -p /home/dev/app && chown -R dev:dev /home/dev
2828

2929
COPY entrypoint.sh /entrypoint.sh
30-
RUN chmod +x /entrypoint.sh
30+
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh
3131

3232
EXPOSE 22
33+
EXPOSE 3334
3334
ENTRYPOINT ["/entrypoint.sh"]

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,25 @@ pnpm run docker-git clone https://github.com/agiens/crm/issues/123 --force-env
3737
pnpm run docker-git clone https://github.com/agiens/crm/tree/vova-fork --force --mcp-playwright
3838
```
3939

40+
## API Docker (separate runtime)
41+
42+
HTTP API (`packages/api`) has a dedicated Docker image and compose file:
43+
44+
```bash
45+
docker compose -f docker-compose.api.yml up -d --build
46+
curl -s http://127.0.0.1:3334/health
47+
```
48+
49+
By default API port `3334` is published to host (`127.0.0.1:3334`).
50+
51+
Useful env overrides:
52+
- `DOCKER_GIT_API_BIND_HOST` (default: `127.0.0.1`)
53+
- `DOCKER_GIT_API_PORT` (default: `3334`)
54+
- `DOCKER_GIT_PROJECTS_ROOT_HOST` (host path, default: `/home/dev/.docker-git`)
55+
- `DOCKER_GIT_PROJECTS_ROOT` (container path, default: `/home/dev/.docker-git`)
56+
57+
Detailed federation subscription workflow and JSON examples are documented in `packages/api/README.md`.
58+
4059
## Parallel Issues / PRs
4160

4261
When you clone GitHub issue or PR URLs, docker-git creates isolated project paths and container names:

docker-compose.api.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
services:
2+
api:
3+
build:
4+
context: .
5+
dockerfile: packages/api/Dockerfile
6+
container_name: docker-git-api
7+
environment:
8+
DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334}
9+
DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}
10+
DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN: ${DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN:-}
11+
DOCKER_GIT_FEDERATION_ACTOR: ${DOCKER_GIT_FEDERATION_ACTOR:-docker-git}
12+
ports:
13+
- "${DOCKER_GIT_API_BIND_HOST:-127.0.0.1}:${DOCKER_GIT_API_PORT:-3334}:${DOCKER_GIT_API_PORT:-3334}"
14+
volumes:
15+
- /var/run/docker.sock:/var/run/docker.sock
16+
- ${DOCKER_GIT_PROJECTS_ROOT_HOST:-/home/dev/.docker-git}:${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}
17+
restart: unless-stopped

packages/api/Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
FROM ubuntu:24.04
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
WORKDIR /workspace
5+
6+
RUN apt-get update && apt-get install -y --no-install-recommends \
7+
ca-certificates curl git docker.io docker-compose-v2 \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
11+
&& apt-get install -y --no-install-recommends nodejs \
12+
&& npm i -g pnpm@10.28.0 \
13+
&& rm -rf /var/lib/apt/lists/*
14+
15+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json tsconfig.json ./
16+
COPY patches ./patches
17+
COPY packages ./packages
18+
19+
RUN pnpm install --frozen-lockfile
20+
RUN pnpm --filter ./packages/lib build
21+
RUN pnpm --filter ./packages/api build
22+
23+
ENV DOCKER_GIT_API_PORT=3334
24+
EXPOSE 3334
25+
26+
CMD ["node", "packages/api/dist/src/main.js"]

packages/api/README.md

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
# @effect-template/api
22

3-
Clean-slate v1 HTTP API for docker-git orchestration.
3+
HTTP API for docker-git orchestration (projects, agents, logs/events, federation).
44

55
## UI wrapper
66

7-
После запуска API открой:
7+
After API startup open:
88

99
- `http://localhost:3334/`
1010

11-
Это встроенная фронт-обвязка для ручного тестирования endpoint-ов (проекты, агенты, логи, SSE).
11+
This page is a built-in UI shell for manual API checks without CLI.
1212

13-
## Run
13+
## Run (local)
1414

1515
```bash
1616
pnpm --filter ./packages/api build
1717
pnpm --filter ./packages/api start
1818
```
1919

20-
Env:
20+
## Run (dedicated Docker for API)
2121

22+
From repository root:
23+
24+
```bash
25+
docker compose -f docker-compose.api.yml up -d --build
26+
curl -s http://127.0.0.1:3334/health
27+
```
28+
29+
Default port mapping:
30+
31+
- host: `127.0.0.1:3334`
32+
- container: `3334`
33+
34+
Optional env:
35+
36+
- `DOCKER_GIT_API_BIND_HOST` (default: `127.0.0.1`)
2237
- `DOCKER_GIT_API_PORT` (default: `3334`)
23-
- `DOCKER_GIT_PROJECTS_ROOT` (default: `~/.docker-git`)
24-
- `DOCKER_GIT_API_LOG_LEVEL` (default: `info`)
25-
- `DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN` (optional public ActivityPub domain, e.g. `https://social.my-domain.tld`)
38+
- `DOCKER_GIT_PROJECTS_ROOT_HOST` (host path with docker-git projects, default: `/home/dev/.docker-git`)
39+
- `DOCKER_GIT_PROJECTS_ROOT` (container path, default: `/home/dev/.docker-git`)
40+
- `DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN` (optional public ActivityPub origin)
2641
- `DOCKER_GIT_FEDERATION_ACTOR` (default: `docker-git`)
2742

2843
## Endpoints
@@ -35,7 +50,7 @@ Env:
3550
- `GET /federation/followers`
3651
- `GET /federation/following`
3752
- `GET /federation/liked`
38-
- `POST /federation/follows` (create ActivityPub `Follow` activity for task-feed subscription)
53+
- `POST /federation/follows` (create ActivityPub `Follow` subscription)
3954
- `GET /federation/follows`
4055
- `GET /projects`
4156
- `GET /projects/:projectId`
@@ -54,20 +69,71 @@ Env:
5469
- `POST /projects/:projectId/agents/:agentId/stop`
5570
- `GET /projects/:projectId/agents/:agentId/logs`
5671

57-
## Example
72+
## Subscription workflow (ActivityPub Follow + ForgeFed issues)
73+
74+
1. Read actor profile (contains `inbox/outbox/followers/following/liked`):
5875

5976
```bash
60-
curl -s http://localhost:3334/projects
61-
curl -s -X POST http://localhost:3334/projects/<projectId>/up
62-
curl -s -N http://localhost:3334/projects/<projectId>/events
77+
curl -s http://127.0.0.1:3334/federation/actor
78+
```
6379

64-
curl -s http://localhost:3334/federation/actor
80+
2. Create follow subscription:
6581

66-
curl -s -X POST http://localhost:3334/federation/follows \
82+
```bash
83+
curl -sS -X POST http://127.0.0.1:3334/federation/follows \
6784
-H 'content-type: application/json' \
68-
-d '{"domain":"social.my-domain.tld","object":"https://social.my-domain.tld/issues/followers"}'
85+
-d '{
86+
"domain":"https://social.provercoder.ai",
87+
"actor":"https://dev.example/users/bot",
88+
"object":"https://tracker.example/issues/followers",
89+
"capability":"https://tracker.example/caps/follow"
90+
}'
91+
```
6992

70-
curl -s -X POST http://localhost:3334/federation/inbox \
93+
`domain` is used as public origin. `.example` hosts in `actor/object/capability` are normalized to that domain.
94+
95+
3. Confirm subscription by sending `Accept` into inbox:
96+
97+
```bash
98+
curl -sS -X POST http://127.0.0.1:3334/federation/inbox \
7199
-H 'content-type: application/json' \
72-
-d '{"@context":["https://www.w3.org/ns/activitystreams","https://forgefed.org/ns"],"id":"https://social.my-domain.tld/offers/42","type":"Offer","target":"https://social.my-domain.tld/issues","object":{"type":"Ticket","id":"https://social.my-domain.tld/issues/42","attributedTo":"https://origin.my-domain.tld/users/alice","summary":"Title","content":"Body"}}'
100+
-d '{
101+
"@context":"https://www.w3.org/ns/activitystreams",
102+
"type":"Accept",
103+
"object":"https://social.provercoder.ai/federation/activities/follows/<id>"
104+
}'
105+
```
106+
107+
4. Verify follow state and collections:
108+
109+
```bash
110+
curl -s http://127.0.0.1:3334/federation/follows
111+
curl -s http://127.0.0.1:3334/federation/following
112+
curl -s http://127.0.0.1:3334/federation/outbox
113+
```
114+
115+
5. Push issue offer through ForgeFed inbox:
116+
117+
```bash
118+
curl -sS -X POST http://127.0.0.1:3334/federation/inbox \
119+
-H 'content-type: application/json' \
120+
-d '{
121+
"@context":["https://www.w3.org/ns/activitystreams","https://forgefed.org/ns"],
122+
"id":"https://social.provercoder.ai/offers/42",
123+
"type":"Offer",
124+
"target":"https://social.provercoder.ai/issues",
125+
"object":{
126+
"type":"Ticket",
127+
"id":"https://social.provercoder.ai/issues/42",
128+
"attributedTo":"https://origin.provercoder.ai/users/alice",
129+
"summary":"Need reproducible CI parity",
130+
"content":"Implement API behavior matching CLI."
131+
}
132+
}'
133+
```
134+
135+
6. Verify persisted issues:
136+
137+
```bash
138+
curl -s http://127.0.0.1:3334/federation/issues
73139
```

packages/api/src/api/contracts.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export type CreateFollowRequest = {
137137
export type FollowStatus = "pending" | "accepted" | "rejected"
138138

139139
export type ActivityPubFollowActivity = {
140-
readonly "@context": "https://www.w3.org/ns/activitystreams"
140+
readonly "@context": string | ReadonlyArray<string>
141141
readonly id: string
142142
readonly type: "Follow"
143143
readonly actor: string
@@ -176,9 +176,9 @@ export type FollowSubscription = {
176176
readonly inbox?: string | undefined
177177
readonly to: ReadonlyArray<string>
178178
readonly capability?: string | undefined
179-
readonly status: FollowStatus
179+
status: FollowStatus
180180
readonly createdAt: string
181-
readonly updatedAt: string
181+
updatedAt: string
182182
readonly activity: ActivityPubFollowActivity
183183
}
184184

packages/api/src/http.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,13 @@ const resolveFederationContext = (
174174

175175
export const makeRouter = () => {
176176
const base = HttpRouter.empty.pipe(
177-
HttpRouter.get("/", textResponse(uiHtml, "text/html; charset=utf-8", 200)),
177+
HttpRouter.get("/",
178+
Effect.gen(function*(_) {
179+
const request = yield* _(HttpServerRequest.HttpServerRequest)
180+
console.log("GET / request:", request.url, "headers:", request.headers)
181+
return yield* _(textResponse(uiHtml, "text/html; charset=utf-8", 200))
182+
}).pipe(Effect.catchAll(errorResponse))
183+
),
178184
HttpRouter.get("/ui/styles.css", textResponse(uiStyles, "text/css; charset=utf-8", 200)),
179185
HttpRouter.get("/ui/app.js", textResponse(uiScript, "application/javascript; charset=utf-8", 200)),
180186
HttpRouter.get("/health", jsonResponse({ ok: true }, 200)),

packages/api/src/program.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createServer } from "node:http"
55

66
import { makeRouter } from "./http.js"
77
import { initializeAgentState } from "./services/agents.js"
8+
import { startOutboxPolling } from "./services/federation.js"
89

910
const resolvePort = (env: Record<string, string | undefined>): number => {
1011
const raw = env["DOCKER_GIT_API_PORT"] ?? env["PORT"]
@@ -42,10 +43,18 @@ export const program = (() => {
4243
const app = router.pipe(HttpServer.serve(requestLogger), HttpServer.withLogAddress)
4344
const server = createServer()
4445
const serverLayer = NodeHttpServer.layer(() => server, { port })
46+
47+
const pollingInterval = parseInt(process.env["DOCKER_GIT_OUTBOX_POLLING_INTERVAL_MS"] ?? "5000", 10)
4548

4649
return Effect.scoped(
4750
Console.log(`docker-git api boot port=${port}`).pipe(
4851
Effect.zipRight(initializeAgentState()),
52+
Effect.zipRight(
53+
Console.log(`docker-git outbox polling interval=${pollingInterval}ms`)
54+
),
55+
Effect.zipRight(
56+
Effect.fork(startOutboxPolling(pollingInterval))
57+
),
4958
Effect.zipRight(Layer.launch(Layer.provide(app, serverLayer)))
5059
)
5160
)

0 commit comments

Comments
 (0)