mcp-rubber-duck 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +19 -0
- package/.env.desktop.example +145 -0
- package/.env.example +45 -0
- package/.env.pi.example +106 -0
- package/.env.template +165 -0
- package/.eslintrc.json +40 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +65 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +58 -0
- package/.github/ISSUE_TEMPLATE/question.md +67 -0
- package/.github/pull_request_template.md +111 -0
- package/.github/workflows/docker-build.yml +138 -0
- package/.github/workflows/release.yml +182 -0
- package/.github/workflows/security.yml +141 -0
- package/.github/workflows/semantic-release.yml +89 -0
- package/.prettierrc +10 -0
- package/.releaserc.json +66 -0
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +242 -0
- package/Dockerfile +62 -0
- package/LICENSE +21 -0
- package/README.md +803 -0
- package/audit-ci.json +8 -0
- package/config/claude_desktop.json +14 -0
- package/config/config.example.json +91 -0
- package/dist/config/config.d.ts +51 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +301 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/types.d.ts +356 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +41 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/duck-provider-enhanced.d.ts +29 -0
- package/dist/providers/duck-provider-enhanced.d.ts.map +1 -0
- package/dist/providers/duck-provider-enhanced.js +230 -0
- package/dist/providers/duck-provider-enhanced.js.map +1 -0
- package/dist/providers/enhanced-manager.d.ts +54 -0
- package/dist/providers/enhanced-manager.d.ts.map +1 -0
- package/dist/providers/enhanced-manager.js +217 -0
- package/dist/providers/enhanced-manager.js.map +1 -0
- package/dist/providers/manager.d.ts +28 -0
- package/dist/providers/manager.d.ts.map +1 -0
- package/dist/providers/manager.js +204 -0
- package/dist/providers/manager.js.map +1 -0
- package/dist/providers/provider.d.ts +29 -0
- package/dist/providers/provider.d.ts.map +1 -0
- package/dist/providers/provider.js +179 -0
- package/dist/providers/provider.js.map +1 -0
- package/dist/providers/types.d.ts +69 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +501 -0
- package/dist/server.js.map +1 -0
- package/dist/services/approval.d.ts +44 -0
- package/dist/services/approval.d.ts.map +1 -0
- package/dist/services/approval.js +159 -0
- package/dist/services/approval.js.map +1 -0
- package/dist/services/cache.d.ts +21 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +63 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/conversation.d.ts +24 -0
- package/dist/services/conversation.d.ts.map +1 -0
- package/dist/services/conversation.js +108 -0
- package/dist/services/conversation.js.map +1 -0
- package/dist/services/function-bridge.d.ts +41 -0
- package/dist/services/function-bridge.d.ts.map +1 -0
- package/dist/services/function-bridge.js +259 -0
- package/dist/services/function-bridge.js.map +1 -0
- package/dist/services/health.d.ts +17 -0
- package/dist/services/health.d.ts.map +1 -0
- package/dist/services/health.js +77 -0
- package/dist/services/health.js.map +1 -0
- package/dist/services/mcp-client-manager.d.ts +49 -0
- package/dist/services/mcp-client-manager.d.ts.map +1 -0
- package/dist/services/mcp-client-manager.js +279 -0
- package/dist/services/mcp-client-manager.js.map +1 -0
- package/dist/tools/approve-mcp-request.d.ts +9 -0
- package/dist/tools/approve-mcp-request.d.ts.map +1 -0
- package/dist/tools/approve-mcp-request.js +111 -0
- package/dist/tools/approve-mcp-request.js.map +1 -0
- package/dist/tools/ask-duck.d.ts +9 -0
- package/dist/tools/ask-duck.d.ts.map +1 -0
- package/dist/tools/ask-duck.js +43 -0
- package/dist/tools/ask-duck.js.map +1 -0
- package/dist/tools/chat-duck.d.ts +9 -0
- package/dist/tools/chat-duck.d.ts.map +1 -0
- package/dist/tools/chat-duck.js +57 -0
- package/dist/tools/chat-duck.js.map +1 -0
- package/dist/tools/clear-conversations.d.ts +8 -0
- package/dist/tools/clear-conversations.d.ts.map +1 -0
- package/dist/tools/clear-conversations.js +17 -0
- package/dist/tools/clear-conversations.js.map +1 -0
- package/dist/tools/compare-ducks.d.ts +8 -0
- package/dist/tools/compare-ducks.d.ts.map +1 -0
- package/dist/tools/compare-ducks.js +49 -0
- package/dist/tools/compare-ducks.js.map +1 -0
- package/dist/tools/duck-council.d.ts +8 -0
- package/dist/tools/duck-council.d.ts.map +1 -0
- package/dist/tools/duck-council.js +69 -0
- package/dist/tools/duck-council.js.map +1 -0
- package/dist/tools/get-pending-approvals.d.ts +15 -0
- package/dist/tools/get-pending-approvals.d.ts.map +1 -0
- package/dist/tools/get-pending-approvals.js +74 -0
- package/dist/tools/get-pending-approvals.js.map +1 -0
- package/dist/tools/list-ducks.d.ts +9 -0
- package/dist/tools/list-ducks.d.ts.map +1 -0
- package/dist/tools/list-ducks.js +47 -0
- package/dist/tools/list-ducks.js.map +1 -0
- package/dist/tools/list-models.d.ts +8 -0
- package/dist/tools/list-models.d.ts.map +1 -0
- package/dist/tools/list-models.js +72 -0
- package/dist/tools/list-models.js.map +1 -0
- package/dist/tools/mcp-status.d.ts +17 -0
- package/dist/tools/mcp-status.d.ts.map +1 -0
- package/dist/tools/mcp-status.js +100 -0
- package/dist/tools/mcp-status.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +19 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +73 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +86 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/safe-logger.d.ts +23 -0
- package/dist/utils/safe-logger.d.ts.map +1 -0
- package/dist/utils/safe-logger.js +145 -0
- package/dist/utils/safe-logger.js.map +1 -0
- package/docker-compose.yml +161 -0
- package/jest.config.js +26 -0
- package/package.json +65 -0
- package/scripts/build-multiarch.sh +290 -0
- package/scripts/deploy-raspbian.sh +410 -0
- package/scripts/deploy.sh +322 -0
- package/scripts/gh-deploy.sh +343 -0
- package/scripts/setup-docker-raspbian.sh +530 -0
- package/server.json +8 -0
- package/src/config/config.ts +357 -0
- package/src/config/types.ts +89 -0
- package/src/index.ts +114 -0
- package/src/providers/duck-provider-enhanced.ts +294 -0
- package/src/providers/enhanced-manager.ts +290 -0
- package/src/providers/manager.ts +257 -0
- package/src/providers/provider.ts +207 -0
- package/src/providers/types.ts +78 -0
- package/src/server.ts +603 -0
- package/src/services/approval.ts +225 -0
- package/src/services/cache.ts +79 -0
- package/src/services/conversation.ts +146 -0
- package/src/services/function-bridge.ts +329 -0
- package/src/services/health.ts +107 -0
- package/src/services/mcp-client-manager.ts +362 -0
- package/src/tools/approve-mcp-request.ts +126 -0
- package/src/tools/ask-duck.ts +74 -0
- package/src/tools/chat-duck.ts +82 -0
- package/src/tools/clear-conversations.ts +24 -0
- package/src/tools/compare-ducks.ts +67 -0
- package/src/tools/duck-council.ts +88 -0
- package/src/tools/get-pending-approvals.ts +90 -0
- package/src/tools/list-ducks.ts +65 -0
- package/src/tools/list-models.ts +101 -0
- package/src/tools/mcp-status.ts +117 -0
- package/src/utils/ascii-art.ts +85 -0
- package/src/utils/logger.ts +116 -0
- package/src/utils/safe-logger.ts +165 -0
- package/systemd/mcp-rubber-duck-with-ollama.service +55 -0
- package/systemd/mcp-rubber-duck.service +58 -0
- package/test-functionality.js +147 -0
- package/test-mcp-interface.js +221 -0
- package/tests/ascii-art.test.ts +36 -0
- package/tests/config.test.ts +239 -0
- package/tests/conversation.test.ts +308 -0
- package/tests/mcp-bridge.test.ts +291 -0
- package/tests/providers.test.ts +269 -0
- package/tests/tools/clear-conversations.test.ts +163 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# MCP Rubber Duck - Universal Deployment Script
|
|
4
|
+
# Works on macOS, Linux, Windows (WSL), and Raspberry Pi
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Colors for output
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Configuration
|
|
16
|
+
DEPLOYMENT_MODE="${DEPLOYMENT_MODE:-auto}"
|
|
17
|
+
PLATFORM="${PLATFORM:-auto}"
|
|
18
|
+
|
|
19
|
+
# Functions
|
|
20
|
+
log() {
|
|
21
|
+
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
warn() {
|
|
25
|
+
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
error() {
|
|
29
|
+
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
|
30
|
+
exit 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
info() {
|
|
34
|
+
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
print_usage() {
|
|
38
|
+
cat << EOF
|
|
39
|
+
Usage: $0 [OPTIONS]
|
|
40
|
+
|
|
41
|
+
Universal deployment script for MCP Rubber Duck
|
|
42
|
+
Works on: macOS, Linux, Windows (WSL), Raspberry Pi
|
|
43
|
+
|
|
44
|
+
OPTIONS:
|
|
45
|
+
-m, --mode MODE Deployment mode: local, docker, ssh
|
|
46
|
+
-p, --platform PLATFORM Target platform: pi, desktop, auto
|
|
47
|
+
--profile PROFILE Docker compose profile: lightweight, desktop, with-ollama
|
|
48
|
+
--ssh-host HOST SSH host for remote deployment
|
|
49
|
+
--dry-run Show what would be done
|
|
50
|
+
-h, --help Show this help message
|
|
51
|
+
|
|
52
|
+
DEPLOYMENT MODES:
|
|
53
|
+
local - Run from source code (npm start)
|
|
54
|
+
docker - Use Docker containers (recommended)
|
|
55
|
+
ssh - Deploy via SSH to remote host
|
|
56
|
+
|
|
57
|
+
PLATFORMS:
|
|
58
|
+
pi - Raspberry Pi (optimized for low memory)
|
|
59
|
+
desktop - Desktop/server (higher resource limits)
|
|
60
|
+
auto - Auto-detect platform
|
|
61
|
+
|
|
62
|
+
EXAMPLES:
|
|
63
|
+
# Auto-detect platform and deploy with Docker
|
|
64
|
+
$0
|
|
65
|
+
|
|
66
|
+
# Deploy to Raspberry Pi with lightweight profile
|
|
67
|
+
$0 --platform pi --profile lightweight
|
|
68
|
+
|
|
69
|
+
# Deploy to desktop with Ollama support
|
|
70
|
+
$0 --platform desktop --profile with-ollama
|
|
71
|
+
|
|
72
|
+
# Deploy to remote Raspberry Pi via SSH
|
|
73
|
+
$0 --mode ssh --ssh-host pi@192.168.1.100
|
|
74
|
+
|
|
75
|
+
EOF
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Detect platform
|
|
79
|
+
detect_platform() {
|
|
80
|
+
if [[ "$PLATFORM" != "auto" ]]; then
|
|
81
|
+
echo "$PLATFORM"
|
|
82
|
+
return
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Check system info
|
|
86
|
+
local arch=$(uname -m)
|
|
87
|
+
local system=$(uname -s)
|
|
88
|
+
local mem_kb=$(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "8000000")
|
|
89
|
+
local mem_gb=$((mem_kb / 1024 / 1024))
|
|
90
|
+
|
|
91
|
+
# Raspberry Pi detection
|
|
92
|
+
if [[ -f /proc/device-tree/model ]] && grep -q "Raspberry Pi" /proc/device-tree/model 2>/dev/null; then
|
|
93
|
+
echo "pi"
|
|
94
|
+
return
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Low memory systems (< 2GB)
|
|
98
|
+
if [[ $mem_gb -lt 2 ]]; then
|
|
99
|
+
echo "pi"
|
|
100
|
+
return
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
echo "desktop"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Create environment file based on platform
|
|
107
|
+
create_env_file() {
|
|
108
|
+
local platform=$1
|
|
109
|
+
local env_file=".env"
|
|
110
|
+
|
|
111
|
+
if [[ -f "$env_file" ]]; then
|
|
112
|
+
info "Using existing $env_file file"
|
|
113
|
+
return
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
log "Creating environment file for $platform platform..."
|
|
117
|
+
|
|
118
|
+
if [[ "$platform" == "pi" ]]; then
|
|
119
|
+
cp .env.pi.example "$env_file" 2>/dev/null || cp .env.template "$env_file"
|
|
120
|
+
else
|
|
121
|
+
cp .env.desktop.example "$env_file" 2>/dev/null || cp .env.template "$env_file"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
warn "Please edit $env_file and add your API keys"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Deploy with Docker
|
|
128
|
+
deploy_docker() {
|
|
129
|
+
local platform=$1
|
|
130
|
+
local profile=${COMPOSE_PROFILE:-}
|
|
131
|
+
|
|
132
|
+
log "Deploying with Docker for $platform platform..."
|
|
133
|
+
|
|
134
|
+
# Set platform-specific defaults
|
|
135
|
+
if [[ "$platform" == "pi" ]]; then
|
|
136
|
+
export DOCKER_CPU_LIMIT="${DOCKER_CPU_LIMIT:-1.5}"
|
|
137
|
+
export DOCKER_MEMORY_LIMIT="${DOCKER_MEMORY_LIMIT:-512M}"
|
|
138
|
+
export DOCKER_MEMORY_RESERVATION="${DOCKER_MEMORY_RESERVATION:-256M}"
|
|
139
|
+
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=256}"
|
|
140
|
+
profile="${profile:-lightweight}"
|
|
141
|
+
else
|
|
142
|
+
export DOCKER_CPU_LIMIT="${DOCKER_CPU_LIMIT:-4.0}"
|
|
143
|
+
export DOCKER_MEMORY_LIMIT="${DOCKER_MEMORY_LIMIT:-2G}"
|
|
144
|
+
export DOCKER_MEMORY_RESERVATION="${DOCKER_MEMORY_RESERVATION:-1G}"
|
|
145
|
+
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=1024}"
|
|
146
|
+
profile="${profile:-desktop}"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Create environment file
|
|
150
|
+
create_env_file "$platform"
|
|
151
|
+
|
|
152
|
+
# Docker commands
|
|
153
|
+
local compose_cmd="docker compose"
|
|
154
|
+
if [[ -n "$profile" && "$profile" != "default" ]]; then
|
|
155
|
+
compose_cmd="$compose_cmd --profile $profile"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
159
|
+
info "DRY RUN: Would run:"
|
|
160
|
+
info " $compose_cmd pull"
|
|
161
|
+
info " $compose_cmd up -d"
|
|
162
|
+
return
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Pull and start
|
|
166
|
+
log "Pulling Docker images..."
|
|
167
|
+
$compose_cmd pull
|
|
168
|
+
|
|
169
|
+
log "Starting containers..."
|
|
170
|
+
$compose_cmd up -d
|
|
171
|
+
|
|
172
|
+
# Show status
|
|
173
|
+
log "Checking container status..."
|
|
174
|
+
docker ps --filter name=mcp-rubber-duck
|
|
175
|
+
|
|
176
|
+
log "✅ Deployment completed!"
|
|
177
|
+
info "Check logs with: docker logs mcp-rubber-duck"
|
|
178
|
+
info "Stop with: $compose_cmd down"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Deploy via SSH
|
|
182
|
+
deploy_ssh() {
|
|
183
|
+
local ssh_host=$1
|
|
184
|
+
|
|
185
|
+
if [[ -z "$ssh_host" ]]; then
|
|
186
|
+
error "SSH host not specified. Use --ssh-host option."
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
log "Deploying to remote host: $ssh_host"
|
|
190
|
+
|
|
191
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
192
|
+
info "DRY RUN: Would deploy to $ssh_host"
|
|
193
|
+
return
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Copy files to remote host
|
|
197
|
+
log "Copying files to remote host..."
|
|
198
|
+
ssh "$ssh_host" "mkdir -p ~/mcp-rubber-duck"
|
|
199
|
+
scp docker-compose.yml "$ssh_host:~/mcp-rubber-duck/"
|
|
200
|
+
scp .env.template "$ssh_host:~/mcp-rubber-duck/"
|
|
201
|
+
scp scripts/deploy.sh "$ssh_host:~/mcp-rubber-duck/"
|
|
202
|
+
|
|
203
|
+
# Run deployment on remote host
|
|
204
|
+
log "Running deployment on remote host..."
|
|
205
|
+
ssh "$ssh_host" "cd ~/mcp-rubber-duck && bash deploy.sh --mode docker --platform pi"
|
|
206
|
+
|
|
207
|
+
log "✅ Remote deployment completed!"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Deploy from source
|
|
211
|
+
deploy_local() {
|
|
212
|
+
local platform=$1
|
|
213
|
+
|
|
214
|
+
log "Deploying from source for $platform platform..."
|
|
215
|
+
|
|
216
|
+
# Check prerequisites
|
|
217
|
+
if ! command -v node &> /dev/null; then
|
|
218
|
+
error "Node.js is not installed"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if ! command -v npm &> /dev/null; then
|
|
222
|
+
error "npm is not installed"
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Set platform-specific environment
|
|
226
|
+
create_env_file "$platform"
|
|
227
|
+
|
|
228
|
+
if [[ "$platform" == "pi" ]]; then
|
|
229
|
+
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=256}"
|
|
230
|
+
else
|
|
231
|
+
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=1024}"
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
235
|
+
info "DRY RUN: Would run:"
|
|
236
|
+
info " npm install"
|
|
237
|
+
info " npm run build"
|
|
238
|
+
info " npm start"
|
|
239
|
+
return
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Build and run
|
|
243
|
+
log "Installing dependencies..."
|
|
244
|
+
npm install
|
|
245
|
+
|
|
246
|
+
log "Building project..."
|
|
247
|
+
npm run build
|
|
248
|
+
|
|
249
|
+
log "Starting MCP server..."
|
|
250
|
+
info "MCP Rubber Duck is now running from source"
|
|
251
|
+
npm start
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# Parse command line arguments
|
|
255
|
+
while [[ $# -gt 0 ]]; do
|
|
256
|
+
case $1 in
|
|
257
|
+
-m|--mode)
|
|
258
|
+
DEPLOYMENT_MODE="$2"
|
|
259
|
+
shift 2
|
|
260
|
+
;;
|
|
261
|
+
-p|--platform)
|
|
262
|
+
PLATFORM="$2"
|
|
263
|
+
shift 2
|
|
264
|
+
;;
|
|
265
|
+
--profile)
|
|
266
|
+
COMPOSE_PROFILE="$2"
|
|
267
|
+
shift 2
|
|
268
|
+
;;
|
|
269
|
+
--ssh-host)
|
|
270
|
+
SSH_HOST="$2"
|
|
271
|
+
shift 2
|
|
272
|
+
;;
|
|
273
|
+
--dry-run)
|
|
274
|
+
DRY_RUN="true"
|
|
275
|
+
shift
|
|
276
|
+
;;
|
|
277
|
+
-h|--help)
|
|
278
|
+
print_usage
|
|
279
|
+
exit 0
|
|
280
|
+
;;
|
|
281
|
+
*)
|
|
282
|
+
error "Unknown option: $1"
|
|
283
|
+
;;
|
|
284
|
+
esac
|
|
285
|
+
done
|
|
286
|
+
|
|
287
|
+
# Main execution
|
|
288
|
+
log "🦆 Starting MCP Rubber Duck deployment"
|
|
289
|
+
|
|
290
|
+
# Detect platform
|
|
291
|
+
DETECTED_PLATFORM=$(detect_platform)
|
|
292
|
+
log "Platform: $DETECTED_PLATFORM"
|
|
293
|
+
|
|
294
|
+
# Auto-detect deployment mode if needed
|
|
295
|
+
if [[ "$DEPLOYMENT_MODE" == "auto" ]]; then
|
|
296
|
+
if command -v docker &> /dev/null; then
|
|
297
|
+
DEPLOYMENT_MODE="docker"
|
|
298
|
+
else
|
|
299
|
+
DEPLOYMENT_MODE="local"
|
|
300
|
+
fi
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
info "Deployment mode: $DEPLOYMENT_MODE"
|
|
304
|
+
info "Target platform: $DETECTED_PLATFORM"
|
|
305
|
+
|
|
306
|
+
# Deploy based on mode
|
|
307
|
+
case $DEPLOYMENT_MODE in
|
|
308
|
+
docker)
|
|
309
|
+
deploy_docker "$DETECTED_PLATFORM"
|
|
310
|
+
;;
|
|
311
|
+
ssh)
|
|
312
|
+
deploy_ssh "${SSH_HOST:-}"
|
|
313
|
+
;;
|
|
314
|
+
local)
|
|
315
|
+
deploy_local "$DETECTED_PLATFORM"
|
|
316
|
+
;;
|
|
317
|
+
*)
|
|
318
|
+
error "Unknown deployment mode: $DEPLOYMENT_MODE"
|
|
319
|
+
;;
|
|
320
|
+
esac
|
|
321
|
+
|
|
322
|
+
log "🎉 Deployment completed successfully!"
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# MCP Rubber Duck - GitHub Container Registry Deployment Script
|
|
4
|
+
# This script simplifies deployment using GitHub CLI and GitHub Container Registry
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Colors for output
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Configuration
|
|
16
|
+
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
|
17
|
+
MAKE_PUBLIC="${MAKE_PUBLIC:-false}"
|
|
18
|
+
BUILD_PLATFORMS="${BUILD_PLATFORMS:-linux/amd64,linux/arm64,linux/arm/v7}"
|
|
19
|
+
|
|
20
|
+
# Functions
|
|
21
|
+
log() {
|
|
22
|
+
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
warn() {
|
|
26
|
+
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
error() {
|
|
30
|
+
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
|
31
|
+
exit 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
info() {
|
|
35
|
+
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
print_usage() {
|
|
39
|
+
cat << EOF
|
|
40
|
+
Usage: $0 [OPTIONS]
|
|
41
|
+
|
|
42
|
+
Deploy MCP Rubber Duck to GitHub Container Registry using GitHub CLI
|
|
43
|
+
|
|
44
|
+
OPTIONS:
|
|
45
|
+
-t, --tag TAG Docker image tag (default: latest)
|
|
46
|
+
--public Make package public after push
|
|
47
|
+
--platforms PLATFORMS Target platforms (default: linux/amd64,linux/arm64,linux/arm/v7)
|
|
48
|
+
--local-only Build for local architecture only (faster testing)
|
|
49
|
+
--dry-run Show what would be done without executing
|
|
50
|
+
-v, --verbose Verbose output
|
|
51
|
+
-h, --help Show this help message
|
|
52
|
+
|
|
53
|
+
EXAMPLES:
|
|
54
|
+
# Deploy latest version to GitHub Container Registry
|
|
55
|
+
$0
|
|
56
|
+
|
|
57
|
+
# Deploy with specific tag
|
|
58
|
+
$0 --tag v1.2.0
|
|
59
|
+
|
|
60
|
+
# Deploy and make package public
|
|
61
|
+
$0 --public
|
|
62
|
+
|
|
63
|
+
# Local development build
|
|
64
|
+
$0 --local-only --tag dev
|
|
65
|
+
|
|
66
|
+
# See what would happen
|
|
67
|
+
$0 --dry-run
|
|
68
|
+
|
|
69
|
+
REQUIREMENTS:
|
|
70
|
+
- GitHub CLI (gh) installed and authenticated
|
|
71
|
+
- Docker with buildx support
|
|
72
|
+
- Repository must be initialized as git repo
|
|
73
|
+
- Must have push access to repository
|
|
74
|
+
|
|
75
|
+
EOF
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Check prerequisites
|
|
79
|
+
check_prerequisites() {
|
|
80
|
+
log "Checking prerequisites..."
|
|
81
|
+
|
|
82
|
+
# Check if gh is installed
|
|
83
|
+
if ! command -v gh &> /dev/null; then
|
|
84
|
+
error "GitHub CLI (gh) is not installed. Install from: https://cli.github.com/"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Check if authenticated with GitHub
|
|
88
|
+
if ! gh auth status &> /dev/null; then
|
|
89
|
+
error "Not authenticated with GitHub. Run: gh auth login"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Check if Docker is installed
|
|
93
|
+
if ! command -v docker &> /dev/null; then
|
|
94
|
+
error "Docker is not installed or not in PATH"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Check if buildx is available
|
|
98
|
+
if ! docker buildx version &> /dev/null; then
|
|
99
|
+
error "Docker buildx is not available. Please update Docker to a newer version."
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Check if we're in a git repository
|
|
103
|
+
if ! git rev-parse --git-dir &> /dev/null; then
|
|
104
|
+
error "This script must be run from within a git repository"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
log "✅ All prerequisites satisfied"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Get GitHub and repository information
|
|
111
|
+
get_github_info() {
|
|
112
|
+
log "Getting GitHub repository information..."
|
|
113
|
+
|
|
114
|
+
# Get GitHub username
|
|
115
|
+
GITHUB_USER=$(gh api user --jq .login 2>/dev/null)
|
|
116
|
+
if [[ -z "$GITHUB_USER" ]]; then
|
|
117
|
+
error "Could not get GitHub username. Check your gh authentication."
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Try to get repository info from current directory
|
|
121
|
+
if gh repo view &> /dev/null; then
|
|
122
|
+
REPO_NAME=$(gh repo view --json name --jq .name)
|
|
123
|
+
REPO_OWNER=$(gh repo view --json owner --jq .owner.login)
|
|
124
|
+
REPO_URL=$(gh repo view --json url --jq .url)
|
|
125
|
+
|
|
126
|
+
# Check if we have push access
|
|
127
|
+
if ! gh api "repos/$REPO_OWNER/$REPO_NAME" --jq .permissions.push | grep -q true; then
|
|
128
|
+
warn "You may not have push access to $REPO_OWNER/$REPO_NAME"
|
|
129
|
+
fi
|
|
130
|
+
else
|
|
131
|
+
# Fallback to git remote
|
|
132
|
+
REPO_NAME=$(basename "$(git rev-parse --show-toplevel)" 2>/dev/null || echo "mcp-rubber-duck")
|
|
133
|
+
REPO_OWNER="$GITHUB_USER"
|
|
134
|
+
REPO_URL="https://github.com/$REPO_OWNER/$REPO_NAME"
|
|
135
|
+
warn "Could not get repository info from gh. Using fallback: $REPO_OWNER/$REPO_NAME"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Build image name
|
|
139
|
+
IMAGE_NAME="ghcr.io/$REPO_OWNER/$REPO_NAME"
|
|
140
|
+
|
|
141
|
+
info "GitHub User: $GITHUB_USER"
|
|
142
|
+
info "Repository: $REPO_OWNER/$REPO_NAME"
|
|
143
|
+
info "Image name: $IMAGE_NAME:$IMAGE_TAG"
|
|
144
|
+
info "Repository URL: $REPO_URL"
|
|
145
|
+
|
|
146
|
+
log "✅ GitHub information gathered"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Authenticate Docker with GitHub Container Registry
|
|
150
|
+
authenticate_docker() {
|
|
151
|
+
log "Authenticating Docker with GitHub Container Registry..."
|
|
152
|
+
|
|
153
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
154
|
+
info "DRY RUN: Would authenticate Docker with ghcr.io"
|
|
155
|
+
return
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
if ! gh auth token | docker login ghcr.io -u "$GITHUB_USER" --password-stdin; then
|
|
159
|
+
error "Failed to authenticate Docker with ghcr.io"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
log "✅ Docker authenticated with ghcr.io"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Build and push Docker image
|
|
166
|
+
build_and_push() {
|
|
167
|
+
log "Building and pushing Docker image..."
|
|
168
|
+
|
|
169
|
+
# Determine platforms
|
|
170
|
+
local platforms="$BUILD_PLATFORMS"
|
|
171
|
+
if [[ "${LOCAL_ONLY:-false}" == "true" ]]; then
|
|
172
|
+
platforms=""
|
|
173
|
+
info "Building for local architecture only"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
177
|
+
info "DRY RUN: Would build and push:"
|
|
178
|
+
info " Image: $IMAGE_NAME:$IMAGE_TAG"
|
|
179
|
+
info " Platforms: ${platforms:-$(uname -m)}"
|
|
180
|
+
return
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Use the enhanced build script with GitHub support
|
|
184
|
+
local build_args=(
|
|
185
|
+
"./scripts/build-multiarch.sh"
|
|
186
|
+
"--github"
|
|
187
|
+
"--tag" "$IMAGE_TAG"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if [[ "${LOCAL_ONLY:-false}" == "true" ]]; then
|
|
191
|
+
build_args+=("--local")
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
if [[ "${VERBOSE:-false}" == "true" ]]; then
|
|
195
|
+
build_args+=("--verbose")
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
log "Running: ${build_args[*]}"
|
|
199
|
+
if "${build_args[@]}"; then
|
|
200
|
+
log "✅ Build and push completed successfully"
|
|
201
|
+
else
|
|
202
|
+
error "❌ Build and push failed"
|
|
203
|
+
fi
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# Manage package visibility and settings
|
|
207
|
+
manage_package() {
|
|
208
|
+
log "Managing GitHub package settings..."
|
|
209
|
+
|
|
210
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
211
|
+
info "DRY RUN: Would manage package settings"
|
|
212
|
+
return
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# Wait a moment for package to appear
|
|
216
|
+
sleep 5
|
|
217
|
+
|
|
218
|
+
# Check if package exists
|
|
219
|
+
if ! gh api "user/packages/container/$REPO_NAME" &> /dev/null; then
|
|
220
|
+
warn "Package not yet available. It may take a moment to appear in GitHub."
|
|
221
|
+
return
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Make package public if requested
|
|
225
|
+
if [[ "$MAKE_PUBLIC" == "true" ]]; then
|
|
226
|
+
log "Making package public..."
|
|
227
|
+
if gh api --method PATCH "user/packages/container/$REPO_NAME" --field visibility=public; then
|
|
228
|
+
log "✅ Package is now public"
|
|
229
|
+
else
|
|
230
|
+
warn "Failed to make package public (may require manual action)"
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# Link package to repository
|
|
235
|
+
log "Linking package to repository..."
|
|
236
|
+
if gh api --method PUT "user/packages/container/$REPO_NAME/restore" --field token="$(gh auth token)" &> /dev/null; then
|
|
237
|
+
log "✅ Package linked to repository"
|
|
238
|
+
else
|
|
239
|
+
info "Package repository linking may require manual action"
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
log "✅ Package management completed"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Show package information and next steps
|
|
246
|
+
show_package_info() {
|
|
247
|
+
log "📦 Package Information:"
|
|
248
|
+
|
|
249
|
+
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
|
250
|
+
info "DRY RUN: Package would be available at:"
|
|
251
|
+
info " URL: https://github.com/$REPO_OWNER/packages/container/package/$REPO_NAME"
|
|
252
|
+
info " Pull command: docker pull ghcr.io/$REPO_OWNER/$REPO_NAME:$IMAGE_TAG"
|
|
253
|
+
return
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Package URL
|
|
257
|
+
echo " 📍 Package URL: https://github.com/$REPO_OWNER/packages/container/package/$REPO_NAME"
|
|
258
|
+
|
|
259
|
+
# Pull command
|
|
260
|
+
echo " 🐳 Pull command: docker pull $IMAGE_NAME:$IMAGE_TAG"
|
|
261
|
+
|
|
262
|
+
# Try to get package info
|
|
263
|
+
if gh api "user/packages/container/$REPO_NAME" &> /dev/null; then
|
|
264
|
+
local visibility
|
|
265
|
+
local updated
|
|
266
|
+
visibility=$(gh api "user/packages/container/$REPO_NAME" --jq .visibility 2>/dev/null || echo "unknown")
|
|
267
|
+
updated=$(gh api "user/packages/container/$REPO_NAME" --jq .updated_at 2>/dev/null || echo "unknown")
|
|
268
|
+
|
|
269
|
+
echo " 👁️ Visibility: $visibility"
|
|
270
|
+
echo " 📅 Updated: $updated"
|
|
271
|
+
|
|
272
|
+
# Show versions
|
|
273
|
+
local versions
|
|
274
|
+
versions=$(gh api "user/packages/container/$REPO_NAME/versions" --jq 'length' 2>/dev/null || echo "0")
|
|
275
|
+
echo " 🏷️ Versions: $versions"
|
|
276
|
+
|
|
277
|
+
else
|
|
278
|
+
warn "Package information not yet available"
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
echo
|
|
282
|
+
log "🚀 Multi-Platform Deployment:"
|
|
283
|
+
echo " 1. On your deployment target, update docker-compose.yml (or create .env):"
|
|
284
|
+
echo " DOCKER_IMAGE=$IMAGE_NAME:$IMAGE_TAG"
|
|
285
|
+
echo " 2. Pull and run (works on all platforms):"
|
|
286
|
+
echo " docker compose pull"
|
|
287
|
+
echo " docker compose up -d"
|
|
288
|
+
|
|
289
|
+
if [[ "$MAKE_PUBLIC" != "true" ]]; then
|
|
290
|
+
echo
|
|
291
|
+
info "💡 To make package public for easier access:"
|
|
292
|
+
echo " gh api --method PATCH user/packages/container/$REPO_NAME --field visibility=public"
|
|
293
|
+
fi
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# Parse command line arguments
|
|
297
|
+
while [[ $# -gt 0 ]]; do
|
|
298
|
+
case $1 in
|
|
299
|
+
-t|--tag)
|
|
300
|
+
IMAGE_TAG="$2"
|
|
301
|
+
shift 2
|
|
302
|
+
;;
|
|
303
|
+
--public)
|
|
304
|
+
MAKE_PUBLIC="true"
|
|
305
|
+
shift
|
|
306
|
+
;;
|
|
307
|
+
--platforms)
|
|
308
|
+
BUILD_PLATFORMS="$2"
|
|
309
|
+
shift 2
|
|
310
|
+
;;
|
|
311
|
+
--local-only)
|
|
312
|
+
LOCAL_ONLY="true"
|
|
313
|
+
shift
|
|
314
|
+
;;
|
|
315
|
+
--dry-run)
|
|
316
|
+
DRY_RUN="true"
|
|
317
|
+
shift
|
|
318
|
+
;;
|
|
319
|
+
-v|--verbose)
|
|
320
|
+
VERBOSE="true"
|
|
321
|
+
shift
|
|
322
|
+
;;
|
|
323
|
+
-h|--help)
|
|
324
|
+
print_usage
|
|
325
|
+
exit 0
|
|
326
|
+
;;
|
|
327
|
+
*)
|
|
328
|
+
error "Unknown option: $1"
|
|
329
|
+
;;
|
|
330
|
+
esac
|
|
331
|
+
done
|
|
332
|
+
|
|
333
|
+
# Main execution
|
|
334
|
+
log "🦆 Starting GitHub Container Registry deployment for MCP Rubber Duck"
|
|
335
|
+
|
|
336
|
+
check_prerequisites
|
|
337
|
+
get_github_info
|
|
338
|
+
authenticate_docker
|
|
339
|
+
build_and_push
|
|
340
|
+
manage_package
|
|
341
|
+
show_package_info
|
|
342
|
+
|
|
343
|
+
log "🎉 GitHub deployment completed successfully!"
|