feat: add 宝塔面板 (Baota Panel) CLI harness#324
Conversation
- Full CLI for server management: sites, SSL, databases, DNS, firewall, cron - Bridge pattern via sudo + panel Python for all operations - 121 tests (85 unit + 36 E2E), 100% pass rate - SKILL.md for AI agent discovery - registry.json entry in devops category
…S flags by provider, remove debug prints
…ildcards, JSON status, E2E module fallback
…hlex, bridge overwrite
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3037d7767
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return format_output({'status': False, 'msg': f'Unsupported provider: {provider}'}, use_json) | ||
| pdata = {} | ||
| for f in prov['fields']: | ||
| val = kwargs.get(f.lower()) |
There was a problem hiding this comment.
Honor DNS credentials passed on CLI
When config dns set dnspod --id ... --token ... is used, baota_cli passes provider-specific keys such as ID/Token (or AccessKeyId/AccessKeySecret) into set_dns_api, but this lookup lowercases the field name before reading kwargs. The explicit CLI credentials are therefore ignored and the command reports “No credentials” unless matching environment variables are set, so the documented config dns set flow cannot configure DNS APIs from its flags.
Useful? React with 👍 / 👎.
| data = call_bridge('list_dns_records', domain=domain) | ||
| records = data if isinstance(data, list) else [] | ||
| return format_output(records, use_json, f'DNS Records for {domain}') |
There was a problem hiding this comment.
Surface DNS list errors instead of empty success
When no DNS credentials are configured, the bridge returns an error dict (status: False), but this code coerces any non-list response to [] and format_output then reports a successful empty record list under --json. In that setup-failure scenario, agents and users are told the domain has no records instead of being told to run config dns set, which makes DNS troubleshooting and automation decisions incorrect.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95d2a7adb2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| result = call_bridge('apply_ssl', timeout=120, site_id=site_id, domains=domain_list, | ||
| auth_type=auth_type, auth_to='') |
There was a problem hiding this comment.
Pass a valid auth_to to SSL validation
When sites ssl apply or sites ssl deploy uses the default --auth dns, this sends auth_to='' into aaPanel's apply_cert_api. I checked the upstream acme_v2 implementation: DNS validation expects auth_to to be either dns for manual DNS or a Provider|account|token API descriptor, and an empty string reaches create_dns_record and fails with dns_name or account or token is empty; HTTP validation similarly expects a site id/path. As a result the default SSL workflow cannot issue certificates even after config dns set succeeds.
Useful? React with 👍 / 👎.
| for name in ['DNSPodDns', 'AliyunDns', 'CloudFlareDns']: | ||
| creds = dns_config.get(name, []) | ||
| if creds and len(creds) > 0: | ||
| return name, creds[0] |
There was a problem hiding this comment.
Select DNS credentials by domain
When more than one DNS provider is configured, record operations always use the first provider in this hard-coded order, not the provider that owns the requested domain. For example, if DNSPod credentials exist and Cloudflare credentials are later added for a Cloudflare-hosted domain, config dns record add/list/delete example.com ... still sends the request to DNSPod and either mutates the wrong account or returns misleading empty/error results. The bridge should choose credentials based on the domain/provider mapping or accept an explicit provider.
Useful? React with 👍 / 👎.
…der for DNS records
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b635b5a2b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| for label, output in results: | ||
| click.echo(f'--- {label} ---') | ||
| click.echo(output) |
There was a problem hiding this comment.
Emit valid JSON from SSL deploy
When --json is used with sites ssl deploy, this loop still prints human-readable section headers and then two separate JSON strings, so the command's overall stdout is not parseable JSON even though automation requested JSON output. This only affects the deploy workflow because it composes the SSL and cron results manually instead of returning one structured object through format_output.
Useful? React with 👍 / 👎.
| if action == 'add': | ||
| full_name = kw.get('subdomain', '') + '.' + domain | ||
| resp = req.post('https://api.cloudflare.com/client/v4/zones/' + zone_id + '/dns_records', | ||
| headers=headers, json={'type': kw.get('type_', 'A'), 'name': full_name, 'content': kw.get('value', ''), 'ttl': int(kw.get('ttl', 600))}, timeout=30).json() |
There was a problem hiding this comment.
Handle Cloudflare apex records explicitly
When adding a Cloudflare record for the zone apex, callers normally use @ (or sometimes an empty subdomain), but this concatenation sends @.example.com or .example.com as the Cloudflare record name instead of example.com. That makes root-record creation fail for Cloudflare while DNSPod/Aliyun receive the subdomain separately, so the Cloudflare branch should special-case @/empty to the bare domain.
Useful? React with 👍 / 👎.
|
Thanks for the substantial Baota harness work. The latest review round still has a couple of behavior blockers:
Please add regression coverage for both JSON deploy output and Cloudflare apex record construction. Given this harness touches production panel operations, a concise real-backend smoke/limitations note would also help reviewers understand what was actually exercised. |
|
Registry placement, install metadata, and the harness structure are correct. The blockers are credential handling and two behavior bugs already flagged on HEAD that haven't been fixed. Security (blocking):
Behavior (already flagged on HEAD
Please add regression tests for 5 and 6 — they were requested last round and still aren't there. The earlier Codex round (SSL |
Description
Adds
cli-anything-baota— a CLI harness for 宝塔面板 (Baota Panel), a popular Linux server management panel. Provides agent-native command-line access to sites, SSL, databases, DNS, firewall, cron, files, and panel configuration.This is a re-submission of #323, now targeting the
feat/baotabranch as required by CONTRIBUTING.md.Closes #323
Type of Change
For New Software CLIs (in-repo)
BAOTA.mdSOP document exists atbaota/agent-harness/BAOTA.mdSKILL.mdexists atskills/cli-anything-baota/SKILL.mdSKILL.mdexists atcli_anything/baota/skills/SKILL.mdcli_anything/baota/tests/test_core.pyare present and pass without backendcli_anything/baota/tests/test_full_e2e.pyare presentREADME.mdincludes the new software (with link to harness directory)registry.jsonincludes an entry withsource_url: null(see Contributing guide)repl_skin.pyinutils/is an unmodified copy from the pluginGeneral Checklist
--jsonflag is supported on any new commandsfeat:,fix:,docs:,test:)Test Results
Changes from #323
The previous PR #323 was closed so this submission follows the contribution guide's requirement to develop on a feature branch (
feat/baota) rather thanmaindirectly. All review feedback from the prior round (P1 files/export shadowing, P2 DNS flag mapping, P1 bridge auto-deploy, P2 cron wildcards, P2 JSON status, P2 REPL shlex, multi-provider DNS support) is already addressed on this branch.