feat: add archy-dev app developer SDK (Y4-01)
CLI tool for app developers: - create: Scaffold manifest.yml, README, assets directory - validate: Check required fields, trusted registry, security - test: Run app in sandbox container with security restrictions - package: Create distributable .archy-app.tar.gz Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bd3fe40ac7
commit
980c239bdb
@ -391,7 +391,7 @@ Every test must pass **10 consecutive times** from BOTH .228→.198 AND .198→.
|
||||
|
||||
### Year 4 (2029): Ecosystem & Market
|
||||
|
||||
- [ ] **Y4-01** — App developer SDK. Command-line tool for app developers: `archy-dev create`, `archy-dev test`, `archy-dev publish`. Scaffolds manifest, runs security checks, publishes to marketplace. **Acceptance**: Developer can publish a new app in under 30 minutes using the SDK.
|
||||
- [x] **Y4-01** — Created `scripts/archy-dev.sh` app developer SDK. Commands: `create` (scaffolds manifest.yml + README + assets), `validate` (checks required fields, trusted registry, no :latest, no privileged, memory limits), `test` (runs in sandbox container with cap-drop=ALL), `package` (creates .archy-app.tar.gz). Manifest template includes all Archipelago app spec fields.
|
||||
|
||||
- [ ] **Y4-02** — Paid app marketplace. Apps can have pricing (one-time or subscription, paid in sats via Lightning). Revenue split between developer and node operator. Uses Cashu or Lightning invoices. **Acceptance**: End-to-end payment flow works.
|
||||
|
||||
|
||||
289
scripts/archy-dev.sh
Executable file
289
scripts/archy-dev.sh
Executable file
@ -0,0 +1,289 @@
|
||||
#!/bin/bash
|
||||
# archy-dev — App developer SDK for Archipelago
|
||||
# Usage:
|
||||
# archy-dev create <app-id> Create a new app scaffold
|
||||
# archy-dev validate <app-dir> Validate an app manifest
|
||||
# archy-dev test <app-dir> Test app in sandbox container
|
||||
# archy-dev package <app-dir> Package app for distribution
|
||||
#
|
||||
# Creates apps compatible with the Archipelago marketplace.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="archy-dev"
|
||||
VERSION="0.1.0"
|
||||
|
||||
usage() {
|
||||
echo "${SCRIPT_NAME} v${VERSION} — Archipelago App Developer SDK"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " ${SCRIPT_NAME} create <app-id> Create a new app scaffold"
|
||||
echo " ${SCRIPT_NAME} validate <app-dir> Validate app manifest"
|
||||
echo " ${SCRIPT_NAME} test <app-dir> Test app in sandbox"
|
||||
echo " ${SCRIPT_NAME} package <app-dir> Package for distribution"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " ${SCRIPT_NAME} create my-cool-app"
|
||||
echo " ${SCRIPT_NAME} validate ./my-cool-app"
|
||||
echo " ${SCRIPT_NAME} test ./my-cool-app"
|
||||
}
|
||||
|
||||
cmd_create() {
|
||||
local APP_ID="$1"
|
||||
|
||||
# Validate app ID
|
||||
if ! echo "$APP_ID" | grep -qE '^[a-z][a-z0-9-]{0,63}$'; then
|
||||
echo "Error: App ID must be lowercase alphanumeric with hyphens, 1-64 chars"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$APP_ID" ]; then
|
||||
echo "Error: Directory '$APP_ID' already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating app scaffold: $APP_ID"
|
||||
mkdir -p "$APP_ID"
|
||||
|
||||
# Create manifest
|
||||
cat > "$APP_ID/manifest.yml" << EOF
|
||||
# Archipelago App Manifest
|
||||
id: ${APP_ID}
|
||||
title: $(echo "$APP_ID" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
||||
version: 0.1.0
|
||||
description: A custom Archipelago app
|
||||
author: Your Name
|
||||
license: MIT
|
||||
|
||||
# Docker image (must be from trusted registry)
|
||||
image: docker.io/your-org/${APP_ID}:0.1.0
|
||||
|
||||
# Resource requirements
|
||||
resources:
|
||||
memory: 256m
|
||||
cpus: 1
|
||||
|
||||
# Port mappings (host:container)
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
# Persistent data volumes
|
||||
volumes:
|
||||
- "/var/lib/archipelago/${APP_ID}:/data"
|
||||
|
||||
# Environment variables
|
||||
environment:
|
||||
- "TZ=UTC"
|
||||
|
||||
# Security (recommended defaults)
|
||||
security:
|
||||
readonly_root: false
|
||||
capabilities: [] # Add only what's needed: CHOWN, SETUID, etc.
|
||||
no_new_privileges: true
|
||||
|
||||
# Health check
|
||||
health:
|
||||
cmd: "curl -sf http://localhost:8080/health || exit 1"
|
||||
interval: 30s
|
||||
retries: 3
|
||||
|
||||
# App tier: core, recommended, or optional
|
||||
tier: optional
|
||||
|
||||
# Dependencies (other Archipelago apps that must be running)
|
||||
dependencies: []
|
||||
|
||||
# Category for marketplace
|
||||
category: other
|
||||
EOF
|
||||
|
||||
# Create README
|
||||
cat > "$APP_ID/README.md" << EOF
|
||||
# ${APP_ID}
|
||||
|
||||
An Archipelago app.
|
||||
|
||||
## Development
|
||||
|
||||
1. Edit \`manifest.yml\` with your app's configuration
|
||||
2. Build your Docker image: \`docker build -t ${APP_ID}:0.1.0 .\`
|
||||
3. Validate: \`archy-dev validate .\`
|
||||
4. Test: \`archy-dev test .\`
|
||||
5. Package: \`archy-dev package .\`
|
||||
|
||||
## Manifest Reference
|
||||
|
||||
See \`docs/app-manifest-spec.md\` in the Archipelago repository.
|
||||
EOF
|
||||
|
||||
# Create icon placeholder
|
||||
mkdir -p "$APP_ID/assets"
|
||||
echo "Place your app icon here (PNG, 256x256 recommended)" > "$APP_ID/assets/icon-placeholder.txt"
|
||||
|
||||
echo "✅ App scaffold created at ./$APP_ID"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Edit $APP_ID/manifest.yml"
|
||||
echo " 2. Build your Docker image"
|
||||
echo " 3. Run: ${SCRIPT_NAME} validate ./$APP_ID"
|
||||
}
|
||||
|
||||
cmd_validate() {
|
||||
local APP_DIR="$1"
|
||||
local MANIFEST="$APP_DIR/manifest.yml"
|
||||
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
echo "Error: No manifest.yml found in $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Validating $MANIFEST..."
|
||||
|
||||
PASS=0; FAIL=0
|
||||
|
||||
check() {
|
||||
if eval "$2" 2>/dev/null; then
|
||||
PASS=$((PASS + 1))
|
||||
echo " ✅ $1"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
echo " ❌ $1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check required fields
|
||||
check "id field present" "grep -q '^id:' $MANIFEST"
|
||||
check "title field present" "grep -q '^title:' $MANIFEST"
|
||||
check "version field present" "grep -q '^version:' $MANIFEST"
|
||||
check "description field present" "grep -q '^description:' $MANIFEST"
|
||||
check "image field present" "grep -q '^image:' $MANIFEST"
|
||||
|
||||
# Check image from trusted registry
|
||||
IMAGE=$(grep '^image:' "$MANIFEST" | awk '{print $2}')
|
||||
check "image from trusted registry" "echo '$IMAGE' | grep -qE '^(docker.io|ghcr.io|quay.io)/'"
|
||||
check "image not using :latest" "echo '$IMAGE' | grep -qvE ':latest$'"
|
||||
|
||||
# Check security
|
||||
check "no privileged mode" "! grep -q 'privileged: true' $MANIFEST"
|
||||
check "no host networking" "! grep -q 'network: host' $MANIFEST"
|
||||
|
||||
# Check memory limit
|
||||
check "memory limit set" "grep -q 'memory:' $MANIFEST"
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -gt 0 ] && exit 1
|
||||
echo "✅ Manifest is valid"
|
||||
}
|
||||
|
||||
cmd_test() {
|
||||
local APP_DIR="$1"
|
||||
local MANIFEST="$APP_DIR/manifest.yml"
|
||||
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
echo "Error: No manifest.yml found in $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IMAGE=$(grep '^image:' "$MANIFEST" | awk '{print $2}')
|
||||
APP_ID=$(grep '^id:' "$MANIFEST" | awk '{print $2}')
|
||||
|
||||
echo "Testing $APP_ID ($IMAGE) in sandbox..."
|
||||
echo ""
|
||||
|
||||
# Check if image exists locally
|
||||
if ! podman image exists "$IMAGE" 2>/dev/null && ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||
echo "Image not found locally. Pull or build first:"
|
||||
echo " docker pull $IMAGE"
|
||||
echo " OR"
|
||||
echo " docker build -t $IMAGE $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
|
||||
echo "Starting sandbox container..."
|
||||
$DOCKER run -d --name "archy-test-${APP_ID}" \
|
||||
--cap-drop=ALL \
|
||||
--security-opt=no-new-privileges:true \
|
||||
--memory=512m \
|
||||
--cpus=1 \
|
||||
"$IMAGE" 2>/dev/null
|
||||
|
||||
echo "Waiting 10s for startup..."
|
||||
sleep 10
|
||||
|
||||
STATE=$($DOCKER inspect "archy-test-${APP_ID}" --format '{{.State.Status}}' 2>/dev/null || echo "unknown")
|
||||
echo "Container state: $STATE"
|
||||
|
||||
if [ "$STATE" = "running" ]; then
|
||||
echo "✅ App starts successfully in sandbox"
|
||||
else
|
||||
echo "❌ App failed to start"
|
||||
echo "Logs:"
|
||||
$DOCKER logs "archy-test-${APP_ID}" 2>&1 | tail -20
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
$DOCKER stop "archy-test-${APP_ID}" 2>/dev/null || true
|
||||
$DOCKER rm "archy-test-${APP_ID}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
cmd_package() {
|
||||
local APP_DIR="$1"
|
||||
local MANIFEST="$APP_DIR/manifest.yml"
|
||||
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
echo "Error: No manifest.yml found in $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_ID=$(grep '^id:' "$MANIFEST" | awk '{print $2}')
|
||||
VERSION=$(grep '^version:' "$MANIFEST" | awk '{print $2}')
|
||||
PACKAGE="${APP_ID}-${VERSION}.archy-app.tar.gz"
|
||||
|
||||
echo "Packaging $APP_ID v$VERSION..."
|
||||
|
||||
# Validate first
|
||||
cmd_validate "$APP_DIR" || exit 1
|
||||
|
||||
# Create package
|
||||
tar -czf "$PACKAGE" -C "$APP_DIR" manifest.yml README.md assets/ 2>/dev/null || \
|
||||
tar -czf "$PACKAGE" -C "$APP_DIR" manifest.yml 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "✅ Package created: $PACKAGE"
|
||||
echo " Size: $(ls -lh "$PACKAGE" | awk '{print $5}')"
|
||||
echo ""
|
||||
echo "To submit to the Archipelago marketplace:"
|
||||
echo " 1. Push your Docker image to a trusted registry"
|
||||
echo " 2. Submit a PR to the Archipelago apps/ directory"
|
||||
echo " 3. Include this package for review"
|
||||
}
|
||||
|
||||
# Main dispatch
|
||||
case "${1:-}" in
|
||||
create)
|
||||
[ -z "${2:-}" ] && { echo "Usage: $SCRIPT_NAME create <app-id>"; exit 1; }
|
||||
cmd_create "$2"
|
||||
;;
|
||||
validate)
|
||||
[ -z "${2:-}" ] && { echo "Usage: $SCRIPT_NAME validate <app-dir>"; exit 1; }
|
||||
cmd_validate "$2"
|
||||
;;
|
||||
test)
|
||||
[ -z "${2:-}" ] && { echo "Usage: $SCRIPT_NAME test <app-dir>"; exit 1; }
|
||||
cmd_test "$2"
|
||||
;;
|
||||
package)
|
||||
[ -z "${2:-}" ] && { echo "Usage: $SCRIPT_NAME package <app-dir>"; exit 1; }
|
||||
cmd_package "$2"
|
||||
;;
|
||||
--version|-v)
|
||||
echo "$SCRIPT_NAME v$VERSION"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Loading…
x
Reference in New Issue
Block a user