#!/bin/sh # Substitute the app port in supervisord template and start supervisord. # Default port 8088; override at runtime with -e MASKANX_PORT=3000. set -e export ADCLAW_PORT="${MASKANX_PORT:-${ADCLAW_PORT:-8088}}" export ADCLAW_WORKING_DIR="${MASKANX_WORKING_DIR:-${ADCLAW_WORKING_DIR:-/app/working}}" export ADCLAW_SECRET_DIR="${MASKANX_SECRET_DIR:-${ADCLAW_SECRET_DIR:-/app/working.secret}}" export ADCLAW_ENABLED_CHANNELS="${MASKANX_ENABLED_CHANNELS:-${ADCLAW_ENABLED_CHANNELS:-discord,dingtalk,feishu,qq,console,telegram}}" export ADCLAW_STORAGE_BACKEND="${MASKANX_STORAGE_BACKEND:-${ADCLAW_STORAGE_BACKEND:-json}}" # Ensure config.json exists (first run only — don't overwrite user config) CONFIG="${ADCLAW_WORKING_DIR}/config.json" if [ ! -f "$CONFIG" ] || [ ! -s "$CONFIG" ]; then echo "entrypoint: No config.json found, generating default..." maskanx init --defaults --accept-security 2>/dev/null || true fi # Migrate config.json — apply new defaults to existing config without overwriting user data python3 -c " import json, os, sys cfg_path = os.environ.get('ADCLAW_WORKING_DIR', '/app/working') + '/config.json' if not os.path.isfile(cfg_path): sys.exit(0) with open(cfg_path) as f: cfg = json.load(f) changed = False # Force correct defaults that should never be True in production if cfg.get('show_tool_details') is not False: cfg['show_tool_details'] = False changed = True # Ensure filter_tool_messages=True on all channels for ch_name, ch_cfg in cfg.get('channels', {}).items(): if isinstance(ch_cfg, dict) and ch_cfg.get('filter_tool_messages') is not True: ch_cfg['filter_tool_messages'] = True changed = True if changed: with open(cfg_path, 'w') as f: json.dump(cfg, f, indent=2, ensure_ascii=False) print('entrypoint: config.json migrated (applied new defaults)') else: print('entrypoint: config.json OK (no migration needed)') " 2>/dev/null || true # Enable Citedy MCP client if API key is provided at runtime if [ -n "$CITEDY_API_KEY" ]; then CONFIG="${ADCLAW_WORKING_DIR}/config.json" if [ -f "$CONFIG" ]; then python3 -c " import json, os cfg_path = os.environ.get('ADCLAW_WORKING_DIR', '/app/working') + '/config.json' with open(cfg_path) as f: cfg = json.load(f) mcp = cfg.setdefault('mcp', {}).setdefault('clients', {}) key = os.environ['CITEDY_API_KEY'] mcp['citedy'] = { 'name': 'citedy_mcp', 'description': 'Citedy SEO & Marketing Tools (70+ tools)', 'enabled': True, 'transport': 'streamable_http', 'url': 'https://mcp.citedy.com/mcp', 'headers': {'Authorization': f'Bearer {key}', 'Accept': 'application/json, text/event-stream'}, 'env': {'CITEDY_API_KEY': key}, } with open(cfg_path, 'w') as f: json.dump(cfg, f, indent=2) print('entrypoint: Citedy MCP client enabled') " 2>/dev/null || true fi fi # Enable Exa search MCP if API key is provided at runtime if [ -n "$EXA_API_KEY" ]; then CONFIG="${ADCLAW_WORKING_DIR}/config.json" if [ -f "$CONFIG" ]; then python3 -c " import json, os cfg_path = os.environ.get('ADCLAW_WORKING_DIR', '/app/working') + '/config.json' with open(cfg_path) as f: cfg = json.load(f) mcp = cfg.setdefault('mcp', {}).setdefault('clients', {}) key = os.environ['EXA_API_KEY'] mcp['exa'] = { 'name': 'exa_mcp', 'description': 'Exa AI search: web, code, people, companies', 'enabled': True, 'command': 'npx', 'args': ['-y', 'exa-mcp-server'], 'env': {'EXA_API_KEY': key}, } with open(cfg_path, 'w') as f: json.dump(cfg, f, indent=2) print('entrypoint: Exa MCP client enabled') " 2>/dev/null || true fi fi # Enable Telegram channel if bot token is provided at runtime if [ -n "$TELEGRAM_BOT_TOKEN" ]; then CONFIG="${ADCLAW_WORKING_DIR}/config.json" if [ -f "$CONFIG" ]; then python3 -c " import json, os cfg_path = os.environ.get('ADCLAW_WORKING_DIR', '/app/working') + '/config.json' with open(cfg_path) as f: cfg = json.load(f) tg = cfg.setdefault('channels', {}).setdefault('telegram', {}) tg['enabled'] = True tg['bot_token'] = os.environ['TELEGRAM_BOT_TOKEN'] with open(cfg_path, 'w') as f: json.dump(cfg, f, indent=2) print('entrypoint: Telegram channel enabled') " 2>/dev/null || true fi fi # Sync new built-in skills to active_skills (non-destructive: skip existing) python3 -c " from adclaw.agents.skills_manager import sync_skills_to_working_dir synced, skipped = sync_skills_to_working_dir(skill_names=None, force=False) if synced: print(f'entrypoint: synced {synced} new skill(s) to active_skills') else: print(f'entrypoint: all skills up to date ({skipped} existing)') " 2>/dev/null || true # Ensure working dirs are owned by MaskanX user (entrypoint runs as root) mkdir -p /app/logs 2>/dev/null || true chown -R maskanx:maskanx "${ADCLAW_WORKING_DIR}" "${ADCLAW_SECRET_DIR}" /app/logs 2>/dev/null || true # Run Postgres migrations when requested. Keep JSON/file mode as the default so # local users can still start the app without a database. case "$(printf '%s' "$ADCLAW_STORAGE_BACKEND" | tr '[:upper:]' '[:lower:]')" in postgres|postgresql|pg) echo "entrypoint: PostgreSQL storage enabled, running migrations..." maskanx db migrate ;; esac # Generate supervisord.conf from templates based on MASKANX_VARIANT. # core → base template only (supervisord + app). # browser/full → base + browser template (adds dbus, xvfb, xfce4, pinchtab). MASKANX_VARIANT="${MASKANX_VARIANT:-${ADCLAW_VARIANT:-full}}" SUPERVISORD_CONF="/etc/supervisor/conf.d/supervisord.conf" envsubst '${ADCLAW_PORT}' \ < /etc/supervisor/conf.d/supervisord.conf.template \ > "$SUPERVISORD_CONF" if [ "$MASKANX_VARIANT" != "core" ] && [ -f /etc/supervisor/conf.d/supervisord.browser.conf.template ]; then cat /etc/supervisor/conf.d/supervisord.browser.conf.template >> "$SUPERVISORD_CONF" echo "entrypoint: supervisord configured with browser programs (variant=${MASKANX_VARIANT})" else echo "entrypoint: supervisord configured without browser (variant=${MASKANX_VARIANT})" fi exec /usr/bin/supervisord -c "$SUPERVISORD_CONF"