openscribe/site/tools/apiscp.php
Laurence 8fd9b694f8
Some checks failed
ci / firmware (pull_request) Failing after 31s
ci / openapi (pull_request) Failing after 33s
ci / emulator (pull_request) Failing after 24s
ci / server (pull_request) Failing after 37s
docs+site: Nightjar brand, hosted service, infrastructure plan, saved site
Persists the branding/commercial/infrastructure work (previously only in chat and
on the live site) into version control.

What changed:
- docs/naming.md: due-diligence and the decision to brand the product Nightjar
  (OpenScribe domain/GitHub/trademark collisions; alternatives screened; why Nightjar).
- docs/hosted-service.md: the commercial model - plan tiers (Self-host / Cloud
  Starter / Cloud Pro / Private), indicative pricing, backend mapping, and the
  build-to-sell roadmap (metering, billing, provisioning, DPA).
- docs/infrastructure.md: backend infra plan. Primary option = self-host on the
  3x Minisforum MS-02 cluster (one with an RTX 3050 6GB) fronted by a Cloudflare
  Tunnel, with node roles, the 3050 capacity reality, caveats, and a Hetzner cloud
  fallback. Provision-later.
- site/: reproducible marketing-site source - block content for all 11 pages
  (rebranded to Nightjar), the navigation, the Contact Form 7 config (honeypot),
  the ApisCP SOAP helper (tools/apiscp.php, no secret), and a README on how the
  WordPress site is built and managed via the API.
- state/: DECISIONS (Nightjar rebrand, hosted service, MS-02 backend), PROJECT
  (brand + commercial section), TODO (rename decision, trademark, mailbox, pricing,
  hosted-service build, infra provisioning).

Why:
- User asked to save everything to the repo. Captures the product rebrand, the
  commercialisation plan, and the infrastructure decision so a cold session has the
  full picture.

Notes:
- The repo is still named `openscribe`; the product/brand is Nightjar. A full
  codebase rename is deferred (tracked in TODO + docs/naming.md).
- No secrets committed: the ApisCP API key is read from a local scratch file, never
  the repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 22:24:24 +01:00

79 lines
3.1 KiB
PHP

<?php declare(strict_types=1);
// ApisCP SOAP helper for openscribe.12hobbies.studio provisioning.
// Reads the API key from a scratch file so the secret is never embedded here.
// Usage:
// php apiscp.php --functions [filter] list SOAP operations (optionally grep)
// php apiscp.php <module_function> [json-arg ...]
// JSON args are decoded (so '["openscribe","/var/www/openscribe"]'-style or bare values work).
$key = trim((string)file_get_contents('C:/temp/claudetemp/apiscp.key'));
if ($key === '') { fwrite(STDERR, "no api key\n"); exit(2); }
$endpoint = 'https://howler.qsplace.co.uk:2083';
// Use a locally-cached WSDL to avoid a second HTTPS fetch; fall back to remote.
$wsdl = is_file('C:/temp/claudetemp/apnscp.wsdl')
? 'C:/temp/claudetemp/apnscp.wsdl'
: $endpoint . '/apnscp.wsdl';
// The panel cert is valid, but this PHP build ships no CA bundle, so relax verify.
$ctx = stream_context_create(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
$client = new SoapClient($wsdl, [
'connection_timeout' => 30,
'location' => $endpoint . '/soap?authkey=' . $key,
'uri' => 'urn:net.apnscp.soap',
'trace' => 1,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_MEMORY,
'stream_context' => $ctx,
]);
$cmd = $argv[1] ?? '';
// Drive wp-cli with the command string read from a file (avoids all shell-quoting pain
// when passing large HTML content). Usage: --wpcli-file <cmdfile> <hostname> <path>
if ($cmd === '--wpcli-file') {
$command = rtrim((string)file_get_contents($argv[2]), "\r\n");
$host = $argv[3];
$path = $argv[4] ?? '';
try {
$res = $client->__soapCall('wordpress_cli', [$command, [], $host, $path]);
echo json_encode($res, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), "\n";
exit(isset($res['success']) && !$res['success'] ? 1 : 0);
} catch (Throwable $e) {
fwrite(STDERR, 'ERROR: ' . $e->getMessage() . "\n");
exit(1);
}
}
if ($cmd === '--functions') {
$filter = strtolower($argv[2] ?? '');
foreach ($client->__getFunctions() as $fn) {
if ($filter === '' || strpos(strtolower($fn), $filter) !== false) {
echo $fn, "\n";
}
}
exit(0);
}
if ($cmd === '') { fwrite(STDERR, "usage: php apiscp.php <module_function> [json-arg ...]\n"); exit(2); }
$args = [];
foreach (array_slice($argv, 2) as $a) {
if (strncmp($a, '@@', 2) === 0) { // @@path -> file contents as a string arg
$args[] = (string)file_get_contents(substr($a, 2));
continue;
}
$d = json_decode($a, true);
$args[] = ($d === null && $a !== 'null') ? $a : $d;
}
try {
$res = $client->__soapCall($cmd, $args);
echo json_encode($res, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), "\n";
} catch (Throwable $e) {
fwrite(STDERR, 'ERROR: ' . $e->getMessage() . "\n");
// surface the raw response for debugging (no secrets in it)
if (method_exists($client, '__getLastResponse')) {
fwrite(STDERR, "LAST RESPONSE: " . substr((string)$client->__getLastResponse(), 0, 600) . "\n");
}
exit(1);
}