diff --git a/README.md b/README.md index d3e30c5..d8993dc 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,41 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: } ``` +## Docker Deployment + +A [`deploy.sh`](deploy.sh:1) script is provided for easy Docker build and deployment. + +### Quick Start + +```bash +./deploy.sh build # Build the Docker image +./deploy.sh run # Run the container locally +``` + +### Commands + +| Command | Description | +|---------|-------------| +| `build` | Build the Docker image | +| `run` | Run the container locally with cache volume | +| `push` | Push the image to a registry | +| `deploy` | Build and push in one step | +| `clean` | Remove local images and containers | + +### Options + +```bash +./deploy.sh build -t v1.0.0 # Build with specific tag +./deploy.sh push -r docker.io/myuser/ # Push to Docker Hub +./deploy.sh deploy -t v1.0.0 -r ghcr.io/myuser/ # Build and push to GitHub Container Registry +``` + +### Environment Variables + +- `IMAGE_TAG` - Image tag (default: latest) +- `REGISTRY` - Registry prefix (e.g., "docker.io/username/") +- `IMAGE_NAME` - Image name (default: character-details) + ## Cache Character data is cached at `~/.local/share/character_details/cache/` as JSON files. @@ -79,6 +114,7 @@ character_details/ server.py # FastMCP server and tool definitions pyproject.toml README.md + deploy.sh # Docker build and deployment script ``` ## Test Characters diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..9dd7c08 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,217 @@ +#!/bin/bash +# +# Deploy script for character-details MCP server +# Usage: ./deploy.sh [command] [options] +# + +set -e + +# Configuration +IMAGE_NAME="character-details" +IMAGE_TAG="${IMAGE_TAG:-latest}" +REGISTRY="${REGISTRY:-}" +FULL_IMAGE_NAME="${REGISTRY}${IMAGE_NAME}:${IMAGE_TAG}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Show usage +show_usage() { + cat << EOF +Usage: $0 [command] [options] + +Commands: + build Build the Docker image + run Run the container locally + push Push the image to registry + deploy Build and push in one step + clean Remove local images and containers + help Show this help message + +Options: + -t, --tag TAG Set image tag (default: latest) + -r, --registry REG Set registry prefix (e.g., "docker.io/username/") + -n, --name NAME Set image name (default: character-details) + +Environment Variables: + IMAGE_TAG Set image tag + REGISTRY Set registry prefix + IMAGE_NAME Set image name + +Examples: + $0 build # Build with default tag + $0 build -t v1.0.0 # Build with specific tag + $0 run # Run locally + $0 push -r docker.io/myuser/ # Push to Docker Hub + $0 deploy -t v1.0.0 -r ghcr.io/myuser/ # Build and push to GitHub Container Registry + +EOF +} + +# Parse options +parse_options() { + while [[ $# -gt 0 ]]; do + case $1 in + -t|--tag) + IMAGE_TAG="$2" + shift 2 + ;; + -r|--registry) + REGISTRY="$2" + shift 2 + ;; + -n|--name) + IMAGE_NAME="$2" + shift 2 + ;; + -*) + log_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + break + ;; + esac + done + + # Update full image name + FULL_IMAGE_NAME="${REGISTRY}${IMAGE_NAME}:${IMAGE_TAG}" +} + +# Build the Docker image +cmd_build() { + log_info "Building Docker image: ${FULL_IMAGE_NAME}" + + # Check if Dockerfile exists + if [[ ! -f "Dockerfile" ]]; then + log_error "Dockerfile not found in current directory" + exit 1 + fi + + # Build the image + docker build -t "${FULL_IMAGE_NAME}" -t "${REGISTRY}${IMAGE_NAME}:latest" . + + log_success "Built image: ${FULL_IMAGE_NAME}" + log_info "Image size: $(docker images "${FULL_IMAGE_NAME}" --format '{{.Size}}')" +} + +# Run the container locally +cmd_run() { + log_info "Running container: ${FULL_IMAGE_NAME}" + + # Create cache directory if it doesn't exist + CACHE_DIR="${HOME}/.local/share/character_details" + mkdir -p "${CACHE_DIR}" + + log_info "Mounting cache directory: ${CACHE_DIR}" + + # Run the container + docker run -it --rm \ + -v "${CACHE_DIR}:/root/.local/share/character_details" \ + "${FULL_IMAGE_NAME}" +} + +# Push the image to registry +cmd_push() { + log_info "Pushing image: ${FULL_IMAGE_NAME}" + + if [[ -z "${REGISTRY}" ]]; then + log_warn "No registry specified. Using Docker Hub default." + fi + + docker push "${FULL_IMAGE_NAME}" + + # Also push latest tag + docker push "${REGISTRY}${IMAGE_NAME}:latest" + + log_success "Pushed image: ${FULL_IMAGE_NAME}" +} + +# Build and push in one step +cmd_deploy() { + cmd_build + cmd_push +} + +# Clean up local images and containers +cmd_clean() { + log_info "Cleaning up Docker resources for ${IMAGE_NAME}" + + # Stop and remove containers + local containers + containers=$(docker ps -aq --filter "ancestor=${REGISTRY}${IMAGE_NAME}") + if [[ -n "${containers}" ]]; then + log_info "Removing containers..." + docker rm -f ${containers} + fi + + # Remove images + local images + images=$(docker images -q "${REGISTRY}${IMAGE_NAME}") + if [[ -n "${images}" ]]; then + log_info "Removing images..." + docker rmi -f ${images} + fi + + log_success "Cleanup complete" +} + +# Main entry point +main() { + # Parse global options first + parse_options "$@" + + # Get command + local command="${1:-help}" + shift || true + + case "${command}" in + build) + cmd_build + ;; + run) + cmd_run + ;; + push) + cmd_push + ;; + deploy) + cmd_deploy + ;; + clean) + cmd_clean + ;; + help|--help|-h) + show_usage + ;; + *) + log_error "Unknown command: ${command}" + show_usage + exit 1 + ;; + esac +} + +main "$@"