# 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