mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-20 12:19:07 +00:00
feat(goose): update config using uv script with pyyaml (#6)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
94
mcontainer/drivers/goose/update-goose-config.py
Normal file
94
mcontainer/drivers/goose/update-goose-config.py
Normal 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.")
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user