|
| 1 | +--- |
| 2 | +name: bin-script |
| 3 | +description: Creates a new CLI script in bin/ for DirectAdmin API operations. Use when user says 'add bin script', 'create CLI tool', 'new admin script', or needs a standalone operational script like bin/add_user.php or bin/suspend_user.php. Covers autoload require, HTTPSocket setup, SSL connect, arg parsing, and result printing. Do NOT use for src/ class code or Plugin.php event handlers. |
| 4 | +--- |
| 5 | +# bin-script |
| 6 | + |
| 7 | +## Critical |
| 8 | + |
| 9 | +- All scripts live in `bin/` — never `src/`. |
| 10 | +- Always use `use Detain\MyAdminDirectAdminWeb\HTTPSocket;` with the autoloader — never include the HTTPSocket source file directly (that is legacy, see `bin/add_domain.php`). |
| 11 | +- Port is always `2222` — never 80, 443, or 8080. |
| 12 | +- SSL: use the ssl scheme prefix when `$server_ssl == 'Y'`; otherwise connect bare host. Never use the http scheme prefix in new scripts. |
| 13 | +- Use `fetch_parsed_body()` for structured results; use `fetch_body()` only when you need raw string output. |
| 14 | +- Check `$result['error'] != "0"` for API errors; error detail is in `$result['text']` and `$result['details']`. |
| 15 | +- Never interpolate raw `$_GET`/`$_POST` into queries — these are CLI scripts; use `$_SERVER['argv']` for input. |
| 16 | + |
| 17 | +## Instructions |
| 18 | + |
| 19 | +### 1. Choose the bootstrap style |
| 20 | + |
| 21 | +Two patterns exist. Pick one based on whether the script needs live DB server credentials: |
| 22 | + |
| 23 | +**A — Hardcoded / dev credentials** (like `bin/add_user.php`, `bin/suspend_user.php`): |
| 24 | +```php |
| 25 | +require_once('../vendor/autoload.php'); |
| 26 | +``` |
| 27 | +Used for quick standalone scripts where credentials are set directly in the file. |
| 28 | + |
| 29 | +**B — MyAdmin DB lookup** (like `bin/show_all_users.php`, `bin/server_information.php`): |
| 30 | +```php |
| 31 | +include_once __DIR__.'/../../../../include/functions.inc.php'; |
| 32 | +``` |
| 33 | +Used when the script must look up a real server from `website_masters`. Requires running inside a MyAdmin checkout. |
| 34 | + |
| 35 | +Verify your chosen autoload path resolves before writing the rest of the script. |
| 36 | + |
| 37 | +### 2. Write the file header |
| 38 | + |
| 39 | +```php |
| 40 | +<?php |
| 41 | + |
| 42 | +use Detain\MyAdminDirectAdminWeb\HTTPSocket; |
| 43 | + |
| 44 | +// Step 1 bootstrap here (require_once or include_once) |
| 45 | +``` |
| 46 | + |
| 47 | +### 3. Set server connection variables |
| 48 | + |
| 49 | +For pattern A: |
| 50 | +```php |
| 51 | +$server_login = 'admin'; |
| 52 | +$server_pass = 'admin_password'; |
| 53 | +$server_host = 'da1.is.cc'; // where the API connects to |
| 54 | +$server_ssl = 'Y'; |
| 55 | +$server_port = 2222; |
| 56 | +``` |
| 57 | + |
| 58 | +For pattern B (DB lookup): |
| 59 | +```php |
| 60 | +$db = get_module_db('webhosting'); |
| 61 | +if (count($_SERVER['argv']) < 2) { |
| 62 | + die("Call like {$_SERVER['argv'][0]} <hostname>\nwhere <hostname> is a webhosting server such as webhosting2004.interserver.net"); |
| 63 | +} |
| 64 | +$db->query("select * from website_masters where website_name='".$db->real_escape($_SERVER['argv'][1])."'", __LINE__, __FILE__); |
| 65 | +if ($db->num_rows() == 0) { |
| 66 | + die("Invalid Server {$_SERVER['argv'][1]} passed, did not match any webhosting server name"); |
| 67 | +} |
| 68 | +$db->next_record(MYSQL_ASSOC); |
| 69 | +$server_name = $db->Record['website_ip']; |
| 70 | +$server_port = 2222; |
| 71 | +$password = $db->Record['website_key']; |
| 72 | +``` |
| 73 | + |
| 74 | +### 4. Connect HTTPSocket |
| 75 | + |
| 76 | +For pattern A: |
| 77 | +```php |
| 78 | +$sock = new HTTPSocket(); |
| 79 | +if ($server_ssl == 'Y') { |
| 80 | + $sock->connect('ssl://'.$server_host, $server_port); |
| 81 | +} else { |
| 82 | + $sock->connect($server_host, $server_port); |
| 83 | +} |
| 84 | +$sock->set_login($server_login, $server_pass); |
| 85 | +``` |
| 86 | + |
| 87 | +For pattern B: |
| 88 | +```php |
| 89 | +$sock = new HTTPSocket(); |
| 90 | +$sock->connect('ssl://'.$server_name, $server_port); |
| 91 | +$sock->set_login('admin', $password); |
| 92 | +``` |
| 93 | + |
| 94 | +For POST requests, add `$sock->set_method('POST');` immediately after `set_login()`. |
| 95 | + |
| 96 | +For admin impersonation: `$sock->set_login('admin|'.$username, $adminPass);` |
| 97 | + |
| 98 | +Verify `HTTPSocket` is importable: run `vendor/bin/phpunit tests/HTTPSocketTest.php`. |
| 99 | + |
| 100 | +### 5. Issue the DirectAdmin API query |
| 101 | + |
| 102 | +GET (no payload — pass params as array): |
| 103 | +```php |
| 104 | +$sock->query('/CMD_API_ENDPOINT', ['param' => $value]); |
| 105 | +``` |
| 106 | + |
| 107 | +POST (with payload array): |
| 108 | +```php |
| 109 | +$sock->query( |
| 110 | + '/CMD_API_ENDPOINT', |
| 111 | + [ |
| 112 | + 'action' => 'create', |
| 113 | + 'key' => $value, |
| 114 | + ] |
| 115 | +); |
| 116 | +``` |
| 117 | + |
| 118 | +Common endpoints and their required fields: |
| 119 | + |
| 120 | +| Action | Endpoint | Key fields | |
| 121 | +|---|---|---| |
| 122 | +| Create user | `/CMD_API_ACCOUNT_USER` | `action=create`, `username`, `email`, `passwd`, `passwd2`, `domain`, `package`, `ip` | |
| 123 | +| Suspend user | `/CMD_API_SELECT_USERS` | `location=CMD_SELECT_USERS`, `suspend=Suspend`, `select0=$username` | |
| 124 | +| Unsuspend user | `/CMD_API_SELECT_USERS` | `location=CMD_SELECT_USERS`, `suspend=Unsuspend`, `select0=$username` | |
| 125 | +| Delete user | `/CMD_API_SELECT_USERS` | `confirmed=Confirm`, `delete=yes`, `select0=$username` | |
| 126 | +| Show user config | `/CMD_API_SHOW_USER_CONFIG` | `user=$username` | |
| 127 | +| All users | `/CMD_API_SHOW_ALL_USERS` | — | |
| 128 | +| Server stats | `/CMD_API_ADMIN_STATS` | — | |
| 129 | + |
| 130 | +### 6. Fetch and print the result |
| 131 | + |
| 132 | +```php |
| 133 | +$result = $sock->fetch_parsed_body(); // array via parse_str |
| 134 | +print_r($result); |
| 135 | + |
| 136 | +if ($result['error'] != '0') { |
| 137 | + echo "<b>Error: <br>\n"; |
| 138 | + echo $result['text']."<br>\n"; |
| 139 | + echo $result['details']."<br></b>\n"; |
| 140 | +} else { |
| 141 | + echo "Success<br>\n"; |
| 142 | +} |
| 143 | + |
| 144 | +exit(0); |
| 145 | +``` |
| 146 | + |
| 147 | +Omit the error-check block for read-only/info queries that don't return an `error` key (e.g. `CMD_API_SHOW_ALL_USERS` returns a list). |
| 148 | + |
| 149 | +## Examples |
| 150 | + |
| 151 | +**User says:** "Add a bin script to add a subdomain for a user" |
| 152 | + |
| 153 | +**Actions taken:** |
| 154 | +1. Pattern A chosen (dev/standalone, no DB needed). |
| 155 | +2. File created at `bin/add_subdomain.php`. |
| 156 | +3. Credentials set as variables; SSL connect to port 2222. |
| 157 | +4. `set_method('POST')` added because this is a write operation. |
| 158 | +5. Query to `/CMD_API_SUBDOMAINS` with `action=create`. |
| 159 | +6. `fetch_parsed_body()` called, error checked. |
| 160 | + |
| 161 | +**Result:** |
| 162 | +```php |
| 163 | +<?php |
| 164 | + |
| 165 | +use Detain\MyAdminDirectAdminWeb\HTTPSocket; |
| 166 | + |
| 167 | +require_once('../vendor/autoload.php'); |
| 168 | + |
| 169 | +$server_login = 'admin'; |
| 170 | +$server_pass = 'admin_password'; |
| 171 | +$server_host = 'da1.is.cc'; // where the API connects to |
| 172 | +$server_ssl = 'Y'; |
| 173 | +$server_port = 2222; |
| 174 | + |
| 175 | +$username = 'targetuser'; |
| 176 | +$subdomain = 'sub'; |
| 177 | +$domain = 'example.com'; |
| 178 | + |
| 179 | +$sock = new HTTPSocket(); |
| 180 | +if ($server_ssl == 'Y') { |
| 181 | + $sock->connect('ssl://'.$server_host, $server_port); |
| 182 | +} else { |
| 183 | + $sock->connect($server_host, $server_port); |
| 184 | +} |
| 185 | +$sock->set_login('admin|'.$username, $server_pass); |
| 186 | +$sock->set_method('POST'); |
| 187 | + |
| 188 | +$sock->query( |
| 189 | + '/CMD_API_SUBDOMAINS', |
| 190 | + [ |
| 191 | + 'action' => 'create', |
| 192 | + 'domain' => $domain, |
| 193 | + 'subdomain' => $subdomain, |
| 194 | + ] |
| 195 | +); |
| 196 | + |
| 197 | +$result = $sock->fetch_parsed_body(); |
| 198 | +print_r($result); |
| 199 | + |
| 200 | +if ($result['error'] != '0') { |
| 201 | + echo "<b>Error creating subdomain {$subdomain}.{$domain}:<br>\n"; |
| 202 | + echo $result['text']."<br>\n"; |
| 203 | + echo $result['details']."<br></b>\n"; |
| 204 | +} else { |
| 205 | + echo "Subdomain {$subdomain}.{$domain} created<br>\n"; |
| 206 | +} |
| 207 | + |
| 208 | +exit(0); |
| 209 | +``` |
| 210 | + |
| 211 | +## Common Issues |
| 212 | + |
| 213 | +**`Class 'Detain\MyAdminDirectAdminWeb\HTTPSocket' not found`** |
| 214 | +- You used a direct include of the HTTPSocket source file instead of the autoloader. |
| 215 | +- Fix: use `require_once('../vendor/autoload.php');` and add `use Detain\MyAdminDirectAdminWeb\HTTPSocket;` at top. |
| 216 | +- Verify: `vendor/bin/phpunit tests/HTTPSocketTest.php` must pass. |
| 217 | + |
| 218 | +**`$result['error']` is undefined / `print_r` shows empty array** |
| 219 | +- You called `fetch_parsed_body()` on an endpoint that returns raw text (e.g. `CMD_API_SHOW_ALL_USERS` returns a list). |
| 220 | +- Fix: switch to `fetch_body()` and parse manually, or check DirectAdmin docs for that endpoint's response format. |
| 221 | + |
| 222 | +**`Connection refused` or SSL handshake failure** |
| 223 | +- Port 2222 must be open on the target server. |
| 224 | +- The ssl scheme prefix is required when `$server_ssl == 'Y'` — omitting it causes a plaintext-on-TLS mismatch. |
| 225 | +- Fix: confirm with `openssl s_client -connect da1.is.cc:2222`. |
| 226 | + |
| 227 | +**Pattern B: `Invalid Server ... passed, did not match any webhosting server name`** |
| 228 | +- The `$_SERVER['argv'][1]` value doesn't match a `website_name` in `website_masters`. |
| 229 | +- Fix: check valid hostnames with `SELECT website_name FROM website_masters LIMIT 10;`. |
| 230 | + |
| 231 | +**Script works locally but `include_once __DIR__.'/../../../..'` fails in another checkout depth** |
| 232 | +- Pattern B hard-codes four levels up to the MyAdmin root. |
| 233 | +- Fix: use pattern A with hardcoded credentials for portable standalone scripts, or adjust the relative path to match your checkout structure. |
0 commit comments