feat(goose): update config using uv script with pyyaml (#6)

This commit is contained in:
2025-04-02 23:27:37 +02:00
committed by GitHub
parent cf31c7c25d
commit 1201eb2d3d
5 changed files with 117 additions and 160 deletions

View File

@@ -24,17 +24,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir -p /var/run/sshd && chmod 0755 /var/run/sshd
# Do NOT enable root login or set root password here
# Install python dependencies
# This is done before copying scripts for better cache management
# Consider moving this WORKDIR /tmp section if goose CLI isn't strictly needed for base image setup
# Install deps
WORKDIR /tmp
RUN curl -fsSL https://astral.sh/uv/install.sh -o install.sh && \
sh install.sh && \
mv /root/.local/bin/uv /usr/local/bin/uv && \
mv /root/.local/bin/uvx /usr/local/bin/uvx && \
rm install.sh
RUN curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh -o download_cli.sh && \
chmod +x download_cli.sh && \
./download_cli.sh && \
# Move goose to a system-wide location
mv /root/.local/bin/goose /usr/local/bin/goose && \
# Clean up
rm -rf /root/.local download_cli.sh /tmp/goose-*
rm -rf download_cli.sh /tmp/goose-*
# Create app directory
WORKDIR /app
@@ -44,13 +45,13 @@ COPY mc-init.sh /mc-init.sh
COPY entrypoint.sh /entrypoint.sh
COPY mc-driver.yaml /mc-driver.yaml
COPY init-status.sh /init-status.sh
COPY update-goose-config.sh /usr/local/bin/update-goose-config.sh
COPY update-goose-config.py /usr/local/bin/update-goose-config.py
# Extend env via bashrc
# Make scripts executable
RUN chmod +x /mc-init.sh /entrypoint.sh /init-status.sh \
/usr/local/bin/update-goose-config.sh
/usr/local/bin/update-goose-config.py
# Set up initialization status check on login
RUN echo '[ -x /init-status.sh ] && /init-status.sh' >> /etc/bash.bashrc

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# Script to check and display initialization status - optimized version
# Script to check and display initialization status
# Only proceed if running as root
if [ "$(id -u)" != "0" ]; then

View File

@@ -41,10 +41,17 @@ fi
# Create home directory and set permissions
mkdir -p /home/mcuser
chown $MC_USER_ID:$MC_GROUP_ID /home/mcuser
# Ensure /app exists and has correct ownership (important for volume mounts)
mkdir -p /app
chown $MC_USER_ID:$MC_GROUP_ID /app
# Copy /root/.local/bin to the user's home directory
if [ -d /root/.local/bin ]; then
echo "Copying /root/.local/bin to /home/mcuser/.local/bin..."
mkdir -p /home/mcuser/.local/bin
cp -r /root/.local/bin/* /home/mcuser/.local/bin/
chown -R $MC_USER_ID:$MC_GROUP_ID /home/mcuser/.local/bin
fi
# Start SSH server only if explicitly enabled
if [ "$MC_SSH_ENABLED" = "true" ]; then
echo "Starting SSH server..."
@@ -141,14 +148,14 @@ if [ -n "$MC_PERSISTENT_LINKS" ]; then
fi
# Update Goose configuration with available MCP servers (run as mcuser after symlinks are created)
if [ -f "/usr/local/bin/update-goose-config.sh" ]; then
if [ -f "/usr/local/bin/update-goose-config.py" ]; then
echo "Updating Goose configuration with MCP servers as mcuser..."
gosu mcuser bash /usr/local/bin/update-goose-config.sh
elif [ -f "$(dirname "$0")/update-goose-config.sh" ]; then
gosu mcuser /usr/local/bin/update-goose-config.py
elif [ -f "$(dirname "$0")/update-goose-config.py" ]; then
echo "Updating Goose configuration with MCP servers as mcuser..."
gosu mcuser bash "$(dirname "$0")/update-goose-config.sh"
gosu mcuser "$(dirname "$0")/update-goose-config.py"
else
echo "Warning: update-goose-config.sh script not found. Goose configuration will not be updated."
echo "Warning: update-goose-config.py script not found. Goose configuration will not be updated."
fi
# Mark initialization as complete

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["ruamel.yaml"]
# ///
import json
import os
from pathlib import Path
from ruamel.yaml import YAML
# Path to goose config
GOOSE_CONFIG = Path.home() / ".config/goose/config.yaml"
CONFIG_DIR = GOOSE_CONFIG.parent
# Create config directory if it doesn't exist
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
def update_config():
"""Update Goose configuration based on environment variables and config file"""
yaml = YAML()
# Load or initialize the YAML configuration
if not GOOSE_CONFIG.exists():
config_data = {"extensions": {}}
else:
with GOOSE_CONFIG.open("r") as f:
config_data = yaml.load(f)
if "extensions" not in config_data:
config_data["extensions"] = {}
# Get MCP information from environment variables
mcp_count = int(os.environ.get("MCP_COUNT", "0"))
mcp_names_str = os.environ.get("MCP_NAMES", "[]")
try:
mcp_names = json.loads(mcp_names_str)
print(f"Found {mcp_count} MCP servers: {', '.join(mcp_names)}")
except json.JSONDecodeError:
mcp_names = []
print("Error parsing MCP_NAMES environment variable")
# Process each MCP - collect the MCP configs to add or update
for idx in range(mcp_count):
mcp_name = os.environ.get(f"MCP_{idx}_NAME")
mcp_type = os.environ.get(f"MCP_{idx}_TYPE")
mcp_host = os.environ.get(f"MCP_{idx}_HOST")
# Always use container's SSE port (8080) not the host-bound port
if mcp_name and mcp_host:
# Use standard MCP SSE port (8080)
mcp_url = f"http://{mcp_host}:8080/sse"
print(f"Processing MCP extension: {mcp_name} ({mcp_type}) - {mcp_url}")
config_data["extensions"][mcp_name] = {
"enabled": True,
"name": mcp_name,
"timeout": 60,
"type": "sse",
"uri": mcp_url,
"envs": {},
}
elif mcp_name and os.environ.get(f"MCP_{idx}_URL"):
# For remote MCPs, use the URL provided in environment
mcp_url = os.environ.get(f"MCP_{idx}_URL")
print(
f"Processing remote MCP extension: {mcp_name} ({mcp_type}) - {mcp_url}"
)
config_data["extensions"][mcp_name] = {
"enabled": True,
"name": mcp_name,
"timeout": 60,
"type": "sse",
"uri": mcp_url,
"envs": {},
}
# Write the updated configuration back to the file
with GOOSE_CONFIG.open("w") as f:
yaml.dump(config_data, f)
print(f"Updated Goose configuration at {GOOSE_CONFIG}")
if __name__ == "__main__":
mcp_count_str = os.environ.get("MCP_COUNT", "0")
mcp_count = int(mcp_count_str)
if mcp_count > 0:
print("Updating Goose configuration with MCP servers...")
update_config()
print("Goose configuration updated successfully!")
else:
print("No MCP servers found, using default Goose configuration.")

View File

@@ -1,145 +0,0 @@
#!/bin/bash
# Script to update Goose configuration with MCP servers using Python standard library
# Define config path
GOOSE_CONFIG="$HOME/.config/goose/config.yaml"
CONFIG_DIR="$(dirname "$GOOSE_CONFIG")"
# Create config directory if it doesn't exist
mkdir -p "$CONFIG_DIR"
# Function to update config using Python without yaml module
update_config() {
python3 - << 'EOF'
import os
import json
import re
# Path to goose config
config_path = os.path.expanduser('~/.config/goose/config.yaml')
# Check if file exists, create if not
if not os.path.exists(config_path):
with open(config_path, 'w') as f:
f.write("extensions:\n")
# Read the entire file
with open(config_path, 'r') as f:
content = f.read()
# Get MCP information from environment variables
mcp_count = int(os.environ.get('MCP_COUNT', '0'))
mcp_names_str = os.environ.get('MCP_NAMES', '[]')
try:
mcp_names = json.loads(mcp_names_str)
print(f"Found {mcp_count} MCP servers: {', '.join(mcp_names)}")
except:
mcp_names = []
print("Error parsing MCP_NAMES environment variable")
# Check if extensions key exists, add if not
if 'extensions:' not in content:
content = "extensions:\n" + content
# Process each MCP - we'll collect the mcp configs to add or update
mcp_configs = []
for idx in range(mcp_count):
mcp_name = os.environ.get(f'MCP_{idx}_NAME')
mcp_type = os.environ.get(f'MCP_{idx}_TYPE')
mcp_host = os.environ.get(f'MCP_{idx}_HOST')
# Always use container's SSE port (8080) not the host-bound port
if mcp_name and mcp_host:
# Use standard MCP SSE port (8080)
mcp_url = f"http://{mcp_host}:8080/sse"
print(f"Processing MCP extension: {mcp_name} ({mcp_type}) - {mcp_url}")
mcp_configs.append((mcp_name, mcp_url))
elif mcp_name and os.environ.get(f'MCP_{idx}_URL'):
# For remote MCPs, use the URL provided in environment
mcp_url = os.environ.get(f'MCP_{idx}_URL')
print(f"Processing remote MCP extension: {mcp_name} ({mcp_type}) - {mcp_url}")
mcp_configs.append((mcp_name, mcp_url))
# Now we'll update the config file line by line, preserving all content
lines = content.split('\n')
output_lines = []
in_extensions = False
current_ext = None
extension_added = set() # Track which extensions we've processed
# First pass - update existing extensions and track them
for line in lines:
# Check if we're entering extensions section
if line.strip() == 'extensions:':
in_extensions = True
output_lines.append(line)
continue
# Look for extension definition (2-space indentation)
if in_extensions and re.match(r'^ (\w+):', line):
match = re.match(r'^ (\w+):', line)
current_ext = match.group(1)
output_lines.append(line)
# Mark as seen if this is one of our MCPs
for mcp_name, _ in mcp_configs:
if mcp_name == current_ext:
extension_added.add(mcp_name)
continue
# If we're in an MCP extension that we need to update
if in_extensions and current_ext and current_ext in [n for n, _ in mcp_configs]:
# If this is a URI line, replace it with our URL
if line.strip().startswith('uri:'):
for mcp_name, mcp_url in mcp_configs:
if mcp_name == current_ext:
output_lines.append(f' uri: {mcp_url}')
break
# If this is a type line, ensure it's SSE
elif line.strip().startswith('type:'):
output_lines.append(' type: sse')
# If this is enabled line, ensure it's true
elif line.strip().startswith('enabled:'):
output_lines.append(' enabled: true')
# Otherwise keep the line
else:
output_lines.append(line)
continue
# If we're moving to a non-2-space indented line, we're out of the current extension
if in_extensions and current_ext and not line.startswith(' ') and line.strip():
current_ext = None
# For any other line, just add it
output_lines.append(line)
# Add any MCP extensions that weren't found in the existing config
if in_extensions:
for mcp_name, mcp_url in mcp_configs:
if mcp_name not in extension_added:
print(f"Adding new MCP extension: {mcp_name}")
output_lines.append(f' {mcp_name}:')
output_lines.append(f' enabled: true')
output_lines.append(f' name: {mcp_name}')
output_lines.append(f' timeout: 60')
output_lines.append(f' type: sse')
output_lines.append(f' uri: {mcp_url}')
output_lines.append(f' envs: {{}}')
# Write the updated content back
with open(config_path, 'w') as f:
f.write('\n'.join(output_lines))
print(f"Updated Goose configuration at {config_path}")
EOF
}
# Check if MCP servers are defined
if [ -n "$MCP_COUNT" ] && [ "$MCP_COUNT" -gt 0 ]; then
echo "Updating Goose configuration with MCP servers..."
update_config
echo "Goose configuration updated successfully!"
else
echo "No MCP servers found, using default Goose configuration."
fi