archy/docs/app-developer-guide.md

278 lines
7.8 KiB
Markdown
Raw Normal View History

# Archipelago App Developer Guide
Build and publish containerized apps for the Archipelago ecosystem.
## Overview
Apps run as Podman containers on user nodes. You publish app manifests to Nostr relays, where nodes discover and install them through the community marketplace.
## App Manifest
Every app needs a manifest (YAML for local apps, JSON for marketplace publishing).
### Template Manifest
```yaml
# apps/my-app/manifest.yml
app:
id: my-app # Unique, lowercase kebab-case
name: My App
version: 1.0.0 # Semantic versioning
container:
image: docker.io/myorg/my-app:1.0.0 # Never use :latest
ports:
- container: 8080
host: 8180
protocol: tcp
volumes:
- name: data
path: /data
env:
APP_MODE: production
capabilities: [] # Only add if absolutely necessary
readonly_root: true # Required
no_new_privileges: true # Required
run_as_user: 1000 # Must be >= 1000
metadata:
description:
short: "One-line description (max 120 chars)"
long: "Detailed description of what this app does and why."
author:
name: "Your Name"
did: "did:key:z6Mk..." # Your Archipelago node DID
category: money # money | commerce | data | networking | home | community | other
icon_url: "https://example.com/icon.png"
repo_url: "https://github.com/myorg/my-app"
license: MIT
min_archipelago_version: "0.1.0"
dependencies: [] # e.g., ["bitcoin-knots"] if this app needs Bitcoin
```
### Required Fields
| Field | Description |
|-------|-------------|
| `app.id` | Unique identifier, lowercase, kebab-case only |
| `app.name` | Human-readable name |
| `app.version` | Semantic version (major.minor.patch) |
| `container.image` | Full image reference with pinned version tag |
| `metadata.description.short` | One-line description, max 120 characters |
| `metadata.author.did` | Your node's DID (get via `node.did` RPC) |
## Security Requirements
These are enforced by the marketplace and the node. Non-compliant apps are flagged.
### Mandatory
1. **No `:latest` tag** — Pin a specific version: `myapp:1.0.0`
2. **Read-only root filesystem**`readonly_root: true` (use volumes for writable data)
3. **Non-root user**`run_as_user: 1000` or higher
4. **No privilege escalation**`no_new_privileges: true`
5. **Minimal capabilities** — Drop all caps, only add required ones
### Allowed Capabilities
Only these Linux capabilities may be requested:
| Capability | When Needed |
|-----------|-------------|
| `CHOWN` | App needs to change file ownership |
| `NET_BIND_SERVICE` | App binds to ports below 1024 |
| `DAC_OVERRIDE` | App needs to bypass file permissions |
| `SETUID`, `SETGID` | App manages user switching (e.g., nginx) |
### Forbidden
- `--network host` — Apps cannot share the host network
- Mounting system paths: `/`, `/etc`, `/var`, `/usr`, `/proc`, `/sys`
- `SYS_ADMIN`, `SYS_PTRACE`, or any privileged capability
- Hardcoded secrets in environment variables or images
## Container Best Practices
### Volumes
```yaml
volumes:
- name: data # App data persists across restarts
path: /data
- name: config # Configuration files
path: /config
```
Data is stored at `/var/lib/archipelago/{app-id}/` on the host.
### Health Checks
Define a health check endpoint in your container:
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
```
### Logging
- Log to stdout/stderr (Podman captures container logs)
- Never log secrets, passwords, or keys
- Use structured logging (JSON) for machine parsing
### Networking
Apps get their own network namespace. To connect to other Archipelago apps:
```yaml
# If your app needs to talk to Bitcoin
dependencies:
- bitcoin-knots
container:
env:
BITCOIN_RPC_HOST: bitcoin-knots # Container DNS name on archy-net
BITCOIN_RPC_PORT: "8332"
```
The `archy-net` Podman network provides DNS resolution between containers.
## Publishing to the Marketplace
### 1. Build and Push Your Image
```bash
podman build -t docker.io/myorg/my-app:1.0.0 .
podman push docker.io/myorg/my-app:1.0.0
```
### 2. Get Your Node's DID
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-d '{"method":"node.did"}'
# Returns: {"result":{"did":"did:key:z6Mk..."}}
```
### 3. Publish via RPC
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-H "Content-Type: application/json" \
-d '{
"method": "marketplace.publish",
"params": {
"app_id": "my-app",
"name": "My App",
"version": "1.0.0",
"description": {"short": "A useful tool", "long": "Detailed description..."},
"author": {"name": "Dev Name", "did": "did:key:z6Mk...", "nostr_pubkey": ""},
"container": {
"image": "docker.io/myorg/my-app:1.0.0",
"ports": [{"container": 8080, "host": 8180, "protocol": "tcp"}],
"volumes": [],
"env": {},
"capabilities": [],
"readonly_root": true,
"no_new_privileges": true,
"run_as_user": 1000
},
"category": "other",
"icon_url": "",
"repo_url": "https://github.com/myorg/my-app",
"license": "MIT",
"min_archipelago_version": "0.1.0",
"dependencies": []
}
}'
```
The manifest is published to all configured Nostr relays as a NIP-78 event (kind 30078).
### 4. Verify Discovery
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-d '{"method":"marketplace.discover"}'
# Your app should appear in the results
```
## Trust Model
Published apps receive trust scores (0-100) based on:
| Factor | Points | How to Maximize |
|--------|--------|-----------------|
| Valid DID in author | 30 | Always include your node's DID |
| Found on multiple relays | 5-20 | Configure many relays in your node |
| Developer in federation | 20 | Have federated peers who trust you |
| Proper semver version | 10 | Use `major.minor.patch` format |
| Repository URL present | 5 | Include your repo URL |
| Security compliance | 15 | Meet all security requirements |
### Trust Tiers
| Score | Tier | User Experience |
|-------|------|----------------|
| 80-100 | Verified | One-click install |
| 50-79 | Community | Install with confirmation |
| 20-49 | Unverified | Install with warning |
| 0-19 | Untrusted | Requires explicit override |
## Testing Your App
### Local Testing
```bash
# Run your container locally
podman run -d --name my-app \
-p 8180:8080 \
--read-only \
--security-opt no-new-privileges \
--user 1000:1000 \
docker.io/myorg/my-app:1.0.0
# Verify it works
curl http://localhost:8180/health
# Check logs
podman logs my-app
```
### On an Archipelago Node
1. Install via the marketplace UI or RPC:
```bash
curl -b cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-d '{"method":"package.install","params":{"id":"my-app","dockerImage":"docker.io/myorg/my-app:1.0.0"}}'
```
2. Verify the container is running:
```bash
curl -b cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-d '{"method":"container-list"}'
```
3. Check the UI at `http://192.168.1.228/app/my-app/`
### Validate Manifest
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"marketplace.verify","params":{...your manifest...}}'
# Returns: {"result":{"valid":true,"issues":[],"trust_score":65,"trust_tier":"community"}}
```
## Updating Your App
1. Build and push the new version: `docker.io/myorg/my-app:1.1.0`
2. Publish an updated manifest with the new version
3. NIP-33 replaceable events: the latest publish overwrites the previous one on relays
4. Nodes running your app can see the update in their marketplace
## App Icon
- Provide a URL to your app icon (PNG, WebP, or SVG)
- Recommended size: 256x256 pixels
- Square aspect ratio
- If no icon URL, a generic placeholder is shown in the marketplace