<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Host power control. Stops the array, shuts the machine down, or reboots it. The three
//   most destructive operations the plugin can perform, deliberately isolated in one small
//   file rather than folded into a general-purpose action endpoint.
//
// OPERATIONAL MODEL
//   The command is detached and the response returns immediately. A shutdown kills the web
//   server that is serving this request, so waiting on the child would mean the browser sees
//   a connection reset rather than a result. Backgrounding is what lets the UI acknowledge
//   the action before the host stops answering.
//
//   ok:true therefore means "accepted and dispatched", not "completed". Nothing can report
//   the completion of an operation that ends the process reporting it.
//
// DESIGN PRINCIPLES
//   Three fixed commands, chosen by name.
//       The action string selects among three compiled-in literals through a match. No part
//       of the request is ever interpolated into the command, so there is no injection
//       surface to protect — the request cannot express a command that is not one of these
//       three.
//
//   Reads its body as JSON, not as a form.
//       php://input is parsed directly, because POST bodies are unreliable on this Unraid
//       PHP setup and reading the raw stream sidesteps the form parser entirely.
//
//   No scheduling, no delay, no cancel.
//       There is nothing to cancel because there is no window in which to cancel it. A delayed
//       shutdown with a cancel path would need state, and state that can be wrong about
//       whether a host is about to power off is worse than no state.
//
// OPERATIONAL SAFEGUARDS
//   POST only, checked first.
//       A GET cannot power off the host, so no link, prefetch, bookmark, or browser history
//       entry can reach these commands.
//
//   Strict allowlist with strict comparison.
//       in_array($action, ['stop','shutdown','restart'], true) — the third argument matters.
//       Without it PHP's loose comparison would accept values that are not these strings, and
//       the match below would then have no arm for them.
//
//   The match has no default arm, and that is safe only because of the check above.
//       An unmatched action would raise \UnhandledMatchError, which is a hard failure rather
//       than a wrong command — but it is unreachable, because the allowlist already rejected
//       everything that is not one of the three. The two guards are deliberately redundant.
//
//   The audit line is written before the command is dispatched, not after.
//       Ordering is the whole point: after a shutdown there is no "after". Timestamp, action
//       and originating IP land in actions.log with LOCK_EX while the machine is still
//       running, so a power event always has a record of who asked for it.
//
//   The audit write is best-effort and never blocks the action.
//       @ and FILE_APPEND — a full or read-only flash must not be able to prevent a shutdown.
//       Losing the log line is the lesser failure; the log is gitignored and local by design.
//
// REQUEST
//   POST {"action":"stop"}       stop the array (mdcmd stop)
//   POST {"action":"shutdown"}   power off the host
//   POST {"action":"restart"}    reboot the host
//
// RESPONSE
//   {"ok":true}                             accepted and dispatched — not completed
//   {"ok":false,"error":"POST only"|"invalid action"}
//
// DEPENDS ON
//   include/config.php          SCRIPTS_DIR (audit log location)
//   /usr/local/sbin/mdcmd       array control
//   /sbin/shutdown              host power control
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/config.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['ok' => false, 'error' => 'POST only']);
    exit;
}

$body  = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $body['action'] ?? '';

$allowed = ['stop', 'shutdown', 'restart'];
if (!in_array($action, $allowed, true)) {
    echo json_encode(['ok' => false, 'error' => 'invalid action']);
    exit;
}

$cmd = match($action) {
    'stop'     => '/usr/local/sbin/mdcmd stop',
    'shutdown' => '/sbin/shutdown -h now',
    'restart'  => '/sbin/shutdown -r now',
};

$logLine = date('Y-m-d H:i:s') . " action={$action} ip=" . ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . "\n";
@file_put_contents(SCRIPTS_DIR . '/actions.log', $logLine, FILE_APPEND | LOCK_EX);

exec($cmd . ' > /dev/null 2>&1 &');
echo json_encode(['ok' => true]);
