# 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 ```bash 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 ```bash # Generate a 64-character hex secret openssl rand -hex 32 # Or with Python python -c "import secrets; print(secrets.token_hex(32))" ``` ### Local development ```bash export JWT_SECRET="your-generated-secret-here" python server.py ``` ### Docker Swarm (recommended for production) Create a Docker secret: ```bash # 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: ```yaml 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: ```bash # 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 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. ```bash # 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 `/auth/verify` endpoint. This allows any L1 provider to federate with L2 without needing the JWT secret. **Authorization**: L1 servers must identify themselves when calling `/auth/verify` by passing their URL. Only servers listed in `L1_SERVERS` are authorized to verify tokens. 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: ```bash curl -X POST http://localhost:8200/assets \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -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: ```bash curl -X POST http://localhost:8200/assets/record-run \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -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 ```