Compare commits

8 Commits

Author SHA1 Message Date
myron af03a2f2d8 Fix reactor startup: auto-create venv, requirements.txt, SETUP button, self-install
- deploy/requirements.txt: explicit pip deps (fastapi, uvicorn, aiomysql, aiohttp, anthropic, trafilatura)
- jarvis-deploy.sh: self-installs to /usr/local/bin/ on every run so updates propagate;
  auto-creates venv and installs packages if missing before restart attempt
- admin/index.php: add SETUP button next to RESTART — runs full setup+start in one click
  (creates venv, installs deps, copies reactor.py, starts it)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8tDRrQqgLjqXebMCBNcP3
2026-07-01 04:11:12 -05:00
myron dfc92a6791 Merge branch 'master' 2026-07-01 04:09:28 -05:00
myron 05522edb1d Add llava vision: use Ollama llava:7b for all image analysis, Claude as fallback
- Add _ollama_vision_call() using Ollama /api/generate with images array
- handle_screenshot: try llava first (free, on-LAN), fall back to Claude on error;
  text-only snapshots now use ollama instead of groq
- handle_vision: same llava-first/Claude-fallback pattern; caller can force
  provider='ollama' or 'claude' explicitly; stored provider_used reflects actual
  provider that ran

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8tDRrQqgLjqXebMCBNcP3
2026-07-01 04:05:37 -05:00
myron 435af8ccc9 Wire Ollama properly: fix IP, upgrade to llama3.1:8b, use for guardian/sitrep
- reactor.py: fix OLLAMA_HOST .95 → .210 (actual Ollama VM IP)
- reactor.py: upgrade OLLAMA_MODEL 1b → 8b (llama3.1:8b has tool-calling, 131K ctx)
- reactor.py: guardian alerts and sitrep now use ollama instead of groq (free, on-LAN, no quota)
- config.example.php: same IP/model fixes + fix GROQ_MODEL_SEARCH to compound-beta-mini (groq/ prefix causes 404)
- deploy script: patch live config.php on every deploy to correct Ollama IP/model and Groq model name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8tDRrQqgLjqXebMCBNcP3
2026-07-01 04:02:30 -05:00
myron a9ea75db98 Fix Arc Reactor restart: use bash for source, add systemd service, fix deploy
The admin panel restart command used shell_exec which runs /bin/sh (dash on
Ubuntu) — dash doesn't support 'source', so the venv never activated and the
reactor silently never started.

- admin/index.php: wrap restart in bash -c so 'source' works; try systemctl
  first then fall back to nohup
- deploy/jarvis-arc.service: add proper systemd unit so reactor auto-starts
  on boot and auto-restarts on crash
- deploy/jarvis-deploy.sh: install+enable service file when it changes; mkdir
  log dir before restart; fall back to nohup if systemctl not set up yet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8tDRrQqgLjqXebMCBNcP3
2026-07-01 03:56:11 -05:00
myron 651455cb47 Fix guardian mode: create missing tables on startup, fix conversations column name
- Add CREATE TABLE IF NOT EXISTS for guardian_config and guardian_events in
  reactor lifespan so tables are created automatically on next restart
- Fix conversations column bug: reactor used 'message' but schema has 'content';
  fix INSERT and SELECT queries to use content (SELECT aliases it as 'message'
  so the JSON response key stays the same)
