deploy
All checks were successful
Deploy / deploy-staging (push) Successful in 24s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-06-20 01:50:07 +02:00
parent 3f81f7a670
commit 2cdd14c400
210 changed files with 55853 additions and 36 deletions

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
require_once dirname(__DIR__) . '/config/fileload.php';
$targetModule = '';
$statusOnly = false;
foreach (array_slice($argv, 1) as $arg) {
$arg = trim((string) $arg);
if ($arg === '') {
continue;
}
if ($arg === '--status') {
$statusOnly = true;
continue;
}
if (str_starts_with($arg, '--module=')) {
$targetModule = trim(substr($arg, 9));
continue;
}
if ($targetModule === '') {
$targetModule = $arg;
}
}
$modules = app()->modules()->all();
$results = [];
foreach ($modules as $name => $meta) {
if (!is_string($name) || $name === '') {
continue;
}
if ($targetModule !== '' && $targetModule !== $name) {
continue;
}
if (empty($meta['enabled'])) {
continue;
}
$intervalDefs = app()->modules()->intervalTasks($name);
$cronDefs = app()->modules()->cronTasks($name);
if ($intervalDefs === [] && $cronDefs === []) {
continue;
}
if ($statusOnly) {
$results[$name] = [
'interval' => app()->modules()->intervalTaskStatuses($name),
'cron' => app()->modules()->cronTaskStatuses($name),
];
continue;
}
$results[$name] = [
'interval' => app()->modules()->runDueIntervalTasks($name),
'cron' => app()->modules()->runDueCronTasks($name),
];
}
echo json_encode([
'ok' => true,
'ran_at_utc' => gmdate('Y-m-d H:i:s'),
'module' => $targetModule !== '' ? $targetModule : null,
'mode' => $statusOnly ? 'status' : 'run',
'results' => $results,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;

View File

@@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
$root = dirname(__DIR__, 2);
chdir($root);
require $root . '/config/fileload.php';
$module = 'pi_control';
$pdo = module_fn($module, 'pdo');
module_fn($module, 'ensure_schema');
$table = fn(string $name) => module_fn($module, 'table', $name);
$settings = modules()->settings($module);
$strictHostKey = !empty($settings['terminal_strict_hostkey']);
$updateCmd = <<<'SH'
if ! command -v apt-get >/dev/null 2>&1; then echo "__ERR__NO_APT"; exit 2; fi;
if sudo -n apt update -qq >/dev/null 2>&1; then echo "__APT_UPDATE__=1"; else echo "__APT_UPDATE__=0"; fi;
count=$(apt-get -s dist-upgrade 2>/dev/null | grep -c "^Inst ");
echo "__COUNT__=$count"
SH;
$upgradeCmd = <<<'SH'
id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")";
current="$(. /etc/os-release 2>/dev/null && echo "${VERSION_CODENAME:-}")";
if [ "$id" != "debian" ] || [ -z "$current" ]; then echo "__UPGRADE__=0"; exit 0; fi;
latest="$( (command -v curl >/dev/null 2>&1 && curl -fsSL https://deb.debian.org/debian/dists/stable/Release) || (command -v wget >/dev/null 2>&1 && wget -qO- https://deb.debian.org/debian/dists/stable/Release) )";
latest="$(printf "%s" "$latest" | awk -F": " "/^Codename:/{print $2}")";
if [ -z "$latest" ]; then echo "__UPGRADE__=0"; echo "__RAW__=NO_FETCH"; exit 0; fi;
if [ "$current" != "$latest" ]; then echo "__UPGRADE__=1"; else echo "__UPGRADE__=0"; fi
SH;
$driver = (string)$pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$nowExpr = $driver === 'pgsql' ? 'NOW()' : "DATETIME('now')";
$hosts = $pdo->query('SELECT * FROM ' . $table('hosts'))->fetchAll(PDO::FETCH_ASSOC);
foreach ($hosts as $host) {
$id = (int)($host['id'] ?? 0);
if ($id <= 0) continue;
[$updExit, $updOut, $updErr] = runSshCommandCapture($host, $updateCmd, $strictHostKey, 20);
$updOutStr = (string)$updOut;
$updErrStr = (string)$updErr;
$updateCount = null;
$updatePreview = '';
$updateErr = str_contains($updOutStr, '__ERR__NO_APT');
if (preg_match('/^__COUNT__=(\d+)$/m', $updOutStr, $m)) {
$updateCount = (int)$m[1];
}
$updatePreview = trim($updOutStr);
if (strlen($updatePreview) > 1200) {
$updatePreview = substr($updatePreview, 0, 1200);
}
[$upgExit, $upgOut, $upgErr] = runSshCommandCapture($host, $upgradeCmd, $strictHostKey, 25);
$upgOutStr = (string)$upgOut;
$upgErrStr = (string)$upgErr;
$upgradeAvailable = null;
$upgradeErr = str_contains($upgOutStr, '__ERR__');
if ($upgExit === 0 && !$upgradeErr) {
if (preg_match('/^__UPGRADE__=(0|1)$/m', $upgOutStr, $m)) {
$upgradeAvailable = $m[1] === '1';
}
}
$updErrVal = (!$updateErr && $updateCount !== null) ? null : trim($updErrStr ?: $updOutStr);
$upgErrVal = $upgExit === 0 && !$upgradeErr ? null : trim($upgErrStr ?: $upgOutStr);
$stmt = $pdo->prepare(
'UPDATE ' . $table('hosts') . ' SET update_checked_at = ' . $nowExpr . ',
update_count = :update_count,
update_preview = :update_preview,
update_error = :update_error,
upgrade_available = :upgrade_available,
upgrade_raw = :upgrade_raw,
upgrade_error = :upgrade_error
WHERE id = :id'
);
$stmt->execute([
'update_count' => $updateCount,
'update_preview' => $updatePreview !== '' ? $updatePreview : null,
'update_error' => $updErrVal,
'upgrade_available' => $upgradeAvailable === null ? null : ($upgradeAvailable ? 1 : 0),
'upgrade_raw' => $upgExit === 0 ? trim($upgOutStr) : null,
'upgrade_error' => $upgErrVal,
'id' => $id,
]);
}
echo "OK\n";
function runSshCommandCapture(array $host, string $command, bool $strictHostKey, int $timeoutSec): array
{
$hostAddr = (string)($host['host'] ?? '');
$user = (string)($host['username'] ?? '');
$port = (int)($host['port'] ?? 22);
$authType = (string)($host['auth_type'] ?? 'key');
$keyPath = (string)($host['key_path'] ?? '');
$password = (string)($host['password'] ?? '');
$opts = $strictHostKey
? '-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts'
: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null';
$opts .= ' -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o LogLevel=ERROR -o RequestTTY=no';
$target = escapeshellarg($user . '@' . $hostAddr);
$remote = '/bin/sh -c ' . escapeshellarg($command);
$cmd = 'ssh -T ' . $opts . ' -p ' . (int)$port . ' ';
if ($authType === 'key' && $keyPath !== '') {
$cmd .= '-i ' . escapeshellarg($keyPath) . ' -o BatchMode=yes ';
} elseif ($authType === 'key') {
$cmd .= '-o BatchMode=yes ';
}
$cmd .= $target . ' -- ' . $remote;
if ($authType === 'pass' && $password !== '') {
$cmd = 'sshpass -p ' . escapeshellarg($password) . ' ' . $cmd;
}
$descriptors = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($process)) {
return [255, '', 'proc_open failed'];
}
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$out = '';
$err = '';
$start = time();
while (true) {
$status = proc_get_status($process);
$out .= stream_get_contents($pipes[1]);
$err .= stream_get_contents($pipes[2]);
if (!$status['running']) {
$exit = (int)$status['exitcode'];
proc_close($process);
return [$exit, $out, $err];
}
if (time() - $start > $timeoutSec) {
proc_terminate($process, 9);
proc_close($process);
return [124, $out, $err];
}
usleep(100000);
}
}

View File

@@ -0,0 +1,219 @@
<?php
declare(strict_types=1);
set_time_limit(0);
$root = dirname(__DIR__, 2);
chdir($root);
$fileload = $root . '/config/fileload.php';
if (!file_exists($fileload)) {
fwrite(STDERR, "[worker] Missing config: {$fileload}\n");
while (true) {
sleep(30);
}
}
require $fileload;
$module = 'pi_control';
$pdo = module_fn($module, 'pdo');
module_fn($module, 'ensure_schema');
$table = fn(string $name) => module_fn($module, 'table', $name);
$settingsReloadSec = 30;
$redis = null;
$queueName = 'pi_control:queue';
$defaultTimeout = 300;
$lastSettingsAt = 0;
$strictHostKey = getenv('PI_CONTROL_STRICT_HOSTKEY') === '1';
$driver = (string)$pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$nowExpr = $driver === 'pgsql' ? 'NOW()' : "DATETIME('now')";
while (true) {
if (time() - $lastSettingsAt >= $settingsReloadSec) {
$settings = modules()->settings($module);
$queueName = (string)($settings['redis']['queue'] ?? ($settings['redis.queue'] ?? (getenv('PI_CONTROL_REDIS_QUEUE') ?: 'pi_control:queue')));
$defaultTimeout = (int)($settings['exec_default_timeout'] ?? (getenv('PI_CONTROL_EXEC_DEFAULT_TIMEOUT') !== false ? (int)getenv('PI_CONTROL_EXEC_DEFAULT_TIMEOUT') : 300));
$defaultTimeout = $defaultTimeout > 0 ? $defaultTimeout : 300;
$settingsReloadSec = (int)($settings['settings_reload_sec'] ?? (getenv('PI_CONTROL_SETTINGS_RELOAD_SEC') !== false ? (int)getenv('PI_CONTROL_SETTINGS_RELOAD_SEC') : 30));
$settingsReloadSec = $settingsReloadSec > 0 ? $settingsReloadSec : 30;
$strictHostKey = !empty($settings['terminal_strict_hostkey']) || getenv('PI_CONTROL_STRICT_HOSTKEY') === '1';
$redis = module_fn($module, 'redis');
$lastSettingsAt = time();
}
try {
$job = $redis->command(['BLPOP', $queueName, 5]);
} catch (\Throwable $e) {
fwrite(STDERR, '[worker] Redis error: ' . $e->getMessage() . PHP_EOL);
$lastSettingsAt = 0;
sleep(5);
continue;
}
if (!$job || !is_array($job) || count($job) < 2) {
continue;
}
$payload = (string)$job[1];
$data = json_decode($payload, true);
if (!is_array($data) || empty($data['run_id'])) {
continue;
}
$runId = (int)$data['run_id'];
try {
$runStmt = $pdo->prepare('SELECT * FROM ' . $table('runs') . ' WHERE id = :id LIMIT 1');
$runStmt->execute(['id' => $runId]);
$run = $runStmt->fetch(PDO::FETCH_ASSOC);
if (!$run || ($run['status'] ?? '') !== 'queued') {
continue;
}
} catch (\Throwable $e) {
fwrite(STDERR, '[worker] DB error: ' . $e->getMessage() . PHP_EOL);
sleep(2);
continue;
}
$hostId = (int)($run['host_id'] ?? 0);
if ($hostId <= 0) {
continue;
}
$timeoutSec = (int)($run['timeout_sec'] ?? 0);
$timeoutSec = $timeoutSec > 0 ? $timeoutSec : $defaultTimeout;
$lockTtl = max($timeoutSec + 60, 120);
$lockKey = 'pi_control:lock:host:' . $hostId;
try {
$lockOk = $redis->command(['SET', $lockKey, (string)$runId, 'NX', 'EX', (string)$lockTtl]);
} catch (\Throwable $e) {
fwrite(STDERR, '[worker] Redis lock error: ' . $e->getMessage() . PHP_EOL);
$lastSettingsAt = 0;
sleep(2);
continue;
}
if ($lockOk !== 'OK') {
try {
$redis->command(['RPUSH', $queueName, $payload]);
} catch (\Throwable $e) {
fwrite(STDERR, '[worker] Redis requeue error: ' . $e->getMessage() . PHP_EOL);
$lastSettingsAt = 0;
}
usleep(250000);
continue;
}
$pdo->exec('UPDATE ' . $table('runs') . ' SET status = \'running\', started_at = ' . $nowExpr . ' WHERE id = ' . (int)$runId);
$hostStmt = $pdo->prepare('SELECT * FROM ' . $table('hosts') . ' WHERE id = :id LIMIT 1');
$hostStmt->execute(['id' => $hostId]);
$host = $hostStmt->fetch(PDO::FETCH_ASSOC);
if (!$host) {
$pdo->exec('UPDATE ' . $table('runs') . ' SET status = \'failed\', error = \'Host not found\', finished_at = ' . $nowExpr . ' WHERE id = ' . (int)$runId);
try {
$redis->command(['DEL', $lockKey]);
} catch (\Throwable $e) {
fwrite(STDERR, '[worker] Redis unlock error: ' . $e->getMessage() . PHP_EOL);
$lastSettingsAt = 0;
}
continue;
}
$commandText = (string)($run['command_text'] ?? '');
[$status, $exitCode, $output, $error] = executeSsh($host, $commandText, $timeoutSec, $strictHostKey);
$output = truncateText($output, 20000);
$error = truncateText($error, 20000);
$stmt = $pdo->prepare(
'UPDATE ' . $table('runs') . ' SET status = :status, output = :output, error = :error, exit_code = :exit_code, finished_at = ' . $nowExpr . ' WHERE id = :id'
);
$stmt->execute([
'status' => $status,
'output' => $output !== '' ? $output : null,
'error' => $error !== '' ? $error : null,
'exit_code' => $exitCode,
'id' => $runId,
]);
try {
$redis->command(['DEL', $lockKey]);
} catch (\Throwable $e) {
fwrite(STDERR, '[worker] Redis unlock error: ' . $e->getMessage() . PHP_EOL);
$lastSettingsAt = 0;
}
}
function executeSsh(array $host, string $command, int $timeoutSec, bool $strictHostKey): array
{
$hostAddr = (string)($host['host'] ?? '');
$user = (string)($host['username'] ?? '');
$port = (int)($host['port'] ?? 22);
$authType = (string)($host['auth_type'] ?? 'key');
$keyPath = (string)($host['key_path'] ?? '');
$password = (string)($host['password'] ?? '');
$opts = $strictHostKey
? '-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts'
: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null';
$target = escapeshellarg($user . '@' . $hostAddr);
$remoteCmd = escapeshellarg($command);
$cmd = 'ssh ' . $opts . ' -p ' . (int)$port . ' ';
if ($authType === 'key' && $keyPath !== '') {
$cmd .= '-i ' . escapeshellarg($keyPath) . ' ';
}
$cmd .= $target . ' -- ' . $remoteCmd;
if ($authType === 'pass' && $password !== '') {
$cmd = 'sshpass -p ' . escapeshellarg($password) . ' ' . $cmd;
}
$descriptors = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($process)) {
return ['failed', 255, '', 'proc_open failed'];
}
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$output = '';
$error = '';
$start = time();
while (true) {
$status = proc_get_status($process);
$output .= stream_get_contents($pipes[1]);
$error .= stream_get_contents($pipes[2]);
if (!$status['running']) {
$exitCode = (int)$status['exitcode'];
proc_close($process);
$finalStatus = $exitCode === 0 ? 'success' : 'failed';
return [$finalStatus, $exitCode, $output, $error];
}
if (time() - $start > $timeoutSec) {
proc_terminate($process, 9);
proc_close($process);
return ['timeout', 124, $output, $error];
}
usleep(100000);
}
}
function truncateText(string $text, int $limit): string
{
if (strlen($text) <= $limit) {
return $text;
}
return substr($text, 0, $limit) . "\n...truncated...";
}

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
set -euo pipefail
TOKEN="${1:-}"
ENC_COMMAND="${2:-}"
if [[ -z "${TOKEN}" ]]; then
echo "Missing token."
exit 1
fi
API_BASE="${PI_CONTROL_API_URL:-http://gui_nexus}"
API_BASE="${API_BASE%/}"
INFO_URL="${API_BASE}/module/pi_control/terminal_info?token=${TOKEN}"
if [[ -n "${PI_CONTROL_SHARED_SECRET:-}" ]]; then
AUTH_HEADER=(-H "X-Terminal-Secret: ${PI_CONTROL_SHARED_SECRET}")
else
AUTH_HEADER=()
fi
JSON="$(curl -sS "${AUTH_HEADER[@]}" "${INFO_URL}")"
OK="$(echo "${JSON}" | jq -r '.ok')"
if [[ "${OK}" != "true" ]]; then
echo "Invalid or expired token."
exit 1
fi
HOST="$(echo "${JSON}" | jq -r '.host.host')"
PORT="$(echo "${JSON}" | jq -r '.host.port')"
USER="$(echo "${JSON}" | jq -r '.host.username')"
AUTH_TYPE="$(echo "${JSON}" | jq -r '.host.auth_type')"
KEY_PATH="$(echo "${JSON}" | jq -r '.host.key_path')"
PASSWORD="$(echo "${JSON}" | jq -r '.host.password')"
STRICT_HOSTKEY="$(echo "${JSON}" | jq -r '.strict_hostkey // false')"
TMUX_SESSION_JSON="$(echo "${JSON}" | jq -r '.tmux_session // ""')"
COMMAND="$(echo "${JSON}" | jq -r '.command // ""')"
if [[ -z "${COMMAND}" && -n "${ENC_COMMAND}" ]]; then
COMMAND="$(printf '%s' "${ENC_COMMAND}" | base64 -d 2>/dev/null || true)"
fi
if [[ -z "${HOST}" || -z "${USER}" ]]; then
echo "Host data incomplete."
exit 1
fi
SSH_OPTS=()
if [[ "${STRICT_HOSTKEY}" == "true" || "${PI_CONTROL_STRICT_HOSTKEY:-}" == "1" ]]; then
SSH_OPTS=(-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts)
else
SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
fi
SSH_TARGET="${USER}@${HOST}"
TMUX_SESSION="${TMUX_SESSION_JSON:-}"
if [[ -z "${TMUX_SESSION}" ]]; then
TMUX_SESSION="${PI_CONTROL_TMUX_SESSION:-nexus}"
fi
if [[ -n "${COMMAND}" ]]; then
COMMAND_B64="$(printf '%s' "${COMMAND}" | base64)"
REMOTE_CMD="CMD_B64='${COMMAND_B64}'; CMD=\"\$(printf '%s' \"\$CMD_B64\" | base64 -d)\"; if command -v tmux >/dev/null 2>&1; then SESSION=\"${TMUX_SESSION}\"; tmux has-session -t \"\$SESSION\" 2>/dev/null || tmux new-session -d -s \"\$SESSION\"; tmux send-keys -t \"\$SESSION\" \"\$CMD\" C-m; exec tmux attach -t \"\$SESSION\"; else eval \"\$CMD\"; exec /bin/bash -il; fi"
REMOTE_CMD_Q=$(printf "%s" "$REMOTE_CMD" | sed "s/'/'\\\\''/g")
if [[ "${AUTH_TYPE}" == "key" && -n "${KEY_PATH}" ]]; then
ssh "${SSH_OPTS[@]}" -i "${KEY_PATH}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/bash -lc "'${REMOTE_CMD_Q}'" || \
ssh "${SSH_OPTS[@]}" -i "${KEY_PATH}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/sh -lc "'${REMOTE_CMD_Q}'"
elif [[ "${AUTH_TYPE}" == "pass" && -n "${PASSWORD}" ]]; then
sshpass -p "${PASSWORD}" ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/bash -lc "'${REMOTE_CMD_Q}'" || \
sshpass -p "${PASSWORD}" ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/sh -lc "'${REMOTE_CMD_Q}'"
else
ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/bash -lc "'${REMOTE_CMD_Q}'" || \
ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/sh -lc "'${REMOTE_CMD_Q}'"
fi
exit $?
else
REMOTE_CMD="if command -v tmux >/dev/null 2>&1; then exec tmux new -A -s \"${TMUX_SESSION}\"; else exec /bin/bash -il; fi"
REMOTE_CMD_Q=$(printf "%s" "$REMOTE_CMD" | sed "s/'/'\\\\''/g")
if [[ "${AUTH_TYPE}" == "key" && -n "${KEY_PATH}" ]]; then
exec ssh "${SSH_OPTS[@]}" -i "${KEY_PATH}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/bash -lc "'${REMOTE_CMD_Q}'" || \
exec ssh "${SSH_OPTS[@]}" -i "${KEY_PATH}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/sh -lc "'${REMOTE_CMD_Q}'"
elif [[ "${AUTH_TYPE}" == "pass" && -n "${PASSWORD}" ]]; then
exec sshpass -p "${PASSWORD}" ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/bash -lc "'${REMOTE_CMD_Q}'" || \
exec sshpass -p "${PASSWORD}" ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/sh -lc "'${REMOTE_CMD_Q}'"
else
exec ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/bash -lc "'${REMOTE_CMD_Q}'" || \
exec ssh "${SSH_OPTS[@]}" -p "${PORT:-22}" -tt "${SSH_TARGET}" -- /bin/sh -lc "'${REMOTE_CMD_Q}'"
fi
fi

