Skip to content

Commit eed7a15

Browse files
committed
docs: add support docs and deployment templates
1 parent 43781ce commit eed7a15

7 files changed

Lines changed: 286 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,23 @@ sudo systemctl status link-assistant-router
434434
journalctl -u link-assistant-router -f
435435
```
436436

437+
### Akash and Kubernetes
438+
439+
Ready-to-edit deployment templates are included for hosted environments:
440+
441+
- [Akash SDL](deploy/akash/deploy.yaml)
442+
- [Kubernetes manifests](deploy/k8s/router.yaml)
443+
444+
Replace placeholder secrets, set `ACTIVITYPUB_ACTOR_BASE_URL` to the public
445+
router URL, and mount or provision Claude Code credentials at
446+
`CLAUDE_CODE_HOME` before exposing the service.
447+
448+
## ForgeFed Integration
449+
450+
The router exposes ActivityPub/ForgeFed endpoints for service discovery and
451+
problem-source federation. See [docs/forgefed.md](docs/forgefed.md) for the
452+
actor document, inbox, follow activity, and deployment verification steps.
453+
437454
## Token System
438455

439456
The router uses JWT-based custom tokens with the `la_sk_` prefix.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Added
2+
- Document ForgeFed integration endpoints and deployment verification steps.
3+
- Add Akash SDL and Kubernetes deployment templates for the router.

deploy/akash/deploy.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
version: "2.0"
3+
4+
services:
5+
router:
6+
image: ghcr.io/link-assistant/router:latest
7+
expose:
8+
- port: 8080
9+
as: 80
10+
to:
11+
- global: true
12+
env:
13+
- ROUTER_HOST=0.0.0.0
14+
- ROUTER_PORT=8080
15+
- CLAUDE_CODE_HOME=/data/claude
16+
- DATA_DIR=/data/router
17+
- ACTIVITYPUB_ACTOR_BASE_URL=https://router.example.com
18+
- TOKEN_SECRET=replace-with-secure-secret
19+
- ACTIVITYPUB_PUBLIC_KEY_PEM=replace-with-public-key-pem
20+
21+
profiles:
22+
compute:
23+
router:
24+
resources:
25+
cpu:
26+
units: 0.25
27+
memory:
28+
size: 512Mi
29+
storage:
30+
size: 2Gi
31+
placement:
32+
dcloud:
33+
pricing:
34+
router:
35+
denom: uakt
36+
amount: 1000
37+
38+
deployment:
39+
router:
40+
dcloud:
41+
profile: router
42+
count: 1

deploy/k8s/router.yaml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
apiVersion: v1
3+
kind: Namespace
4+
metadata:
5+
name: link-assistant
6+
---
7+
apiVersion: v1
8+
kind: Secret
9+
metadata:
10+
name: link-assistant-router
11+
namespace: link-assistant
12+
type: Opaque
13+
stringData:
14+
TOKEN_SECRET: replace-with-secure-secret
15+
ACTIVITYPUB_PUBLIC_KEY_PEM: |
16+
-----BEGIN PUBLIC KEY-----
17+
replace-with-public-key
18+
-----END PUBLIC KEY-----
19+
---
20+
apiVersion: v1
21+
kind: PersistentVolumeClaim
22+
metadata:
23+
name: link-assistant-router-data
24+
namespace: link-assistant
25+
spec:
26+
accessModes:
27+
- ReadWriteOnce
28+
resources:
29+
requests:
30+
storage: 2Gi
31+
---
32+
apiVersion: apps/v1
33+
kind: Deployment
34+
metadata:
35+
name: link-assistant-router
36+
namespace: link-assistant
37+
labels:
38+
app.kubernetes.io/name: link-assistant-router
39+
spec:
40+
replicas: 1
41+
selector:
42+
matchLabels:
43+
app.kubernetes.io/name: link-assistant-router
44+
template:
45+
metadata:
46+
labels:
47+
app.kubernetes.io/name: link-assistant-router
48+
spec:
49+
containers:
50+
- name: router
51+
image: ghcr.io/link-assistant/router:latest
52+
imagePullPolicy: IfNotPresent
53+
ports:
54+
- name: http
55+
containerPort: 8080
56+
env:
57+
- name: ROUTER_HOST
58+
value: 0.0.0.0
59+
- name: ROUTER_PORT
60+
value: "8080"
61+
- name: CLAUDE_CODE_HOME
62+
value: /data/claude
63+
- name: DATA_DIR
64+
value: /data/router
65+
- name: ACTIVITYPUB_ACTOR_BASE_URL
66+
value: https://router.example.com
67+
- name: TOKEN_SECRET
68+
valueFrom:
69+
secretKeyRef:
70+
name: link-assistant-router
71+
key: TOKEN_SECRET
72+
- name: ACTIVITYPUB_PUBLIC_KEY_PEM
73+
valueFrom:
74+
secretKeyRef:
75+
name: link-assistant-router
76+
key: ACTIVITYPUB_PUBLIC_KEY_PEM
77+
readinessProbe:
78+
httpGet:
79+
path: /health
80+
port: http
81+
periodSeconds: 10
82+
livenessProbe:
83+
httpGet:
84+
path: /health
85+
port: http
86+
periodSeconds: 30
87+
volumeMounts:
88+
- name: router-data
89+
mountPath: /data
90+
volumes:
91+
- name: router-data
92+
persistentVolumeClaim:
93+
claimName: link-assistant-router-data
94+
---
95+
apiVersion: v1
96+
kind: Service
97+
metadata:
98+
name: link-assistant-router
99+
namespace: link-assistant
100+
spec:
101+
selector:
102+
app.kubernetes.io/name: link-assistant-router
103+
ports:
104+
- name: http
105+
port: 80
106+
targetPort: http
107+
---
108+
apiVersion: networking.k8s.io/v1
109+
kind: Ingress
110+
metadata:
111+
name: link-assistant-router
112+
namespace: link-assistant
113+
spec:
114+
rules:
115+
- host: router.example.com
116+
http:
117+
paths:
118+
- path: /
119+
pathType: Prefix
120+
backend:
121+
service:
122+
name: link-assistant-router
123+
port:
124+
name: http

docs/forgefed.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# ForgeFed Integration
2+
3+
Link.Assistant.Router exposes a small ActivityPub/ForgeFed surface so coding
4+
problem sources can discover and address the router as a service actor.
5+
6+
## Endpoints
7+
8+
| Endpoint | Method | Purpose |
9+
|---|---|---|
10+
| `/actor/code` | `GET` | Public service actor document |
11+
| `/inbox/code` | `POST` | Inbound ActivityStreams activities |
12+
| `/outbox/code` | `GET` | Local actor outbox collection |
13+
| `/actors/code/followers` | `GET` | Local followers collection |
14+
| `/activities/follow-problemsets-code-001` | `GET` | Follow activity targeting `problemsets.lefine.pro` |
15+
16+
All successful ActivityPub responses use `application/activity+json`.
17+
18+
## Configuration
19+
20+
Set these values when the router is served from a public URL:
21+
22+
| Variable | Description |
23+
|---|---|
24+
| `ACTIVITYPUB_ACTOR_BASE_URL` | Public origin used in actor, inbox, outbox, and activity IDs |
25+
| `ACTIVITYPUB_PUBLIC_KEY_PEM` | PEM public key advertised in the actor document |
26+
27+
Example:
28+
29+
```bash
30+
export ACTIVITYPUB_ACTOR_BASE_URL=https://router.example.com
31+
export ACTIVITYPUB_PUBLIC_KEY_PEM="$(cat public.pem)"
32+
export TOKEN_SECRET="$(openssl rand -hex 32)"
33+
link-assistant-router serve
34+
```
35+
36+
## Discovery Check
37+
38+
After deployment, verify the actor document:
39+
40+
```bash
41+
curl -H 'Accept: application/activity+json' \
42+
https://router.example.com/actor/code | jq .
43+
```
44+
45+
The response should include:
46+
47+
- `@context` containing `https://www.w3.org/ns/activitystreams` and
48+
`https://forgefed.org/ns`
49+
- `type` set to `Service`
50+
- `inbox` set to the public `/inbox/code` URL
51+
- `publicKey.owner` matching the actor ID
52+
53+
## Following Problem Sets
54+
55+
The router publishes a reusable Follow activity for the public problemsets
56+
actor:
57+
58+
```bash
59+
curl -H 'Accept: application/activity+json' \
60+
https://router.example.com/activities/follow-problemsets-code-001 | jq .
61+
```
62+
63+
Submit that activity to a compatible ForgeFed inbox when a remote problem source
64+
requires an explicit follow request.

