Skill downloader - bash HTTP server using socat

This commit is contained in:
Azat
2026-02-03 00:18:55 +01:00
commit d8034a7dd0
3 changed files with 335 additions and 0 deletions

89
SKILL.md Normal file
View 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
View 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
View 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"