- Add guardian tables to schema.sql for documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8tDRrQqgLjqXebMCBNcP3
2026-07-01 03:49:10 -05:00
myron 874f6e8c5c fix: rename parkersling site key to parkerslingshotrentals in facts_collector 2026-06-23 18:09:30 +00:00
myron 42a82c40cb fix: remove dead __NOVACPX_VM__ branch, dedupe date() call in webhook 2026-06-23 18:03:22 +00:00
9 changed files with 231 additions and 78 deletions
+4 -4
View File
@@ -18,13 +18,13 @@ define(chr(39)+'CLAUDE_MODEL'.chr(39), chr(39)+'claude-sonnet-4-6'.chr(39))
define(chr(39)+'CLAUDE_MAX_TOKENS'.chr(39), 1024);
define(chr(39)+'GROQ_API_KEY'.chr(39), chr(39)+'gsk_...'.chr(39));
define(chr(39)+'GROQ_MODEL_SEARCH'.chr(39), chr(39)+'groq/compound-mini'.chr(39));
define(chr(39)+'GROQ_MODEL_SEARCH'.chr(39), chr(39)+'compound-beta-mini'.chr(39));
define(chr(39)+'GROQ_MODEL_GENERAL'.chr(39), chr(39)+'llama-3.3-70b-versatile'.chr(39));
define(chr(39)+'GROQ_TIMEOUT'.chr(39), 30);
define(chr(39)+'OLLAMA_HOST'.chr(39), chr(39)+'http://10.48.200.95:11434'.chr(39));
define(chr(39)+'OLLAMA_MODEL_PRIMARY'.chr(39), chr(39)+'llama3.2:1b'.chr(39));
define(chr(39)+'OLLAMA_MODEL_HEAVY'.chr(39), chr(39)+'llama3.1:70b'.chr(39));
define(chr(39)+'OLLAMA_HOST'.chr(39), chr(39)+'http://10.48.200.210:11434'.chr(39));
define(chr(39)+'OLLAMA_MODEL_PRIMARY'.chr(39), chr(39)+'llama3.1:8b'.chr(39));
define(chr(39)+'OLLAMA_MODEL_HEAVY'.chr(39), chr(39)+'llama3.1:8b'.chr(39));
define(chr(39)+'OLLAMA_TIMEOUT'.chr(39), 90);
define(chr(39)+'LOCAL_SUBNET'.chr(39), chr(39)+'10.48.200'.chr(39));
+1 -1
View File
@@ -184,7 +184,7 @@ function collect_all(): array {
"jarvis" => "http://127.0.0.1",
'tomsjavajive' => 'https://tomsjavajive.com',
'epictravelexp'=> 'https://epictravelexpeditions.com',
'parkersling' => 'https://parkerslingshotrentals.com',
'parkerslingshotrentals' => 'https://parkerslingshotrentals.com',
'orbishosting' => 'https://orbishosting.com',
'orbisportal' => 'https://orbis.orbishosting.com',
'tomtomgames' => 'https://tomtomgames.com',
+37
View File
@@ -441,4 +441,41 @@ CREATE TABLE `users` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
--
-- Table structure for table `guardian_config`
--
CREATE TABLE IF NOT EXISTS `guardian_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`key_name` varchar(64) NOT NULL,
`value` varchar(255) NOT NULL DEFAULT '',
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_key` (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Table structure for table `guardian_events`
--
CREATE TABLE IF NOT EXISTS `guardian_events` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`event_type` varchar(64) NOT NULL,
`severity` enum('info','warning','critical') NOT NULL DEFAULT 'info',
`agent_id` varchar(64) NOT NULL DEFAULT '',
`hostname` varchar(128) NOT NULL DEFAULT '',
`metric` varchar(64) NOT NULL DEFAULT '',
`value` float NOT NULL DEFAULT 0,
`threshold` float NOT NULL DEFAULT 0,
`message` text NOT NULL,
`ai_analysis` text NOT NULL DEFAULT '',
`acknowledged` tinyint(1) NOT NULL DEFAULT 0,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_severity` (`severity`),
KEY `idx_ack` (`acknowledged`),
KEY `idx_created` (`created_at`),
KEY `idx_agent` (`agent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Dump completed on 2026-06-29 23:15:44
+17
View File
@@ -0,0 +1,17 @@
[Unit]
Description=JARVIS Arc Reactor
After=network-online.target mysql.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/jarvis-arc
ExecStart=/opt/jarvis-arc/venv/bin/python3 /opt/jarvis-arc/reactor.py
Restart=always
RestartSec=10
User=root
StandardOutput=append:/home/jarvis.orbishosting.com/logs/arc_reactor.log
StandardError=append:/home/jarvis.orbishosting.com/logs/arc_reactor.log
[Install]
WantedBy=multi-user.target
+35 -4
View File
@@ -80,11 +80,42 @@ while IFS= read -r path; do
systemctl reload lsws 2>/dev/null || systemctl restart lsws 2>/dev/null
log "OLS reloaded for JARVIS deploy"
# Sync reactor.py to runtime location if it changed
if echo "$CHANGED" | grep -q 'deploy/reactor.py'; then
# Patch live config.php with correct Ollama host/model and Groq search model
CONFIG="$path/api/config.php"
if [ -f "$CONFIG" ]; then
sed -i "s|http://10\.48\.200\.95:11434|http://10.48.200.210:11434|g" "$CONFIG"
sed -i "s|'llama3\.2:1b'|'llama3.1:8b'|g" "$CONFIG"
sed -i "s|\"llama3\.2:1b\"|\"llama3.1:8b\"|g" "$CONFIG"
sed -i "s|'llama3\.1:70b'|'llama3.1:8b'|g" "$CONFIG"
sed -i "s|\"llama3\.1:70b\"|\"llama3.1:8b\"|g" "$CONFIG"
sed -i "s|'groq/compound-mini'|'compound-beta-mini'|g;s|\"groq/compound-mini\"|\"compound-beta-mini\"|g" "$CONFIG"
log "Patched config.php: Ollama IP→.210, model→llama3.1:8b, Groq search→compound-beta-mini"
fi
# Self-install the deploy script on every run
cp "$path/deploy/jarvis-deploy.sh" /usr/local/bin/jarvis-deploy.sh 2>/dev/null && chmod +x /usr/local/bin/jarvis-deploy.sh
# Sync reactor.py + service file to runtime location if they changed
if echo "$CHANGED" | grep -q 'deploy/reactor.py\|deploy/jarvis-arc.service\|deploy/requirements.txt'; then
mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs
cp "$path/deploy/reactor.py" /opt/jarvis-arc/reactor.py
systemctl restart jarvis-arc
log "Arc Reactor updated and restarted (reactor.py changed)"
cp "$path/deploy/requirements.txt" /opt/jarvis-arc/requirements.txt 2>/dev/null
# Bootstrap venv if it doesn't exist
if [ ! -f /opt/jarvis-arc/venv/bin/activate ]; then
log "Arc Reactor venv missing — creating and installing packages"
python3 -m venv /opt/jarvis-arc/venv
/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt
log "Arc Reactor venv ready"
fi
if echo "$CHANGED" | grep -q 'deploy/jarvis-arc.service'; then
cp "$path/deploy/jarvis-arc.service" /etc/systemd/system/jarvis-arc.service
systemctl daemon-reload
systemctl enable jarvis-arc
log "Arc Reactor service file installed and enabled"
fi
systemctl restart jarvis-arc 2>/dev/null || \
bash -c "pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &"
log "Arc Reactor updated and restarted"
fi
fi
+77 -21
View File
@@ -49,8 +49,8 @@ CLAUDE_API_KEY = "sk-ant-api03-JL6vjFeyEfajQmaTOmsT6AfLLPs2icrIAvvJ0hdi4DuMi0155
CLAUDE_MODEL = "claude-sonnet-4-6"
GROQ_API_KEY = "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"
GROQ_MODEL = "llama-3.3-70b-versatile"
OLLAMA_HOST = "http://10.48.200.95:11434"
OLLAMA_MODEL = "llama3.2:1b"
OLLAMA_HOST = "http://10.48.200.210:11434"
OLLAMA_MODEL = "llama3.1:8b"
GMAIL_USER = "myronblair@gmail.com"
GMAIL_PASS = "demsvdylwweacbcx"
@@ -175,6 +175,16 @@ async def _ollama_call(messages: list, system: str = "") -> str:
data = await resp.json()
return data.get("response", "")
async def _ollama_vision_call(image_b64: str, prompt: str) -> str:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{OLLAMA_HOST}/api/generate",
json={"model": "llava:7b", "prompt": prompt, "images": [image_b64], "stream": False},
timeout=aiohttp.ClientTimeout(total=120),
) as resp:
data = await resp.json()
return data.get("response", "").strip()
async def handle_llm(payload: dict) -> dict:
message = payload.get("message", "")
system = payload.get("system", "You are JARVIS, an Iron Man-style AI assistant.")
@@ -658,10 +668,16 @@ async def handle_screenshot(payload: dict) -> dict:
height = result.get("height", 0)
file_size = result.get("file_size", 0)
# Run Claude vision analysis if we have an image
# Run vision analysis if we have an image — llava first, Claude fallback
analysis = ""
provider_used = ""
if do_analyze and image_b64:
try:
analysis = await _ollama_vision_call(image_b64, analyze_prompt)
provider_used = "ollama:llava"
log.info(f"[VISION] llava analysis complete ({len(analysis)} chars)")
except Exception as e:
log.warning(f"[VISION] llava failed: {e} — falling back to Claude")
try:
import anthropic
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
@@ -679,17 +695,17 @@ async def handle_screenshot(payload: dict) -> dict:
)
analysis = msg.content[0].text if msg.content else ""
provider_used = "claude"
log.info(f"[VISION] Claude analysis complete ({len(analysis)} chars)")
except Exception as e:
log.warning(f"[VISION] Claude vision failed: {e}")
analysis = f"Vision analysis unavailable: {e}"
log.info(f"[VISION] Claude fallback analysis complete ({len(analysis)} chars)")
except Exception as e2:
log.warning(f"[VISION] Claude fallback also failed: {e2}")
analysis = f"Vision analysis unavailable: {e2}"
elif do_analyze and not image_b64 and result.get("snapshot_type") == "text":
# Text-only sysinfo snapshot — summarize with LLM
# Text-only sysinfo snapshot — summarize with Ollama
try:
snap_text = json.dumps(result, indent=2)[:3000]
prompt = f"Summarize this server system snapshot for JARVIS. Highlight any concerns:\n\n{snap_text}"
analysis = await llm_call([{"role": "user", "content": prompt}], "groq")
provider_used = "groq"
analysis = await llm_call([{"role": "user", "content": prompt}], "ollama")
provider_used = "ollama"
except Exception as e:
analysis = f"Analysis unavailable: {e}"
@@ -742,8 +758,22 @@ async def handle_vision(payload: dict) -> dict:
if not image_b64:
raise ValueError("No image data provided")
log.info(f"[VISION] Analysis: screenshot_id={screenshot_id} agent={hostname}")
log.info(f"[VISION] Analysis: screenshot_id={screenshot_id} agent={hostname} provider={provider}")
analysis = ""
provider_used = provider
if provider == "ollama" or provider == "llava":
analysis = await _ollama_vision_call(image_b64, prompt)
provider_used = "ollama:llava"
else:
# Default: llava first, Claude fallback
try:
analysis = await _ollama_vision_call(image_b64, prompt)
provider_used = "ollama:llava"
log.info(f"[VISION] llava analysis complete ({len(analysis)} chars)")
except Exception as e:
log.warning(f"[VISION] llava failed: {e} — falling back to Claude")
try:
import anthropic
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
@@ -760,14 +790,15 @@ async def handle_vision(payload: dict) -> dict:
}],
)
analysis = msg.content[0].text if msg.content else ""
except Exception as e:
raise RuntimeError(f"Vision analysis failed: {e}")
provider_used = "claude"
except Exception as e2:
raise RuntimeError(f"Vision analysis failed (llava: {e}, claude: {e2})")
# Update stored screenshot if we have an ID
if screenshot_id:
await db_execute(
"UPDATE agent_screenshots SET vision_analysis=%s, vision_provider=%s WHERE id=%s",
(analysis, "claude", int(screenshot_id))
(analysis, provider_used, int(screenshot_id))
)
return {
@@ -775,7 +806,7 @@ async def handle_vision(payload: dict) -> dict:
"screenshot_id": screenshot_id,
"prompt": prompt,
"analysis": analysis,
"provider": "claude",
"provider": provider_used,
}
@@ -1022,7 +1053,7 @@ async def guardian_loop() -> None:
"for Myron. Be direct about severity and what action to take. "
"No markdown, no headers."
)
ai_msg = await llm_call([{"role": "user", "content": ai_prompt}], "groq")
ai_msg = await llm_call([{"role": "user", "content": ai_prompt}], "ollama")
# Update the most recent guardian event with AI analysis
await db_execute(
"""UPDATE guardian_events SET ai_analysis=%s
@@ -1051,7 +1082,7 @@ async def _guardian_inject_chat(message: str) -> None:
"""Write a proactive JARVIS message into the conversations table so the HUD picks it up."""
try:
await db_execute(
"""INSERT INTO conversations (session_id, role, message, created_at)
"""INSERT INTO conversations (session_id, role, content, created_at)
VALUES ('guardian', 'assistant', %s, NOW())""",
(message,)
)
@@ -1065,7 +1096,7 @@ async def handle_sitrep(payload: dict) -> dict:
payload: { detail: brief|full, provider: groq }
"""
detail = payload.get("detail", "full")
provider = payload.get("provider", "groq")
provider = payload.get("provider", "ollama")
log.info(f"[GUARDIAN] SITREP requested (detail={detail})")
@@ -1806,7 +1837,7 @@ Keep the tone confident and action-oriented. Format with clear sections. Under 4
# Store in conversations so HUD can surface it
await db_execute(
"INSERT INTO conversations (session_id, role, message, created_at) VALUES ('guardian','assistant',%s,NOW())",
"INSERT INTO conversations (session_id, role, content, created_at) VALUES ('guardian','assistant',%s,NOW())",
(review_text,)
)
@@ -2209,6 +2240,31 @@ async def lifespan(app: FastAPI):
log.info(f"◈ JARVIS Arc Reactor v{VERSION} starting on {HOST}:{PORT}")
await get_pool()
await db_execute("UPDATE arc_status SET started_at=NOW(), last_heartbeat=NOW(), version=%s WHERE id=1", (VERSION,))
await db_execute("""CREATE TABLE IF NOT EXISTS guardian_config (
id INT AUTO_INCREMENT PRIMARY KEY,
key_name VARCHAR(64) NOT NULL,
value VARCHAR(255) NOT NULL DEFAULT '',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_key (key_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
await db_execute("""CREATE TABLE IF NOT EXISTS guardian_events (
id INT AUTO_INCREMENT PRIMARY KEY,
event_type VARCHAR(64) NOT NULL,
severity ENUM('info','warning','critical') NOT NULL DEFAULT 'info',
agent_id VARCHAR(64) NOT NULL DEFAULT '',
hostname VARCHAR(128) NOT NULL DEFAULT '',
metric VARCHAR(64) NOT NULL DEFAULT '',
value FLOAT NOT NULL DEFAULT 0,
threshold FLOAT NOT NULL DEFAULT 0,
message TEXT NOT NULL,
ai_analysis TEXT NOT NULL DEFAULT '',
acknowledged TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_severity (severity),
KEY idx_ack (acknowledged),
KEY idx_created (created_at),
KEY idx_agent (agent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
asyncio.create_task(job_poller())
asyncio.create_task(heartbeat_loop())
asyncio.create_task(guardian_loop())
@@ -2728,13 +2784,13 @@ async def guardian_chat_events(since: str = ""):
"""Return proactive guardian messages injected into conversations."""
if since:
rows = await db_fetchall(
"SELECT id, message, created_at FROM conversations "
"SELECT id, content AS message, created_at FROM conversations "
"WHERE session_id='guardian' AND created_at > %s ORDER BY created_at ASC",
(since,)
)
else:
rows = await db_fetchall(
"SELECT id, message, created_at FROM conversations "
"SELECT id, content AS message, created_at FROM conversations "
"WHERE session_id='guardian' ORDER BY created_at DESC LIMIT 10"
)
return rows or []
+6
View File
@@ -0,0 +1,6 @@
fastapi
uvicorn[standard]
aiomysql
aiohttp
anthropic
trafilatura
+19 -2
View File
@@ -550,8 +550,22 @@ if ($action) {
j(['ok'=>true,'msg'=>ucwords(str_replace('_',' ',$wId)).' triggered']);
} else { bad('Unknown cron worker'); }
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'restart') {
shell_exec('pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &');
shell_exec('bash -c "mkdir -p /home/jarvis.orbishosting.com/logs; systemctl restart jarvis-arc 2>/dev/null || (pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &)"');
j(['ok'=>true,'msg'=>'Arc Reactor restarting']);
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'setup') {
$setupLog = '/home/jarvis.orbishosting.com/logs/arc_reactor.log';
$cmd = 'bash -c "'.
'mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs && '.
'cp /var/www/jarvis/deploy/reactor.py /opt/jarvis-arc/reactor.py && '.
'cp /var/www/jarvis/deploy/requirements.txt /opt/jarvis-arc/requirements.txt && '.
'if [ ! -d /opt/jarvis-arc/venv ]; then python3 -m venv /opt/jarvis-arc/venv; fi && '.
'/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt && '.
'pkill -f reactor.py 2>/dev/null; sleep 1 && '.
'cd /opt/jarvis-arc && source venv/bin/activate && '.
'nohup python3 reactor.py >> '.$setupLog.' 2>&1 &'.
'" >> '.$setupLog.' 2>&1';
shell_exec($cmd);
j(['ok'=>true,'msg'=>'Arc Reactor setup started — check log in ~30s then restart']);
} else { bad('Invalid worker action'); }
break;
case 'arc_status':
@@ -2332,7 +2346,10 @@ async function loadWorkers() {
<td class="ts">jarvis-do :7474</td>
<td>${rdot}${ron?'<span style="color:var(--green)">ONLINE</span>':'<span style="color:var(--red)">OFFLINE</span>'}</td>
<td class="ts">${rinfo}</td>
<td><button onclick="workerAction('daemon','arc_reactor','restart')" style="${wBtn('red')}">&#8635; RESTART</button></td>
<td style="display:flex;gap:4px">
<button onclick="workerAction('daemon','arc_reactor','restart')" style="${wBtn('red')}">&#8635; RESTART</button>
<button onclick="workerAction('daemon','arc_reactor','setup')" style="${wBtn('orange')}">&#9881; SETUP</button>
</td>
</tr>`;
}
async function loadDashboard() {
+2 -13
View File
@@ -50,20 +50,9 @@ if (!isset($repoMap[$repo])) {
}
$path = $repoMap[$repo];
// NovaCPX lives on a private VM — the VM polls GitHub every minute via cron
// This webhook receipt confirms GitHub delivered the push notification
if ($path === '__NOVACPX_VM__') {
$commit = $data['after'] ?? 'HEAD';
$msg = "[" . date('Y-m-d H:i:s') . "] NovaCPX push by $pusher (commit: $commit) — VM will deploy within 1 min";
file_put_contents(DEPLOY_LOG, $msg . "\n", FILE_APPEND | LOCK_EX);
echo json_encode(['ok' => true, 'queued' => 'novacpx', 'commit' => $commit]);
exit;
}
$ts = date('Y-m-d H:i:s');
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
$msg = "[" . date('Y-m-d H:i:s') . "] Queued deploy: $repo by $pusher -> $path";
file_put_contents(DEPLOY_LOG, $msg . "\n", FILE_APPEND | LOCK_EX);
file_put_contents(DEPLOY_LOG, "[$ts] Queued deploy: $repo by $pusher -> $path\n", FILE_APPEND | LOCK_EX);
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);