diff --git a/.github/workflows/mlecovisuals-discovery.yml b/.github/workflows/mlecovisuals-discovery.yml new file mode 100644 index 0000000..6342d2a --- /dev/null +++ b/.github/workflows/mlecovisuals-discovery.yml @@ -0,0 +1,122 @@ +name: Mleco Visuals Production Discovery + +on: + push: + branches: [mlecovisuals-release] + workflow_dispatch: + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + signal: + runs-on: ubuntu-latest + steps: + - name: Signal that Actions runner started + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + curl -fsS -X POST \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GH_REPO/issues/1/comments" \ + -d '{"body":"MCP_DISCOVERY_RUNNER_STARTED commit=${{ github.sha }}"}' + + discover: + needs: signal + runs-on: ubuntu-latest + environment: production + env: + SERVER_HOST: ${{ secrets.SERVER_HOST }} + SERVER_USER: ${{ secrets.SERVER_USER }} + SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: '1' + steps: + - name: Validate management connection configuration + run: | + if [ -z "$SERVER_HOST" ] || [ -z "$SERVER_USER" ] || [ -z "$SERVER_SSH_KEY" ]; then + curl -fsS -X POST \ + -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GH_REPO/issues/$PR_NUMBER/comments" \ + -d '{"body":"MCP_DISCOVERY_SECRETS_MISSING_OR_UNAVAILABLE"}' + exit 2 + fi + echo "MANAGEMENT_CONFIG=OK" + + - name: Read-only discovery and publish sanitized report + uses: appleboy/ssh-action@v1.0.3 + env: + GH_TOKEN: ${{ env.GH_TOKEN }} + GH_REPO: ${{ env.GH_REPO }} + PR_NUMBER: ${{ env.PR_NUMBER }} + with: + host: ${{ env.SERVER_HOST }} + username: ${{ env.SERVER_USER }} + key: ${{ env.SERVER_SSH_KEY }} + port: ${{ secrets.SERVER_PORT || 22 }} + command_timeout: 5m + envs: GH_TOKEN,GH_REPO,PR_NUMBER + script: | + set -u + OUT=$(mktemp) + { + echo "DISCOVERY_VERSION=3" + echo "MANAGEMENT_HOST=$(hostname)" + if [ -f /root/.secrets/spaceweb-panel.env ]; then echo "MGMT_SPACEWEB_CREDS=YES"; else echo "MGMT_SPACEWEB_CREDS=NO"; fi + python3 - <<'PY' + import json, pathlib, subprocess + cfg = pathlib.Path('/a0/usr/projects/mcp_server/.runtime/ssh_targets.json') + if not cfg.exists(): + print('TARGET_CATALOG=NOT_FOUND') + raise SystemExit(0) + try: + targets = json.loads(cfg.read_text()) + except Exception as exc: + print('TARGET_CATALOG=INVALID', type(exc).__name__) + raise SystemExit(0) + print('TARGET_COUNT=' + str(len(targets))) + remote_probe = r'''echo REMOTE_HOST=$(hostname); if command -v nginx >/dev/null 2>&1; then echo NGINX=YES; nginx -T 2>/dev/null | grep -E 'server_name[^;]*(globalsystem\.cc|mlecovisuals)' | head -40 || true; else echo NGINX=NO; fi; if [ -f /root/.secrets/spaceweb-panel.env ]; then echo SPACEWEB_CREDS=YES; else echo SPACEWEB_CREDS=NO; fi; if command -v ss >/dev/null 2>&1; then ss -ltn 2>/dev/null | grep -E ':(80|443)[[:space:]]' | head -10 || true; fi''' + for name, raw in sorted(targets.items()): + if not isinstance(raw, dict): + continue + host = str(raw.get('host') or '').strip() + user = str(raw.get('user') or 'root').strip() + port = str(raw.get('port') or 22) + key = str(raw.get('key_path') or '').strip() + print(f'TARGET name={name} host={host} user={user} port={port} key_present={"YES" if key and pathlib.Path(key).exists() else "NO"} password_configured={"YES" if raw.get("password") else "NO"}') + if not host or not key or not pathlib.Path(key).exists(): + continue + cmd = ['ssh','-i',key,'-p',port,'-o','BatchMode=yes','-o','ConnectTimeout=8','-o','StrictHostKeyChecking=no','-o','UserKnownHostsFile=/dev/null',f'{user}@{host}',remote_probe] + try: + cp = subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=25) + print(f'PROBE_BEGIN {name} rc={cp.returncode}') + print(cp.stdout[:12000]) + print(f'PROBE_END {name}') + except Exception as exc: + print(f'PROBE_ERROR {name} {type(exc).__name__}') + PY + echo "PUBLIC_DNS_BEGIN" + getent ahostsv4 globalsystem.cc 2>/dev/null | head -5 || true + getent ahostsv4 mlecovisuals.globalsystem.cc 2>/dev/null | head -5 || true + echo "PUBLIC_DNS_END" + } >"$OUT" 2>&1 + cat "$OUT" + python3 - "$OUT" <<'PY' | curl -fsS -X POST \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/$GH_REPO/issues/$PR_NUMBER/comments" \ + --data-binary @- + import json, pathlib, sys + text = pathlib.Path(sys.argv[1]).read_text(errors='replace')[:50000] + print(json.dumps({'body': '### Mleco Visuals production discovery\n```text\n' + text + '\n```'})) + PY + rm -f "$OUT"