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>
79 lines
3.1 KiB
PHP
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);
|
|
}
|