View File

@@ -0,0 +1,17 @@
FROM alpine:3.20
RUN apk add --no-cache \
ttyd \
bash \
openssh-client \
curl \
jq \
sshpass
WORKDIR /app
COPY tools/pi_control/terminal_entry.sh /app/tools/pi_control/terminal_entry.sh
RUN chmod +x /app/tools/pi_control/terminal_entry.sh
EXPOSE 7681
ENTRYPOINT ["ttyd", "--url-arg", "--base-path", "/ttyd", "/app/tools/pi_control/terminal_entry.sh"]

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env sh
set -eu
APP_ENV="${APP_ENV:-staging}"
if [ "$APP_ENV" = "live" ]; then
SCRIPT="/app/live/tools/pi_control/terminal_entry.sh"
else
SCRIPT="/app/staging/tools/pi_control/terminal_entry.sh"
fi
if [ ! -f "$SCRIPT" ]; then
echo "terminal_entry.sh not found at $SCRIPT"
exit 1
fi
exec ttyd --writable --url-arg "$SCRIPT"

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env sh
set -eu
APP_ENV="${APP_ENV:-staging}"
if [ "$APP_ENV" = "live" ]; then
# NOTE: Renamed from worker.php due to CIFS/SMB invalid argument errors on rename/open.
SCRIPT="/app/live/tools/pi_control/pi_worker.php"
else
# NOTE: Renamed from worker.php due to CIFS/SMB invalid argument errors on rename/open.
SCRIPT="/app/staging/tools/pi_control/pi_worker.php"
fi
if [ ! -f "$SCRIPT" ]; then
echo "pi_worker.php not found at $SCRIPT"
exit 1
fi
exec php "$SCRIPT"

File diff suppressed because it is too large Load Diff