2026-01-09 17:40:38 +00:00
2026-01-09 11:16:57 +00:00
2026-01-07 17:16:05 +00:00
2026-01-07 12:04:58 +00:00

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
export L1_SERVERS=https://celery-artdag.rose-ash.com

# 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

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.

L1 Renderers Configuration

The L1_SERVERS environment variable defines which L1 rendering servers are available to users. Users can attach to these servers from the Renderers page to run effects and manage media.

# Single server (default)
export L1_SERVERS=https://celery-artdag.rose-ash.com

# Multiple servers (comma-separated)
export L1_SERVERS=https://celery-artdag.rose-ash.com,https://renderer2.example.com,https://renderer3.example.com

When a user attaches to an L1 server:

  1. They're redirected to the L1's /auth endpoint with their auth token
  2. L1 calls back to L2's /auth/verify endpoint to validate the token
  3. L1 sets its own local cookie, logging the user in
  4. Their attachment is recorded in the user_renderers table

No shared secrets required: L1 servers verify tokens by calling L2's public /auth/verify endpoint. This allows any L1 provider to federate with L2 without needing the JWT secret.

Users can manage attachments at /renderers.

Web UI

The server provides a web interface:

Path Description
/ Home page with stats and README
/assets Browse registered assets
/activities View published activities
/users List registered users
/renderers Manage L1 renderer connections
/asset/{name} Asset detail page
/login Login page
/register Registration page

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 users
  • assets - Asset registry
  • activities - Signed activities
  • followers - 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
Description
No description provided
Readme 2.2 MiB
Languages
Python 91.3%
HTML 8.4%
Dockerfile 0.2%
Shell 0.1%