Skill downloader - bash HTTP server using socat
This commit is contained in:
89
SKILL.md
Normal file
89
SKILL.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: skill-downloader
|
||||
description: Download and install skills from git.vibe-overflow.com
|
||||
metadata:
|
||||
version: "1.0.0"
|
||||
vibestack:
|
||||
main: false
|
||||
---
|
||||
|
||||
# Skill Downloader
|
||||
|
||||
Downloads and installs skills from the VibeStack registry at `git.vibe-overflow.com/azat/`.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DOWNLOADER_PORT` | `8083` | API port |
|
||||
| `SKILLS_DIR` | `/skills` | Installation directory |
|
||||
| `GIT_HOST` | `git.vibe-overflow.com` | Git server |
|
||||
| `GIT_USER` | `azat` | Git user/org |
|
||||
| `GIT_TOKEN` | (required) | Auth token |
|
||||
|
||||
## API
|
||||
|
||||
### List Available Skills
|
||||
|
||||
```bash
|
||||
curl http://localhost:8083/available
|
||||
```
|
||||
|
||||
Returns all repos from `git.vibe-overflow.com/azat/`:
|
||||
```json
|
||||
{
|
||||
"skills": [
|
||||
{"name": "redis", "description": "Redis cache server"},
|
||||
{"name": "postgres", "description": "PostgreSQL database"},
|
||||
{"name": "backup", "description": "Backup and restore"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### List Installed Skills
|
||||
|
||||
```bash
|
||||
curl http://localhost:8083/installed
|
||||
```
|
||||
|
||||
### Install Skill
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8083/install/redis
|
||||
```
|
||||
|
||||
### Update Skill
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8083/update/redis
|
||||
```
|
||||
|
||||
### Remove Skill
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8083/remove/redis
|
||||
```
|
||||
|
||||
### Health
|
||||
|
||||
```bash
|
||||
curl http://localhost:8083/health
|
||||
```
|
||||
|
||||
## Installation Process
|
||||
|
||||
```
|
||||
POST /install/redis
|
||||
│
|
||||
▼
|
||||
git clone https://git.vibe-overflow.com/azat/redis /skills/redis
|
||||
│
|
||||
▼
|
||||
bash /skills/redis/scripts/autorun.sh
|
||||
│
|
||||
▼
|
||||
supervisorctl reread && update
|
||||
│
|
||||
▼
|
||||
{"success": true, "name": "redis"}
|
||||
```
|
||||
24
scripts/autorun.sh
Normal file
24
scripts/autorun.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Install dependencies
|
||||
install_deps() {
|
||||
local missing=""
|
||||
|
||||
command -v git &>/dev/null || missing="$missing git"
|
||||
command -v curl &>/dev/null || missing="$missing curl"
|
||||
command -v jq &>/dev/null || missing="$missing jq"
|
||||
command -v socat &>/dev/null || missing="$missing socat"
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
echo "Installing:$missing"
|
||||
apt-get update
|
||||
apt-get install -y $missing
|
||||
fi
|
||||
|
||||
echo "Dependencies ready"
|
||||
}
|
||||
|
||||
install_deps
|
||||
|
||||
echo "Skill downloader setup complete"
|
||||
222
scripts/run.sh
Normal file
222
scripts/run.sh
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SKILL_DIR="$(dirname "$(dirname "$0")")"
|
||||
DOWNLOADER_PORT="${DOWNLOADER_PORT:-8083}"
|
||||
SKILLS_DIR="${SKILLS_DIR:-/skills}"
|
||||
GIT_HOST="${GIT_HOST:-git.vibe-overflow.com}"
|
||||
GIT_USER="${GIT_USER:-azat}"
|
||||
GIT_TOKEN="${GIT_TOKEN:-}"
|
||||
|
||||
# Create HTTP handler
|
||||
create_handler() {
|
||||
cat > /tmp/downloader_handler.sh << 'HANDLER'
|
||||
#!/bin/bash
|
||||
|
||||
SKILLS_DIR="${SKILLS_DIR:-/skills}"
|
||||
GIT_HOST="${GIT_HOST:-git.vibe-overflow.com}"
|
||||
GIT_USER="${GIT_USER:-azat}"
|
||||
GIT_TOKEN="${GIT_TOKEN:-}"
|
||||
|
||||
# Read HTTP request
|
||||
read -r request_line
|
||||
method=$(echo "$request_line" | cut -d' ' -f1)
|
||||
path=$(echo "$request_line" | cut -d' ' -f2)
|
||||
|
||||
# Read headers
|
||||
content_length=0
|
||||
while read -r header; do
|
||||
header=$(echo "$header" | tr -d '\r')
|
||||
[ -z "$header" ] && break
|
||||
if [[ "$header" =~ ^[Cc]ontent-[Ll]ength:\ *([0-9]+) ]]; then
|
||||
content_length="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Read body if present
|
||||
body=""
|
||||
if [ "$content_length" -gt 0 ]; then
|
||||
body=$(head -c "$content_length")
|
||||
fi
|
||||
|
||||
# Response helper
|
||||
send_response() {
|
||||
local status="$1"
|
||||
local body="$2"
|
||||
local body_length=${#body}
|
||||
|
||||
printf "HTTP/1.1 %s\r\n" "$status"
|
||||
printf "Content-Type: application/json\r\n"
|
||||
printf "Content-Length: %d\r\n" "$body_length"
|
||||
printf "Access-Control-Allow-Origin: *\r\n"
|
||||
printf "Connection: close\r\n"
|
||||
printf "\r\n"
|
||||
printf "%s" "$body"
|
||||
}
|
||||
|
||||
# Git URL with token
|
||||
git_url() {
|
||||
local repo="$1"
|
||||
if [ -n "$GIT_TOKEN" ]; then
|
||||
echo "https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_USER}/${repo}.git"
|
||||
else
|
||||
echo "https://${GIT_HOST}/${GIT_USER}/${repo}.git"
|
||||
fi
|
||||
}
|
||||
|
||||
# Health check
|
||||
if [ "$path" = "/health" ]; then
|
||||
send_response "200 OK" '{"status":"ok"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# List available skills from Gitea API
|
||||
if [ "$path" = "/available" ] && [ "$method" = "GET" ]; then
|
||||
api_url="https://${GIT_HOST}/api/v1/users/${GIT_USER}/repos"
|
||||
|
||||
if [ -n "$GIT_TOKEN" ]; then
|
||||
repos=$(curl -s -H "Authorization: token $GIT_TOKEN" "$api_url")
|
||||
else
|
||||
repos=$(curl -s "$api_url")
|
||||
fi
|
||||
|
||||
# Transform to our format
|
||||
skills=$(echo "$repos" | jq '[.[] | {name: .name, description: .description, url: .html_url}]')
|
||||
send_response "200 OK" "{\"skills\":$skills}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# List installed skills
|
||||
if [ "$path" = "/installed" ] && [ "$method" = "GET" ]; then
|
||||
skills="[]"
|
||||
for skill_dir in "$SKILLS_DIR"/*/; do
|
||||
[ -d "$skill_dir" ] || continue
|
||||
skill_name=$(basename "$skill_dir")
|
||||
[ "$skill_name" = "*" ] && continue
|
||||
|
||||
skill_md="$skill_dir/SKILL.md"
|
||||
if [ -f "$skill_md" ]; then
|
||||
desc=$(sed -n '/^description:/s/description: *//p' "$skill_md" | head -1)
|
||||
skills=$(echo "$skills" | jq --arg name "$skill_name" --arg desc "$desc" '. + [{"name":$name,"description":$desc}]')
|
||||
else
|
||||
skills=$(echo "$skills" | jq --arg name "$skill_name" '. + [{"name":$name,"description":""}]')
|
||||
fi
|
||||
done
|
||||
send_response "200 OK" "{\"skills\":$skills}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install skill: POST /install/{name}
|
||||
if [[ "$path" =~ ^/install/([a-zA-Z0-9_-]+)$ ]] && [ "$method" = "POST" ]; then
|
||||
skill_name="${BASH_REMATCH[1]}"
|
||||
skill_path="$SKILLS_DIR/$skill_name"
|
||||
|
||||
# Check if already installed
|
||||
if [ -d "$skill_path" ]; then
|
||||
send_response "409 Conflict" "{\"success\":false,\"error\":\"Skill already installed\",\"code\":\"ALREADY_EXISTS\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Clone
|
||||
url=$(git_url "$skill_name")
|
||||
if ! git clone "$url" "$skill_path" 2>&1; then
|
||||
send_response "404 Not Found" "{\"success\":false,\"error\":\"Failed to clone repository\",\"code\":\"CLONE_FAILED\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run autorun.sh if exists
|
||||
autorun="$skill_path/scripts/autorun.sh"
|
||||
if [ -f "$autorun" ]; then
|
||||
chmod +x "$autorun"
|
||||
if ! bash "$autorun" 2>&1; then
|
||||
send_response "500 Internal Server Error" "{\"success\":false,\"error\":\"Autorun failed\",\"code\":\"AUTORUN_FAILED\"}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Make run.sh executable
|
||||
run_script="$skill_path/scripts/run.sh"
|
||||
[ -f "$run_script" ] && chmod +x "$run_script"
|
||||
|
||||
# Notify supervisor
|
||||
supervisorctl reread 2>/dev/null || true
|
||||
supervisorctl update 2>/dev/null || true
|
||||
|
||||
send_response "200 OK" "{\"success\":true,\"name\":\"$skill_name\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update skill: POST /update/{name}
|
||||
if [[ "$path" =~ ^/update/([a-zA-Z0-9_-]+)$ ]] && [ "$method" = "POST" ]; then
|
||||
skill_name="${BASH_REMATCH[1]}"
|
||||
skill_path="$SKILLS_DIR/$skill_name"
|
||||
|
||||
if [ ! -d "$skill_path" ]; then
|
||||
send_response "404 Not Found" "{\"success\":false,\"error\":\"Skill not installed\",\"code\":\"NOT_FOUND\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Git pull
|
||||
cd "$skill_path"
|
||||
if ! git pull 2>&1; then
|
||||
send_response "500 Internal Server Error" "{\"success\":false,\"error\":\"Git pull failed\",\"code\":\"UPDATE_FAILED\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Rerun autorun.sh
|
||||
autorun="$skill_path/scripts/autorun.sh"
|
||||
if [ -f "$autorun" ]; then
|
||||
chmod +x "$autorun"
|
||||
bash "$autorun" 2>&1 || true
|
||||
fi
|
||||
|
||||
# Restart skill
|
||||
supervisorctl restart "$skill_name" 2>/dev/null || true
|
||||
|
||||
send_response "200 OK" "{\"success\":true,\"name\":\"$skill_name\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Remove skill: DELETE /remove/{name}
|
||||
if [[ "$path" =~ ^/remove/([a-zA-Z0-9_-]+)$ ]] && [ "$method" = "DELETE" ]; then
|
||||
skill_name="${BASH_REMATCH[1]}"
|
||||
skill_path="$SKILLS_DIR/$skill_name"
|
||||
|
||||
if [ ! -d "$skill_path" ]; then
|
||||
send_response "404 Not Found" "{\"success\":false,\"error\":\"Skill not installed\",\"code\":\"NOT_FOUND\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stop skill
|
||||
supervisorctl stop "$skill_name" 2>/dev/null || true
|
||||
|
||||
# Remove directory
|
||||
rm -rf "$skill_path"
|
||||
|
||||
# Update supervisor
|
||||
supervisorctl reread 2>/dev/null || true
|
||||
supervisorctl update 2>/dev/null || true
|
||||
|
||||
send_response "200 OK" "{\"success\":true,\"name\":\"$skill_name\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Not found
|
||||
send_response "404 Not Found" '{"error":"Endpoints: GET /available, GET /installed, POST /install/{name}, POST /update/{name}, DELETE /remove/{name}, GET /health"}'
|
||||
HANDLER
|
||||
|
||||
chmod +x /tmp/downloader_handler.sh
|
||||
|
||||
# Export vars for handler
|
||||
export SKILLS_DIR
|
||||
export GIT_HOST
|
||||
export GIT_USER
|
||||
export GIT_TOKEN
|
||||
}
|
||||
|
||||
echo "Starting Skill Downloader on port $DOWNLOADER_PORT..."
|
||||
echo "Skills directory: $SKILLS_DIR"
|
||||
echo "Registry: https://$GIT_HOST/$GIT_USER"
|
||||
|
||||
create_handler
|
||||
exec socat TCP-LISTEN:$DOWNLOADER_PORT,reuseaddr,fork EXEC:"/tmp/downloader_handler.sh"
|
||||
Reference in New Issue
Block a user