fix remote ubuntu server

This commit is contained in:
2026-06-05 14:56:03 +07:00
parent 4b65838f0f
commit 08b94337ad
5 changed files with 130 additions and 47 deletions

View File

@@ -63,12 +63,21 @@ def get_settings() -> Settings:
robot_package_base_url = os.getenv("ROBOT_PACKAGE_BASE_URL", "https://package.pnkr.cloud").rstrip("/") robot_package_base_url = os.getenv("ROBOT_PACKAGE_BASE_URL", "https://package.pnkr.cloud").rstrip("/")
return Settings( return Settings(
agent_version=os.getenv("AGENT_VERSION", "1.0.0"), agent_version=os.getenv("AGENT_VERSION", "1.0.0"),
host=os.getenv("AGENT_HOST", "127.0.0.1"), host=os.getenv("AGENT_HOST", "0.0.0.0"),
port=int(os.getenv("AGENT_PORT", "5010")), port=int(os.getenv("AGENT_PORT", "5010")),
robot_package_base_url=robot_package_base_url, robot_package_base_url=robot_package_base_url,
allowed_origins=_csv( allowed_origins=_csv(
os.getenv("ALLOWED_ORIGINS"), os.getenv("ALLOWED_ORIGINS"),
["https://app.pnkr.cloud", "https://package.pnkr.cloud", "http://localhost:3000", "http://localhost:5173"], [
"https://app.pnkr.cloud",
"https://package.pnkr.cloud",
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:5173",
"http://127.0.0.1:5173",
"http://localhost:8080",
"http://127.0.0.1:8080",
],
), ),
allowed_download_hosts=_csv( allowed_download_hosts=_csv(
os.getenv("ALLOWED_DOWNLOAD_HOSTS"), os.getenv("ALLOWED_DOWNLOAD_HOSTS"),

View File

@@ -4,7 +4,7 @@ After=network.target
[Service] [Service]
WorkingDirectory=/opt/local-installer-agent WorkingDirectory=/opt/local-installer-agent
ExecStart=/opt/local-installer-agent/venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 5010 ExecStart=/opt/local-installer-agent/venv/bin/python -m uvicorn app.main:app --host ${AGENT_HOST} --port ${AGENT_PORT}
Restart=always Restart=always
User=root User=root
EnvironmentFile=/etc/local-installer-agent/agent.env EnvironmentFile=/etc/local-installer-agent/agent.env

View File

@@ -3,6 +3,8 @@ set -euo pipefail
VERSION="${VERSION:-1.0.0}" VERSION="${VERSION:-1.0.0}"
ARCH="${ARCH:-amd64}" ARCH="${ARCH:-amd64}"
AGENT_HOST="${AGENT_HOST:-0.0.0.0}"
AGENT_PORT="${AGENT_PORT:-5010}"
DEB_COMPRESSION="${DEB_COMPRESSION:-gzip}" DEB_COMPRESSION="${DEB_COMPRESSION:-gzip}"
PKG_NAME="local-installer-agent" PKG_NAME="local-installer-agent"
BUILD_ROOT="${BUILD_ROOT:-build}" BUILD_ROOT="${BUILD_ROOT:-build}"
@@ -53,10 +55,10 @@ chmod 755 "${BUILD_DIR}/DEBIAN"
cat > "${BUILD_DIR}/etc/local-installer-agent/agent.env" <<EOF cat > "${BUILD_DIR}/etc/local-installer-agent/agent.env" <<EOF
AGENT_VERSION=${VERSION} AGENT_VERSION=${VERSION}
AGENT_HOST=127.0.0.1 AGENT_HOST=${AGENT_HOST}
AGENT_PORT=5010 AGENT_PORT=${AGENT_PORT}
ROBOT_PACKAGE_BASE_URL=https://package.pnkr.cloud ROBOT_PACKAGE_BASE_URL=https://package.pnkr.cloud
ALLOWED_ORIGINS=https://app.pnkr.cloud,https://package.pnkr.cloud,http://localhost:3000,http://localhost:5173 ALLOWED_ORIGINS=https://app.pnkr.cloud,https://package.pnkr.cloud,http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:8080,http://127.0.0.1:8080
ALLOWED_DOWNLOAD_HOSTS=package.pnkr.cloud ALLOWED_DOWNLOAD_HOSTS=package.pnkr.cloud
ALLOWED_DOCKER_REGISTRIES=registry.robot.package,docker.io ALLOWED_DOCKER_REGISTRIES=registry.robot.package,docker.io
CACHE_DIR=/var/cache/local-installer-agent/packages CACHE_DIR=/var/cache/local-installer-agent/packages

View File

@@ -169,6 +169,50 @@ function getClientOsLabel(os) {
return CLIENT_OS_LABELS[os] || CLIENT_OS_LABELS.unknown; return CLIENT_OS_LABELS[os] || CLIENT_OS_LABELS.unknown;
} }
function parseUrlLike(value) {
const text = normalizeUrl(value);
if (!text) return null;
const urlText = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(text)
? text
: `http://${text}`;
try {
return new URL(urlText);
} catch {
return null;
}
}
function isLoopbackHostname(hostname) {
const host = String(hostname || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
return Boolean(
host === 'localhost'
|| host.endsWith('.localhost')
|| host === '::1'
|| host === '0.0.0.0'
|| /^127(?:\.\d{1,3}){0,3}$/.test(host)
);
}
function isLoopbackAgentEndpoint(agentBaseUrl) {
const parsed = parseUrlLike(agentBaseUrl);
return !parsed?.hostname || isLoopbackHostname(parsed.hostname);
}
function resolveTargetOpenUrl(openUrl, agentBaseUrl, isRemoteAgentEndpoint) {
if (!isRemoteAgentEndpoint) return openUrl;
const parsedOpenUrl = parseUrlLike(openUrl);
const parsedAgentUrl = parseUrlLike(agentBaseUrl);
if (!parsedOpenUrl || !parsedAgentUrl || !isLoopbackHostname(parsedOpenUrl.hostname)) {
return openUrl;
}
parsedOpenUrl.hostname = parsedAgentUrl.hostname;
return parsedOpenUrl.toString();
}
function App() { function App() {
const [settings, setSettings] = useState(readSettings); const [settings, setSettings] = useState(readSettings);
const [draftSettings, setDraftSettings] = useState(settings); const [draftSettings, setDraftSettings] = useState(settings);
@@ -197,8 +241,14 @@ function App() {
const clientOs = useMemo(() => detectClientOs(), []); const clientOs = useMemo(() => detectClientOs(), []);
const isClientLinux = clientOs === 'linux'; const isClientLinux = clientOs === 'linux';
const isClientWindows = clientOs === 'windows'; const isClientWindows = clientOs === 'windows';
const canShowAgentCommand = isClientLinux || isClientWindows; const isLocalAgentEndpoint = useMemo(() => isLoopbackAgentEndpoint(agentBaseUrl), [agentBaseUrl]);
const isRemoteAgentEndpoint = !isLocalAgentEndpoint;
const canUseAgentEndpoint = isRemoteAgentEndpoint || isClientLinux;
const canManageApps = canUseAgentEndpoint;
const canShowAgentCommand = isClientLinux || isClientWindows || isRemoteAgentEndpoint;
const clientOsLabel = getClientOsLabel(clientOs); const clientOsLabel = getClientOsLabel(clientOs);
const agentTargetLabel = isRemoteAgentEndpoint ? 'Remote Ubuntu Agent' : 'Local Agent';
const agentTargetMachine = isRemoteAgentEndpoint ? 'remote Ubuntu server' : 'local machine';
const installedByAppId = useMemo(() => { const installedByAppId = useMemo(() => {
return new Map(installedApps.map((app) => [app.appId, app])); return new Map(installedApps.map((app) => [app.appId, app]));
@@ -284,24 +334,24 @@ function App() {
}, [packageBaseUrl]); }, [packageBaseUrl]);
const refreshAgent = useCallback(async () => { const refreshAgent = useCallback(async () => {
if (!isClientLinux) { if (!canUseAgentEndpoint) {
setAgentHealth(null); setAgentHealth(null);
setSystemInfo(null); setSystemInfo(null);
setInstalledApps([]); setInstalledApps([]);
setAgentStatus({ setAgentStatus({
state: 'warning', state: 'warning',
message: isClientWindows message: isClientWindows
? 'Windows detected. Copy the Agent command and run it in Ubuntu SSH.' ? 'Local endpoint points to this Windows machine. Use a remote Ubuntu Agent endpoint.'
: `Current client is ${clientOsLabel}. Web Client only supports Linux.` : `Local endpoint requires Linux. Current client is ${clientOsLabel}.`
}); });
return false; return false;
} }
setAgentStatus({ state: 'loading', message: 'Đang kiểm tra Agent local' }); setAgentStatus({ state: 'loading', message: `Checking ${agentTargetLabel}` });
try { try {
const health = await fetchAgentHealth(agentBaseUrl); const health = await fetchAgentHealth(agentBaseUrl);
setAgentHealth(health); setAgentHealth(health);
setAgentStatus({ state: 'success', message: `${health.hostname || 'Agent'} online` }); setAgentStatus({ state: 'success', message: `${health.hostname || agentTargetLabel} online` });
const [info, installed] = await Promise.all([ const [info, installed] = await Promise.all([
fetchAgentSystemInfo(agentBaseUrl).catch(() => null), fetchAgentSystemInfo(agentBaseUrl).catch(() => null),
@@ -317,7 +367,7 @@ function App() {
setAgentStatus({ state: 'danger', message: getErrorMessage(error) }); setAgentStatus({ state: 'danger', message: getErrorMessage(error) });
return false; return false;
} }
}, [agentBaseUrl, clientOsLabel, isClientLinux, isClientWindows]); }, [agentBaseUrl, agentTargetLabel, canUseAgentEndpoint, clientOsLabel, isClientWindows]);
const refreshAll = useCallback(async () => { const refreshAll = useCallback(async () => {
await Promise.all([refreshPackage(), refreshAgent()]); await Promise.all([refreshPackage(), refreshAgent()]);
@@ -431,17 +481,17 @@ function App() {
}, []); }, []);
const runAppAction = useCallback(async (action, app) => { const runAppAction = useCallback(async (action, app) => {
if (!isClientLinux) { if (!canManageApps) {
notify('warning', 'Web Client chỉ hỗ trợ cài đặt trên máy Linux.'); notify('warning', 'Local Agent endpoint can install only from a Linux browser machine. Use a remote Ubuntu Agent endpoint.');
return; return;
} }
if (!agentHealth) { if (!agentHealth) {
notify('warning', 'Agent local đang offline. Cài Agent rồi bấm Retry.'); notify('warning', `${agentTargetLabel} is offline. Check the endpoint and press Retry.`);
return; return;
} }
if (action === 'remove' && !window.confirm(`Remove ${app.appName} khỏi máy local?`)) { if (action === 'remove' && !window.confirm(`Remove ${app.appName} from ${agentTargetMachine}?`)) {
return; return;
} }
@@ -476,7 +526,7 @@ function App() {
} finally { } finally {
setBusyAction(''); setBusyAction('');
} }
}, [agentBaseUrl, agentHealth, isClientLinux, notify, packageBaseUrl, startPreflightFailedTask, startTask]); }, [agentBaseUrl, agentHealth, agentTargetLabel, agentTargetMachine, canManageApps, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
const openEndpointDialog = useCallback(() => { const openEndpointDialog = useCallback(() => {
setDraftSettings(settings); setDraftSettings(settings);
@@ -502,7 +552,7 @@ function App() {
const copyInstallCommand = useCallback(async () => { const copyInstallCommand = useCallback(async () => {
if (!canShowAgentCommand) { if (!canShowAgentCommand) {
notify('warning', 'Lệnh cài Agent chỉ hiển thị trên máy Linux.'); notify('warning', 'Agent command is hidden for this client OS.');
return; return;
} }
@@ -590,6 +640,19 @@ function App() {
return () => window.removeEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown);
}, [closeEndpointDialog, endpointDialogOpen]); }, [closeEndpointDialog, endpointDialogOpen]);
const agentStatusTitle = !canUseAgentEndpoint
? (isClientWindows ? 'Remote endpoint needed' : 'Linux client required')
: (agentNeedsUpdate ? 'Agent update available' : (agentHealth ? 'Agent online' : 'Agent offline'));
const agentStatusDetail = !canUseAgentEndpoint
? (isClientWindows ? 'Change endpoint to Ubuntu server IP' : `${clientOsLabel} detected`)
: (agentHealth ? `${agentHealth.hostname || agentTargetLabel} · ${agentHealth.agentVersion || '-'}${agentNeedsUpdate ? ` -> ${latestAgentPackage.version}` : ''}` : agentBaseUrl);
const agentStatusTone = !canUseAgentEndpoint
? 'warning'
: (agentNeedsUpdate ? 'warning' : (agentHealth ? 'success' : (agentStatus.state === 'loading' ? 'info' : 'danger')));
const topbarAgentState = !canUseAgentEndpoint
? 'Use Ubuntu Agent endpoint'
: (agentHealth ? `Ready on ${agentTargetMachine}` : `Waiting for ${agentTargetLabel}`);
return ( return (
<div className="app-shell"> <div className="app-shell">
<aside className="sidebar"> <aside className="sidebar">
@@ -607,9 +670,9 @@ function App() {
<div className="nav-label">Runtime</div> <div className="nav-label">Runtime</div>
<StatusBox <StatusBox
icon={agentHealth ? PlugZap : WifiOff} icon={agentHealth ? PlugZap : WifiOff}
title={!isClientLinux ? (isClientWindows ? 'SSH install mode' : 'Linux client required') : (agentNeedsUpdate ? 'Agent update available' : (agentHealth ? 'Agent online' : 'Agent offline'))} title={agentStatusTitle}
detail={!isClientLinux ? (isClientWindows ? 'Windows detected · run command on Ubuntu SSH' : `${clientOsLabel} detected`) : (agentHealth ? `${agentHealth.hostname || 'localhost'} · ${agentHealth.agentVersion || '-'}${agentNeedsUpdate ? ` -> ${latestAgentPackage.version}` : ''}` : '127.0.0.1:5010')} detail={agentStatusDetail}
tone={!isClientLinux ? 'warning' : (agentNeedsUpdate ? 'warning' : (agentHealth ? 'success' : 'danger'))} tone={agentStatusTone}
/> />
<StatusBox <StatusBox
icon={Server} icon={Server}
@@ -625,7 +688,7 @@ function App() {
<strong title={packageBaseUrl}>{packageBaseUrl}</strong> <strong title={packageBaseUrl}>{packageBaseUrl}</strong>
</div> </div>
<div className="endpoint-summary-row"> <div className="endpoint-summary-row">
<span>Local Agent</span> <span>Agent endpoint</span>
<strong title={agentBaseUrl}>{agentBaseUrl}</strong> <strong title={agentBaseUrl}>{agentBaseUrl}</strong>
</div> </div>
</div> </div>
@@ -640,7 +703,7 @@ function App() {
<header className="topbar"> <header className="topbar">
<div className="topbar-title"> <div className="topbar-title">
<span>robot.installer</span> <span>robot.installer</span>
<strong>{!isClientLinux ? (isClientWindows ? 'Copy command for Ubuntu SSH' : 'Linux client required') : (agentHealth ? 'Ready for install' : 'Waiting for Agent')}</strong> <strong>{topbarAgentState}</strong>
</div> </div>
<div className="topbar-actions"> <div className="topbar-actions">
<a className="icon-button" href={joinUrl(packageBaseUrl, '/api/apps')} target="_blank" rel="noreferrer" title="Open package API"> <a className="icon-button" href={joinUrl(packageBaseUrl, '/api/apps')} target="_blank" rel="noreferrer" title="Open package API">
@@ -657,7 +720,7 @@ function App() {
<div className="page-header"> <div className="page-header">
<div> <div>
<h1>Application catalog</h1> <h1>Application catalog</h1>
<p>Released apps từ package server trạng thái cài đặt trên máy local.</p> <p>Released apps from package server and install state on the selected Agent endpoint.</p>
</div> </div>
{canShowAgentCommand && ( {canShowAgentCommand && (
<div className="page-actions"> <div className="page-actions">
@@ -665,7 +728,7 @@ function App() {
<Clipboard size={15} aria-hidden="true" /> <Clipboard size={15} aria-hidden="true" />
{agentNeedsUpdate ? 'Copy Agent update' : 'Copy Agent command'} {agentNeedsUpdate ? 'Copy Agent update' : 'Copy Agent command'}
</button> </button>
{isClientLinux && ( {isClientLinux && isLocalAgentEndpoint && (
<a className="btn btn-primary" href={joinUrl(packageBaseUrl, '/install-agent.sh')} target="_blank" rel="noreferrer"> <a className="btn btn-primary" href={joinUrl(packageBaseUrl, '/install-agent.sh')} target="_blank" rel="noreferrer">
<Download size={15} aria-hidden="true" /> <Download size={15} aria-hidden="true" />
{agentNeedsUpdate ? 'update-agent.sh' : 'install-agent.sh'} {agentNeedsUpdate ? 'update-agent.sh' : 'install-agent.sh'}
@@ -677,12 +740,12 @@ function App() {
<div className="dashboard-stats"> <div className="dashboard-stats">
<MetricCard label="Released apps" value={stats.available} note={packageStatus.state === 'loading' ? 'loading' : 'from API'} /> <MetricCard label="Released apps" value={stats.available} note={packageStatus.state === 'loading' ? 'loading' : 'from API'} />
<MetricCard label="Installed local" value={stats.installed} note={agentHealth ? 'Agent SQLite' : 'Agent offline'} /> <MetricCard label="Installed target" value={stats.installed} note={agentHealth ? 'Agent SQLite' : 'Agent offline'} />
<MetricCard label="Updates" value={stats.updates} note="version diff" tone={stats.updates ? 'warning' : 'success'} /> <MetricCard label="Updates" value={stats.updates} note="version diff" tone={stats.updates ? 'warning' : 'success'} />
<MetricCard label="Components" value={stats.components} note={selectedApp?.appCode || selectedApp?.appId || 'selected app'} /> <MetricCard label="Components" value={stats.components} note={selectedApp?.appCode || selectedApp?.appId || 'selected app'} />
</div> </div>
{!isClientLinux && ( {!canUseAgentEndpoint && (
<ClientOsNotice <ClientOsNotice
agentCommand={agentCommand} agentCommand={agentCommand}
canShowCommand={canShowAgentCommand} canShowCommand={canShowAgentCommand}
@@ -692,11 +755,11 @@ function App() {
/> />
)} )}
{isClientLinux && !agentHealth && ( {canUseAgentEndpoint && !agentHealth && (
<div className="offline-banner"> <div className="offline-banner">
<AlertCircle size={19} aria-hidden="true" /> <AlertCircle size={19} aria-hidden="true" />
<div> <div>
<strong>Local Installer Agent chưa online</strong> <strong>{agentTargetLabel} is offline</strong>
<code>{agentCommand}</code> <code>{agentCommand}</code>
</div> </div>
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}> <button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
@@ -706,11 +769,11 @@ function App() {
</div> </div>
)} )}
{isClientLinux && agentNeedsUpdate && ( {canUseAgentEndpoint && agentNeedsUpdate && (
<div className="offline-banner agent-update-banner"> <div className="offline-banner agent-update-banner">
<AlertCircle size={19} aria-hidden="true" /> <AlertCircle size={19} aria-hidden="true" />
<div> <div>
<strong>Agent {latestAgentPackage.version} đã sẵn sàng</strong> <strong>Agent {latestAgentPackage.version} is ready</strong>
<code>{agentCommand}</code> <code>{agentCommand}</code>
</div> </div>
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}> <button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
@@ -778,9 +841,10 @@ function App() {
const installBusy = busyAction === `install:${app.appId}` || (rowTaskBusy && rowTask.action === 'install'); const installBusy = busyAction === `install:${app.appId}` || (rowTaskBusy && rowTask.action === 'install');
const updateBusy = busyAction === `update:${app.appId}` || (rowTaskBusy && rowTask.action === 'update'); const updateBusy = busyAction === `update:${app.appId}` || (rowTaskBusy && rowTask.action === 'update');
const removeBusy = busyAction === `remove:${app.appId}` || (rowTaskBusy && rowTask.action === 'remove'); const removeBusy = busyAction === `remove:${app.appId}` || (rowTaskBusy && rowTask.action === 'remove');
const openUrl = app.installed const appOpenUrl = app.installed
? getAppOpenUrl({ ...app, openUrl: app.openUrl || app.installed.openUrl }) ? getAppOpenUrl({ ...app, openUrl: app.openUrl || app.installed.openUrl })
: ''; : '';
const openUrl = resolveTargetOpenUrl(appOpenUrl, agentBaseUrl, isRemoteAgentEndpoint);
return ( return (
<tr <tr
@@ -803,7 +867,7 @@ function App() {
<td>{app.packageCount || 0}</td> <td>{app.packageCount || 0}</td>
<td className="action-col"> <td className="action-col">
<div className="action-group"> <div className="action-group">
{isClientLinux && !app.installed && ( {canManageApps && !app.installed && (
<button <button
className="btn btn-primary compact" className="btn btn-primary compact"
type="button" type="button"
@@ -817,7 +881,7 @@ function App() {
Install Install
</button> </button>
)} )}
{isClientLinux && app.installed && app.canUpdate && ( {canManageApps && app.installed && app.canUpdate && (
<button <button
className="btn btn-warning compact" className="btn btn-warning compact"
type="button" type="button"
@@ -831,7 +895,7 @@ function App() {
Update Update
</button> </button>
)} )}
{isClientLinux && app.installed && openUrl && ( {canManageApps && app.installed && openUrl && (
<a <a
className="btn btn-secondary compact" className="btn btn-secondary compact"
href={openUrl} href={openUrl}
@@ -844,7 +908,7 @@ function App() {
Open App Open App
</a> </a>
)} )}
{isClientLinux && app.installed && ( {canManageApps && app.installed && (
<button <button
className="icon-button danger" className="icon-button danger"
type="button" type="button"
@@ -877,10 +941,12 @@ function App() {
health={agentHealth} health={agentHealth}
systemInfo={systemInfo} systemInfo={systemInfo}
status={agentStatus} status={agentStatus}
title={agentTargetLabel}
endpoint={agentBaseUrl}
latestAgentPackage={latestAgentPackage} latestAgentPackage={latestAgentPackage}
needsUpdate={agentNeedsUpdate} needsUpdate={agentNeedsUpdate}
onCopyUpdate={copyInstallCommand} onCopyUpdate={copyInstallCommand}
showAgentActions={isClientLinux} showAgentActions={canShowAgentCommand}
/> />
{activeTask && ( {activeTask && (
<TaskPanel <TaskPanel
@@ -952,7 +1018,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
/> />
</label> </label>
<label className="settings-field"> <label className="settings-field">
<span>Local Agent</span> <span>Agent endpoint</span>
<input <input
value={draftSettings.agentBaseUrl} value={draftSettings.agentBaseUrl}
onChange={(event) => onChange((current) => ({ onChange={(event) => onChange((current) => ({
@@ -1007,11 +1073,11 @@ function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand
<div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}> <div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}>
<AlertCircle size={19} aria-hidden="true" /> <AlertCircle size={19} aria-hidden="true" />
<div> <div>
<strong>{isWindows ? 'Cài Agent qua Ubuntu SSH' : 'Web Client chỉ hỗ trợ máy Linux'}</strong> <strong>{isWindows ? 'Remote Ubuntu endpoint needed' : 'Local Agent requires Linux'}</strong>
<p> <p>
{isWindows {isWindows
? 'Máy hiện tại là Windows. Hãy SSH vào Ubuntu server rồi chạy lệnh bên dưới trong terminal Ubuntu.' ? 'The endpoint is localhost, so it points at this Windows machine. Change it to http://<ubuntu-server-ip>:5010 or run the command below over Ubuntu SSH.'
: `Máy hiện tại được nhận diện là ${osLabel}. Lệnh cài Agent và các thao tác cài đặt đã được ẩn.`} : `Current client is ${osLabel}. Use a remote Ubuntu Agent endpoint or open the Web Client from Linux.`}
</p> </p>
{canShowCommand && <code>{agentCommand}</code>} {canShowCommand && <code>{agentCommand}</code>}
</div> </div>
@@ -1147,13 +1213,13 @@ function TaskPanel({ task, onClear, onRefresh }) {
); );
} }
function AgentPanel({ health, systemInfo, status, latestAgentPackage, needsUpdate, onCopyUpdate, showAgentActions }) { function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPackage, needsUpdate, onCopyUpdate, showAgentActions }) {
return ( return (
<section className="panel"> <section className="panel">
<div className="panel-header"> <div className="panel-header">
<div> <div>
<h2>Local Agent</h2> <h2>{title || 'Agent'}</h2>
<p>{status.message || '127.0.0.1:5010'}</p> <p>{status.message || endpoint || '127.0.0.1:5010'}</p>
</div> </div>
{needsUpdate ? <AlertCircle className="panel-state warning" size={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" size={20} aria-hidden="true" /> : <XCircle className="panel-state danger" size={20} aria-hidden="true" />} {needsUpdate ? <AlertCircle className="panel-state warning" size={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" size={20} aria-hidden="true" /> : <XCircle className="panel-state danger" size={20} aria-hidden="true" />}
</div> </div>

View File

@@ -1422,7 +1422,9 @@ app.get('/install-agent.sh', (req, res) => {
baseUrl, baseUrl,
...publicApiCorsOrigins.filter((origin) => origin !== '*'), ...publicApiCorsOrigins.filter((origin) => origin !== '*'),
'http://localhost:3000', 'http://localhost:3000',
'http://127.0.0.1:3000' 'http://127.0.0.1:3000',
'http://localhost:8080',
'http://127.0.0.1:8080'
])).join(','); ])).join(',');
res.type('text/x-shellscript').send(`#!/usr/bin/env bash res.type('text/x-shellscript').send(`#!/usr/bin/env bash
set -euo pipefail set -euo pipefail
@@ -1434,6 +1436,8 @@ AGENT_ENV="/etc/local-installer-agent/agent.env"
PACKAGE_HOST="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/:]+).*$#\\1#')" PACKAGE_HOST="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/:]+).*$#\\1#')"
PACKAGE_REGISTRY="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/]+).*$#\\1#')" PACKAGE_REGISTRY="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/]+).*$#\\1#')"
TMP_DEB="/tmp/local-installer-agent.deb" TMP_DEB="/tmp/local-installer-agent.deb"
AGENT_BIND_HOST="\${AGENT_HOST:-0.0.0.0}"
AGENT_BIND_PORT="\${AGENT_PORT:-5010}"
set_agent_env() { set_agent_env() {
KEY="$1" KEY="$1"
@@ -1455,6 +1459,8 @@ apt install -y "$TMP_DEB"
echo "Configuring Local Installer Agent..." echo "Configuring Local Installer Agent..."
mkdir -p /etc/local-installer-agent mkdir -p /etc/local-installer-agent
touch "$AGENT_ENV" touch "$AGENT_ENV"
set_agent_env AGENT_HOST "$AGENT_BIND_HOST"
set_agent_env AGENT_PORT "$AGENT_BIND_PORT"
set_agent_env ROBOT_PACKAGE_BASE_URL "$PACKAGE_BASE_URL" set_agent_env ROBOT_PACKAGE_BASE_URL "$PACKAGE_BASE_URL"
set_agent_env ALLOWED_ORIGINS "${escapeShellDoubleQuoted(agentAllowedOrigins)}" set_agent_env ALLOWED_ORIGINS "${escapeShellDoubleQuoted(agentAllowedOrigins)}"
set_agent_env ALLOWED_DOWNLOAD_HOSTS "$PACKAGE_HOST,localhost,127.0.0.1" set_agent_env ALLOWED_DOWNLOAD_HOSTS "$PACKAGE_HOST,localhost,127.0.0.1"
@@ -1468,7 +1474,7 @@ systemctl restart local-installer-agent
echo "Checking Agent..." echo "Checking Agent..."
for attempt in $(seq 1 20); do for attempt in $(seq 1 20); do
if curl -fsSL http://127.0.0.1:5010/health; then if curl -fsSL "http://127.0.0.1:$AGENT_BIND_PORT/health"; then
echo "" echo ""
echo "Local Installer Agent installed successfully." echo "Local Installer Agent installed successfully."
exit 0 exit 0