tests/integration_test.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,3 +532,38 @@ mod cli_parser_tests {
532532
assert!(!cli.disable_anthropic_api);
533533
}
534534
}
535+
536+
mod support_docs_tests {
537+
use std::fs;
538+
539+
#[test]
540+
fn forgefed_docs_describe_public_actor_and_inbox() {
541+
let docs = fs::read_to_string("docs/forgefed.md").expect("forgefed docs should exist");
542+
543+
assert!(docs.contains("/actor/code"));
544+
assert!(docs.contains("/inbox/code"));
545+
assert!(docs.contains("https://forgefed.org/ns"));
546+
assert!(docs.contains("ACTIVITYPUB_ACTOR_BASE_URL"));
547+
}
548+
549+
#[test]
550+
fn akash_template_exposes_router_and_required_configuration() {
551+
let sdl = fs::read_to_string("deploy/akash/deploy.yaml").expect("akash SDL should exist");
552+
553+
assert!(sdl.contains("version: \"2.0\""));
554+
assert!(sdl.contains("port: 8080"));
555+
assert!(sdl.contains("TOKEN_SECRET=replace-with-secure-secret"));
556+
assert!(sdl.contains("ACTIVITYPUB_ACTOR_BASE_URL=https://router.example.com"));
557+
}
558+
559+
#[test]
560+
fn kubernetes_template_includes_deployment_service_and_health_probes() {
561+
let manifest =
562+
fs::read_to_string("deploy/k8s/router.yaml").expect("k8s manifest should exist");
563+
564+
assert!(manifest.contains("kind: Deployment"));
565+
assert!(manifest.contains("kind: Service"));
566+
assert!(manifest.contains("path: /health"));
567+
assert!(manifest.contains("ACTIVITYPUB_PUBLIC_KEY_PEM"));
568+
}
569+
}

0 commit comments

Comments
 (0)