IPFS pinning can take a long time if content needs to be fetched from the network. Changed all pin operations to run in background threads so they don't block the HTTP response. This fixes the 30s timeout issue when publishing assets. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Art DAG L2 Server - ActivityPub
Ownership registry and ActivityPub federation for Art DAG.
What it does
- Registry: Maintains owned assets with content hashes
- Activities: Creates signed ownership claims (Create activities)
- Federation: ActivityPub endpoints for follow/share
- L1 Integration: Records completed L1 runs as owned assets
- Authentication: User registration, login, JWT tokens
Setup
pip install -r requirements.txt
# Configure (optional - defaults shown)
export ARTDAG_DOMAIN=artdag.rose-ash.com
export ARTDAG_USER=giles
export ARTDAG_DATA=~/.artdag/l2
export DATABASE_URL=postgresql://artdag:artdag@localhost:5432/artdag
# Generate signing keys (required for federation)
python setup_keys.py
# Start server
python server.py
JWT Secret Configuration
The JWT secret is used to sign authentication tokens. Without a persistent secret, tokens are invalidated on server restart.
Generate a secret
# Generate a 64-character hex secret
openssl rand -hex 32
# Or with Python
python -c "import secrets; print(secrets.token_hex(32))"
Local development
export JWT_SECRET="your-generated-secret-here"
python server.py
Docker Swarm (recommended for production)
Create a Docker secret:
# From a generated value
openssl rand -hex 32 | docker secret create jwt_secret -
# Or from a file
echo "your-secret-here" > jwt_secret.txt
docker secret create jwt_secret jwt_secret.txt
rm jwt_secret.txt
Reference in docker-compose.yml:
services:
l2-server:
secrets:
- jwt_secret
secrets:
jwt_secret:
external: true
The server reads secrets from /run/secrets/jwt_secret automatically.
Key Setup
ActivityPub requires RSA keys for signing activities. Generate them:
# Local
python setup_keys.py
# Or with custom paths
python setup_keys.py --data-dir /data/l2 --user giles
# In Docker, exec into container or mount volume
docker exec -it <container> python setup_keys.py
Keys are stored in $ARTDAG_DATA/keys/:
{username}.pem- Private key (chmod 600, NEVER share){username}.pub- Public key (included in actor profile)
Important: Private keys are gitignored. Back them up securely. Losing them invalidates all your signatures.
Client Commands
Upload Media
Register a media asset (image, video, audio) with a content hash:
curl -X POST http://localhost:8200/assets \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"name": "my-video",
"content_hash": "abc123...",
"asset_type": "video",
"tags": ["art", "generated"]
}'
Upload Recipe
Record an L1 run as an owned asset. This fetches the run details from the L1 server and registers the output:
curl -X POST http://localhost:8200/assets/record-run \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"run_id": "uuid-from-l1",
"l1_server": "https://celery-artdag.rose-ash.com",
"output_name": "my-rendered-video"
}'
API Endpoints
Server Info
| Method | Path | Description |
|---|---|---|
| GET | / |
Home page with stats |
Assets
| Method | Path | Description |
|---|---|---|
| GET | /assets |
List all assets |
| GET | /assets/{name} |
Get asset by name |
| POST | /assets |
Upload media - register new asset |
| POST | /assets/record-run |
Upload recipe - record L1 run |
ActivityPub
| Method | Path | Description |
|---|---|---|
| GET | /.well-known/webfinger?resource=acct:user@domain |
Actor discovery |
| GET | /users/{username} |
Actor profile |
| GET | /users/{username}/outbox |
Published activities |
| POST | /users/{username}/inbox |
Receive activities |
| GET | /users/{username}/followers |
Followers list |
| GET | /objects/{content_hash} |
Get object by hash |
| GET | /activities/{index} |
Get activity by index |
Authentication
| Method | Path | Description |
|---|---|---|
| POST | /auth/register |
Register new user |
| POST | /auth/login |
Login, get JWT token |
| GET | /auth/me |
Get current user |
Data Storage
Data stored in PostgreSQL:
users- Registered usersassets- Asset registryactivities- Signed activitiesfollowers- Followers list
RSA keys stored in $ARTDAG_DATA/keys/ (files, not database).
Architecture
L2 Server (port 8200)
│
├── POST /assets (upload media) → Register asset → Create activity → Sign
│
├── POST /assets/record-run (upload recipe) → Fetch L1 run → Register output
│ │
│ └── GET L1_SERVER/runs/{id}
│
├── GET /users/{user}/outbox → Return signed activities
│
└── POST /users/{user}/inbox → Receive Follow requests