diff --git a/agents/fedeo-device-agent/scripts/install-linux.sh b/agents/fedeo-device-agent/scripts/install-linux.sh index 17e1bce4..10f80445 100644 --- a/agents/fedeo-device-agent/scripts/install-linux.sh +++ b/agents/fedeo-device-agent/scripts/install-linux.sh @@ -81,7 +81,9 @@ def unit_quote(value): unit_path.write_text('\n'.join([ '[Unit]', 'Description=FEDEO Scan-Agent', 'After=network-online.target', '', '[Service]', 'Type=simple', - 'WorkingDirectory=' + unit_quote(agent_dir), + # WorkingDirectory takes a literal path, unlike Environment/ExecStart. + # Quotes would become part of the path; only systemd specifiers need escaping. + 'WorkingDirectory=' + str(agent_dir).replace('%', '%%'), 'Environment=' + unit_quote('FEDEO_AGENT_ENV=' + str(config_path)), 'ExecStart=' + unit_quote(node) + ' ' + unit_quote(agent_dir / 'dist/main.js'), 'Restart=on-failure', 'RestartSec=5', 'UMask=0077', '', diff --git a/agents/fedeo-device-agent/tests/installer.test.mjs b/agents/fedeo-device-agent/tests/installer.test.mjs index 2d8e0c46..425dd626 100644 --- a/agents/fedeo-device-agent/tests/installer.test.mjs +++ b/agents/fedeo-device-agent/tests/installer.test.mjs @@ -14,6 +14,8 @@ const agentId = 'b48c0e33-1abd-4b11-89fa-1bb7b39a3057' const token = 'fedeo_agent_' + 'a'.repeat(64) const options = { agentId, token, backendUrl: 'https://fedeo.example.com/backend', autostart: true } const execute = promisify(execFile) +const hasSystemdAnalyze = process.platform === 'linux' && await execute('systemd-analyze', ['--version']) + .then(() => true, error => { if (error.code === 'ENOENT') return false; throw error }) test('backend URLs preserve reverse-proxy paths and reject credentials or non-HTTP protocols', () => { assert.equal(agentBackendUrl('/backend/', 'https://fedeo.example.com'), options.backendUrl) @@ -70,9 +72,13 @@ for (const start of [false, true]) { for (const [file, encoded] of Object.entries(bundle.files)) { assert.equal((await readFile(path.join(agentDir, file))).toString('base64'), encoded) } - const unit = await readFile(path.join(env.XDG_CONFIG_HOME, 'systemd/user', 'fedeo-device-agent-' + agentId + '.service'), 'utf8') + const unitPath = path.join(env.XDG_CONFIG_HOME, 'systemd/user', 'fedeo-device-agent-' + agentId + '.service') + const unit = await readFile(unitPath, 'utf8') assert.ok(unit.includes('data with spaces %%')) assert.ok(!unit.includes(token)) + await t.test('systemd accepts the generated service, including a working directory with spaces and percent signs', { skip: !hasSystemdAnalyze }, async () => { + await execute('systemd-analyze', ['--user', 'verify', unitPath]) + }) const calls = await readFile(env.FEDEO_TEST_SYSTEMD_LOG, 'utf8') assert.equal(calls.includes('--user enable fedeo-device-agent-'), start) assert.equal(calls.includes('--user restart fedeo-device-agent-'), start) diff --git a/frontend/assets/device-agent/bundle.json b/frontend/assets/device-agent/bundle.json index 6a699e06..c3b71491 100644 --- a/frontend/assets/device-agent/bundle.json +++ b/frontend/assets/device-agent/bundle.json @@ -1,6 +1,6 @@ { "version": "0.1.0", - "installerTemplate": "#!/usr/bin/env bash\n# Generated by the FEDEO setup wizard. This file contains the agent's access token.\nset -euo pipefail\numask 077\n\nif [[ \"$(uname -s)\" != Linux ]]; then\n echo 'Diese Installation ist für Linux vorgesehen.' >&2\n exit 1\nfi\nif [[ \"$(id -u)\" == 0 ]]; then\n echo 'Bitte dieses Skript als normaler Benutzer ohne sudo starten.' >&2\n exit 1\nfi\nif [[ \"${1:-}\" != '' && \"${1:-}\" != --no-start ]]; then\n echo 'Aufruf: bash install.sh [--no-start]' >&2\n exit 1\nfi\n\nFEDEO_SETUP_ID='@@AGENT_ID@@'\nFEDEO_SETUP_AUTOSTART='@@AUTOSTART@@'\nFEDEO_SETUP_DIR=\"${XDG_DATA_HOME:-$HOME/.local/share}/fedeo-device-agent/$FEDEO_SETUP_ID\"\nFEDEO_SETUP_CONFIG=\"${XDG_CONFIG_HOME:-$HOME/.config}/fedeo-device-agent/$FEDEO_SETUP_ID.env\"\nFEDEO_SETUP_WORK=\"${XDG_STATE_HOME:-$HOME/.local/state}/fedeo-device-agent/$FEDEO_SETUP_ID\"\nFEDEO_SETUP_UNIT=\"fedeo-device-agent-$FEDEO_SETUP_ID.service\"\nFEDEO_SETUP_UNITS=\"${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user\"\n\necho 'FEDEO Scan-Agent wird für diesen Benutzer eingerichtet.'\nif ! command -v node >/dev/null || ! command -v scanimage >/dev/null || ! python3 -c 'import PIL' >/dev/null 2>&1; then\n if ! command -v apt-get >/dev/null; then\n echo 'Bitte Node.js >= 20, SANE (scanimage), Python 3 und Pillow installieren und das Skript erneut starten.' >&2\n exit 1\n fi\n echo 'Fehlende Systempakete werden über apt installiert (sudo benötigt).'\n sudo apt-get update\n sudo apt-get install -y nodejs sane-utils python3-pil\nfi\nnode -e 'if (Number(process.versions.node.split(\".\")[0]) < 20) { console.error(\"Node.js >= 20 wird benötigt. Bitte Node.js aktualisieren und die Installation erneut starten.\"); process.exit(1) }'\npython3 -c 'import PIL'\n\nFEDEO_SETUP_NODE=\"$(command -v node)\"\nFEDEO_SETUP_HAS_SYSTEMD=false\nif command -v systemctl >/dev/null && systemctl --user show-environment >/dev/null 2>&1; then\n FEDEO_SETUP_HAS_SYSTEMD=true\nfi\nif [[ \"$FEDEO_SETUP_AUTOSTART\" == true && \"$FEDEO_SETUP_HAS_SYSTEMD\" != true ]]; then\n echo 'Der Benutzerdienst von systemd ist nicht erreichbar. Bitte in der Desktop-Sitzung starten oder den Autostart im Assistenten ausschalten.' >&2\n exit 1\nfi\n\nmkdir -p \"$FEDEO_SETUP_DIR\" \"$(dirname \"$FEDEO_SETUP_CONFIG\")\" \"$FEDEO_SETUP_WORK\" \"$FEDEO_SETUP_UNITS\"\n# Stop only the matching installation before replacing its files.\nif [[ \"$FEDEO_SETUP_HAS_SYSTEMD\" == true && \"${1:-}\" != --no-start ]]; then\n systemctl --user stop \"$FEDEO_SETUP_UNIT\" 2>/dev/null || true\nfi\n\npython3 - \"$FEDEO_SETUP_DIR\" \"$FEDEO_SETUP_CONFIG\" \"$FEDEO_SETUP_WORK\" \"$FEDEO_SETUP_NODE\" \"$FEDEO_SETUP_UNITS/$FEDEO_SETUP_UNIT\" <<'FEDEO_PYTHON'\nimport base64\nimport json\nimport os\nfrom pathlib import Path\nimport sys\n\nagent_dir, config_path, work_dir, node, unit_path = map(Path, sys.argv[1:])\nfiles = json.loads(base64.b64decode('@@PAYLOAD_BASE64@@'))\nfor name, encoded in files.items():\n relative = Path(name)\n if relative.is_absolute() or '..' in relative.parts:\n raise ValueError('Ungültiger Dateiname im Installationspaket')\n target = agent_dir / relative\n target.parent.mkdir(parents=True, exist_ok=True)\n target.write_bytes(base64.b64decode(encoded, validate=True))\n\nconfig = base64.b64decode('@@CONFIG_BASE64@@').decode('utf8')\nconfig += 'FEDEO_WORK_DIR=' + str(work_dir) + '\\n'\nconfig_path.write_text(config, encoding='utf8')\nconfig_path.chmod(0o600)\n\ndef unit_quote(value):\n return '\"' + str(value).replace('\\\\', '\\\\\\\\').replace('\"', '\\\\\"').replace('%', '%%') + '\"'\n\nunit_path.write_text('\\n'.join([\n '[Unit]', 'Description=FEDEO Scan-Agent', 'After=network-online.target', '',\n '[Service]', 'Type=simple',\n 'WorkingDirectory=' + unit_quote(agent_dir),\n 'Environment=' + unit_quote('FEDEO_AGENT_ENV=' + str(config_path)),\n 'ExecStart=' + unit_quote(node) + ' ' + unit_quote(agent_dir / 'dist/main.js'),\n 'Restart=on-failure', 'RestartSec=5', 'UMask=0077', '',\n '[Install]', 'WantedBy=default.target', '',\n]), encoding='utf8')\nFEDEO_PYTHON\n\necho \"Agent installiert: $FEDEO_SETUP_DIR\"\necho \"Konfiguration: $FEDEO_SETUP_CONFIG\"\nif [[ \"${1:-}\" == --no-start ]]; then\n echo 'Installation abgeschlossen; Agent wurde nicht gestartet.'\nelif [[ \"$FEDEO_SETUP_AUTOSTART\" == true ]]; then\n systemctl --user daemon-reload\n systemctl --user enable \"$FEDEO_SETUP_UNIT\"\n systemctl --user restart \"$FEDEO_SETUP_UNIT\"\n echo 'Agent gestartet. Er startet künftig bei der Anmeldung.'\n printf 'Protokoll: journalctl --user -u %q -f\\n' \"$FEDEO_SETUP_UNIT\"\n echo 'Jetzt im FEDEO-Assistenten die Verbindung prüfen. Die heruntergeladene Installationsdatei kann anschließend gelöscht werden.'\nelse\n if [[ \"$FEDEO_SETUP_HAS_SYSTEMD\" == true ]]; then\n systemctl --user disable \"$FEDEO_SETUP_UNIT\" 2>/dev/null || true\n systemctl --user daemon-reload\n fi\n echo 'Agent startet im Terminal. Dieses Fenster geöffnet lassen; Beenden mit Strg+C.'\n cd \"$FEDEO_SETUP_DIR\"\n FEDEO_AGENT_ENV=\"$FEDEO_SETUP_CONFIG\" \"$FEDEO_SETUP_NODE\" dist/main.js\nfi\n", + "installerTemplate": "#!/usr/bin/env bash\n# Generated by the FEDEO setup wizard. This file contains the agent's access token.\nset -euo pipefail\numask 077\n\nif [[ \"$(uname -s)\" != Linux ]]; then\n echo 'Diese Installation ist für Linux vorgesehen.' >&2\n exit 1\nfi\nif [[ \"$(id -u)\" == 0 ]]; then\n echo 'Bitte dieses Skript als normaler Benutzer ohne sudo starten.' >&2\n exit 1\nfi\nif [[ \"${1:-}\" != '' && \"${1:-}\" != --no-start ]]; then\n echo 'Aufruf: bash install.sh [--no-start]' >&2\n exit 1\nfi\n\nFEDEO_SETUP_ID='@@AGENT_ID@@'\nFEDEO_SETUP_AUTOSTART='@@AUTOSTART@@'\nFEDEO_SETUP_DIR=\"${XDG_DATA_HOME:-$HOME/.local/share}/fedeo-device-agent/$FEDEO_SETUP_ID\"\nFEDEO_SETUP_CONFIG=\"${XDG_CONFIG_HOME:-$HOME/.config}/fedeo-device-agent/$FEDEO_SETUP_ID.env\"\nFEDEO_SETUP_WORK=\"${XDG_STATE_HOME:-$HOME/.local/state}/fedeo-device-agent/$FEDEO_SETUP_ID\"\nFEDEO_SETUP_UNIT=\"fedeo-device-agent-$FEDEO_SETUP_ID.service\"\nFEDEO_SETUP_UNITS=\"${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user\"\n\necho 'FEDEO Scan-Agent wird für diesen Benutzer eingerichtet.'\nif ! command -v node >/dev/null || ! command -v scanimage >/dev/null || ! python3 -c 'import PIL' >/dev/null 2>&1; then\n if ! command -v apt-get >/dev/null; then\n echo 'Bitte Node.js >= 20, SANE (scanimage), Python 3 und Pillow installieren und das Skript erneut starten.' >&2\n exit 1\n fi\n echo 'Fehlende Systempakete werden über apt installiert (sudo benötigt).'\n sudo apt-get update\n sudo apt-get install -y nodejs sane-utils python3-pil\nfi\nnode -e 'if (Number(process.versions.node.split(\".\")[0]) < 20) { console.error(\"Node.js >= 20 wird benötigt. Bitte Node.js aktualisieren und die Installation erneut starten.\"); process.exit(1) }'\npython3 -c 'import PIL'\n\nFEDEO_SETUP_NODE=\"$(command -v node)\"\nFEDEO_SETUP_HAS_SYSTEMD=false\nif command -v systemctl >/dev/null && systemctl --user show-environment >/dev/null 2>&1; then\n FEDEO_SETUP_HAS_SYSTEMD=true\nfi\nif [[ \"$FEDEO_SETUP_AUTOSTART\" == true && \"$FEDEO_SETUP_HAS_SYSTEMD\" != true ]]; then\n echo 'Der Benutzerdienst von systemd ist nicht erreichbar. Bitte in der Desktop-Sitzung starten oder den Autostart im Assistenten ausschalten.' >&2\n exit 1\nfi\n\nmkdir -p \"$FEDEO_SETUP_DIR\" \"$(dirname \"$FEDEO_SETUP_CONFIG\")\" \"$FEDEO_SETUP_WORK\" \"$FEDEO_SETUP_UNITS\"\n# Stop only the matching installation before replacing its files.\nif [[ \"$FEDEO_SETUP_HAS_SYSTEMD\" == true && \"${1:-}\" != --no-start ]]; then\n systemctl --user stop \"$FEDEO_SETUP_UNIT\" 2>/dev/null || true\nfi\n\npython3 - \"$FEDEO_SETUP_DIR\" \"$FEDEO_SETUP_CONFIG\" \"$FEDEO_SETUP_WORK\" \"$FEDEO_SETUP_NODE\" \"$FEDEO_SETUP_UNITS/$FEDEO_SETUP_UNIT\" <<'FEDEO_PYTHON'\nimport base64\nimport json\nimport os\nfrom pathlib import Path\nimport sys\n\nagent_dir, config_path, work_dir, node, unit_path = map(Path, sys.argv[1:])\nfiles = json.loads(base64.b64decode('@@PAYLOAD_BASE64@@'))\nfor name, encoded in files.items():\n relative = Path(name)\n if relative.is_absolute() or '..' in relative.parts:\n raise ValueError('Ungültiger Dateiname im Installationspaket')\n target = agent_dir / relative\n target.parent.mkdir(parents=True, exist_ok=True)\n target.write_bytes(base64.b64decode(encoded, validate=True))\n\nconfig = base64.b64decode('@@CONFIG_BASE64@@').decode('utf8')\nconfig += 'FEDEO_WORK_DIR=' + str(work_dir) + '\\n'\nconfig_path.write_text(config, encoding='utf8')\nconfig_path.chmod(0o600)\n\ndef unit_quote(value):\n return '\"' + str(value).replace('\\\\', '\\\\\\\\').replace('\"', '\\\\\"').replace('%', '%%') + '\"'\n\nunit_path.write_text('\\n'.join([\n '[Unit]', 'Description=FEDEO Scan-Agent', 'After=network-online.target', '',\n '[Service]', 'Type=simple',\n # WorkingDirectory takes a literal path, unlike Environment/ExecStart.\n # Quotes would become part of the path; only systemd specifiers need escaping.\n 'WorkingDirectory=' + str(agent_dir).replace('%', '%%'),\n 'Environment=' + unit_quote('FEDEO_AGENT_ENV=' + str(config_path)),\n 'ExecStart=' + unit_quote(node) + ' ' + unit_quote(agent_dir / 'dist/main.js'),\n 'Restart=on-failure', 'RestartSec=5', 'UMask=0077', '',\n '[Install]', 'WantedBy=default.target', '',\n]), encoding='utf8')\nFEDEO_PYTHON\n\necho \"Agent installiert: $FEDEO_SETUP_DIR\"\necho \"Konfiguration: $FEDEO_SETUP_CONFIG\"\nif [[ \"${1:-}\" == --no-start ]]; then\n echo 'Installation abgeschlossen; Agent wurde nicht gestartet.'\nelif [[ \"$FEDEO_SETUP_AUTOSTART\" == true ]]; then\n systemctl --user daemon-reload\n systemctl --user enable \"$FEDEO_SETUP_UNIT\"\n systemctl --user restart \"$FEDEO_SETUP_UNIT\"\n echo 'Agent gestartet. Er startet künftig bei der Anmeldung.'\n printf 'Protokoll: journalctl --user -u %q -f\\n' \"$FEDEO_SETUP_UNIT\"\n echo 'Jetzt im FEDEO-Assistenten die Verbindung prüfen. Die heruntergeladene Installationsdatei kann anschließend gelöscht werden.'\nelse\n if [[ \"$FEDEO_SETUP_HAS_SYSTEMD\" == true ]]; then\n systemctl --user disable \"$FEDEO_SETUP_UNIT\" 2>/dev/null || true\n systemctl --user daemon-reload\n fi\n echo 'Agent startet im Terminal. Dieses Fenster geöffnet lassen; Beenden mit Strg+C.'\n cd \"$FEDEO_SETUP_DIR\"\n FEDEO_AGENT_ENV=\"$FEDEO_SETUP_CONFIG\" \"$FEDEO_SETUP_NODE\" dist/main.js\nfi\n", "files": { "dist/api.js": "aW1wb3J0IHsgcmVhZEZpbGUsIHN0YXQgfSBmcm9tICJub2RlOmZzL3Byb21pc2VzIjsKaW1wb3J0IHsgYmFzZW5hbWUgfSBmcm9tICJub2RlOnBhdGgiOwpleHBvcnQgY2xhc3MgRmVkZW9BcGkgewogICAgY29uZmlnOwogICAgY29uc3RydWN0b3IoY29uZmlnKSB7CiAgICAgICAgdGhpcy5jb25maWcgPSBjb25maWc7CiAgICB9CiAgICB1cmwocGF0aCkgewogICAgICAgIHJldHVybiBgJHt0aGlzLmNvbmZpZy5mZWRlb1VybH0ke3BhdGh9YDsKICAgIH0KICAgIGhlYWRlcnMoZXh0cmEpIHsKICAgICAgICByZXR1cm4gewogICAgICAgICAgICAiWC1BZ2VudC1Ub2tlbiI6IHRoaXMuY29uZmlnLmFnZW50VG9rZW4sCiAgICAgICAgICAgIC4uLmV4dHJhLAogICAgICAgIH07CiAgICB9CiAgICBhc3luYyByZXF1ZXN0KHBhdGgsIGluaXQgPSB7fSwgdGltZW91dE1zID0gMzBfMDAwKSB7CiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLnVybChwYXRoKSwgewogICAgICAgICAgICAuLi5pbml0LAogICAgICAgICAgICBoZWFkZXJzOiB0aGlzLmhlYWRlcnMoaW5pdC5oZWFkZXJzKSwKICAgICAgICAgICAgc2lnbmFsOiBBYm9ydFNpZ25hbC50aW1lb3V0KHRpbWVvdXRNcyksCiAgICAgICAgfSk7CiAgICAgICAgaWYgKCFyZXNwb25zZS5vaykgewogICAgICAgICAgICBjb25zdCBib2R5ID0gYXdhaXQgcmVzcG9uc2UudGV4dCgpLmNhdGNoKCgpID0+ICIiKTsKICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGAke2luaXQubWV0aG9kIHx8ICJHRVQifSAke3BhdGh9IGZlaGxnZXNjaGxhZ2VuOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtib2R5fWApOwogICAgICAgIH0KICAgICAgICByZXR1cm4gYXdhaXQgcmVzcG9uc2UuanNvbigpOwogICAgfQogICAgaGVhcnRiZWF0KHBheWxvYWQpIHsKICAgICAgICByZXR1cm4gdGhpcy5yZXF1ZXN0KCIvaW5zdGFuY2UtYWdlbnQvaGVhcnRiZWF0IiwgewogICAgICAgICAgICBtZXRob2Q6ICJQT1NUIiwKICAgICAgICAgICAgaGVhZGVyczogeyAiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL2pzb24iIH0sCiAgICAgICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHBheWxvYWQpLAogICAgICAgIH0pOwogICAgfQogICAgbmV4dFNjYW5Kb2IoKSB7CiAgICAgICAgcmV0dXJuIHRoaXMucmVxdWVzdCgiL2luc3RhbmNlLWFnZW50L3NjYW4tam9icy9uZXh0Iik7CiAgICB9CiAgICB1cGRhdGVTY2FuSm9iU3RhdHVzKGpvYklkLCBzdGF0dXMsIG1lc3NhZ2UpIHsKICAgICAgICByZXR1cm4gdGhpcy5yZXF1ZXN0KGAvaW5zdGFuY2UtYWdlbnQvc2Nhbi1qb2JzLyR7am9iSWR9L3N0YXR1c2AsIHsKICAgICAgICAgICAgbWV0aG9kOiAiUE9TVCIsCiAgICAgICAgICAgIGhlYWRlcnM6IHsgIkNvbnRlbnQtVHlwZSI6ICJhcHBsaWNhdGlvbi9qc29uIiB9LAogICAgICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IHN0YXR1cywgbWVzc2FnZSB9KSwKICAgICAgICB9KTsKICAgIH0KICAgIGFzeW5jIHVwbG9hZFNjYW4oam9iSWQsIHJlc3VsdCkgewogICAgICAgIGlmICgoYXdhaXQgc3RhdChyZXN1bHQucGF0aCkpLnNpemUgPiAxMDAgKiAxMDI0ICogMTAyNCkgewogICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoIkRpZSBTY2FuLURhdGVpIMO8YmVyc2NocmVpdGV0IGRhcyBVcGxvYWQtTGltaXQgdm9uIDEwMCBNaUIuIEJpdHRlIGRlbiBTdGFwZWwgYXVmdGVpbGVuIG9kZXIgZGllIEF1ZmzDtnN1bmcgcmVkdXppZXJlbi4iKTsKICAgICAgICB9CiAgICAgICAgY29uc3QgZm9ybSA9IG5ldyBGb3JtRGF0YSgpOwogICAgICAgIGNvbnN0IGZpbGVCdWZmZXIgPSBhd2FpdCByZWFkRmlsZShyZXN1bHQucGF0aCk7CiAgICAgICAgY29uc3QgZmlsZSA9IG5ldyBGaWxlKFtmaWxlQnVmZmVyXSwgcmVzdWx0LmZpbGVuYW1lIHx8IGJhc2VuYW1lKHJlc3VsdC5wYXRoKSwgewogICAgICAgICAgICB0eXBlOiByZXN1bHQubWltZVR5cGUsCiAgICAgICAgfSk7CiAgICAgICAgZm9ybS5hcHBlbmQoImZpbGUiLCBmaWxlKTsKICAgICAgICByZXR1cm4gdGhpcy5yZXF1ZXN0KGAvaW5zdGFuY2UtYWdlbnQvc2Nhbi1qb2JzLyR7am9iSWR9L3VwbG9hZGAsIHsKICAgICAgICAgICAgbWV0aG9kOiAiUE9TVCIsCiAgICAgICAgICAgIGJvZHk6IGZvcm0sCiAgICAgICAgfSwgMTIwXzAwMCk7CiAgICB9Cn0K", "dist/commands.js": "aW1wb3J0IHsgc3Bhd24gfSBmcm9tICJub2RlOmNoaWxkX3Byb2Nlc3MiOwpleHBvcnQgY29uc3QgY29tbWFuZEV4aXN0cyA9IChjb21tYW5kKSA9PiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgY29uc3QgY2hpbGQgPSBzcGF3bigic2giLCBbIi1jIiwgJ2NvbW1hbmQgLXYgIiQxIiA+L2Rldi9udWxsIDI+JjEnLCAiLS0iLCBjb21tYW5kXSk7CiAgICBjaGlsZC5vbigiZXJyb3IiLCAoKSA9PiByZXNvbHZlKGZhbHNlKSk7CiAgICBjaGlsZC5vbigiY2xvc2UiLCAoY29kZSkgPT4gcmVzb2x2ZShjb2RlID09PSAwKSk7Cn0pOwpleHBvcnQgY29uc3QgcnVuQ29tbWFuZCA9IChjb21tYW5kLCBhcmdzLCBvcHRpb25zID0ge30pID0+IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgIGNvbnN0IGNoaWxkID0gc3Bhd24oY29tbWFuZCwgYXJncywgewogICAgICAgIHN0ZGlvOiBbImlnbm9yZSIsICJwaXBlIiwgInBpcGUiXSwKICAgICAgICBjd2Q6IG9wdGlvbnMuY3dkLAogICAgfSk7CiAgICBjb25zdCBzdGRvdXQgPSBbXTsKICAgIGNvbnN0IHN0ZGVyciA9IFtdOwogICAgbGV0IHRpbWVkT3V0ID0gZmFsc2U7CiAgICBsZXQga2lsbFRpbWVvdXQ7CiAgICBjb25zdCB0aW1lb3V0ID0gb3B0aW9ucy50aW1lb3V0TXMKICAgICAgICA/IHNldFRpbWVvdXQoKCkgPT4gewogICAgICAgICAgICB0aW1lZE91dCA9IHRydWU7CiAgICAgICAgICAgIGNoaWxkLmtpbGwoIlNJR1RFUk0iKTsKICAgICAgICAgICAga2lsbFRpbWVvdXQgPSBzZXRUaW1lb3V0KCgpID0+IGNoaWxkLmtpbGwoIlNJR0tJTEwiKSwgMjAwMCk7CiAgICAgICAgfSwgb3B0aW9ucy50aW1lb3V0TXMpCiAgICAgICAgOiBudWxsOwogICAgY2hpbGQuc3Rkb3V0Lm9uKCJkYXRhIiwgKGNodW5rKSA9PiBzdGRvdXQucHVzaChCdWZmZXIuZnJvbShjaHVuaykpKTsKICAgIGNoaWxkLnN0ZGVyci5vbigiZGF0YSIsIChjaHVuaykgPT4gc3RkZXJyLnB1c2goQnVmZmVyLmZyb20oY2h1bmspKSk7CiAgICBjaGlsZC5vbigiZXJyb3IiLCAoZXJyb3IpID0+IHsKICAgICAgICBpZiAodGltZW91dCkKICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpOwogICAgICAgIGlmIChraWxsVGltZW91dCkKICAgICAgICAgICAgY2xlYXJUaW1lb3V0KGtpbGxUaW1lb3V0KTsKICAgICAgICByZWplY3QoZXJyb3IpOwogICAgfSk7CiAgICBjaGlsZC5vbigiY2xvc2UiLCAoY29kZSwgc2lnbmFsKSA9PiB7CiAgICAgICAgaWYgKHRpbWVvdXQpCiAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0KTsKICAgICAgICBpZiAoa2lsbFRpbWVvdXQpCiAgICAgICAgICAgIGNsZWFyVGltZW91dChraWxsVGltZW91dCk7CiAgICAgICAgaWYgKHRpbWVkT3V0KSB7CiAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYCR7Y29tbWFuZH0gd3VyZGUgbmFjaCAke29wdGlvbnMudGltZW91dE1zfSBtcyBiZWVuZGV0YCkpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQogICAgICAgIGlmIChzaWduYWwpIHsKICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgJHtjb21tYW5kfSB3dXJkZSBkdXJjaCAke3NpZ25hbH0gYmVlbmRldGApKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICByZXNvbHZlKHsKICAgICAgICAgICAgc3Rkb3V0OiBCdWZmZXIuY29uY2F0KHN0ZG91dCkudG9TdHJpbmcoInV0ZjgiKSwKICAgICAgICAgICAgc3RkZXJyOiBCdWZmZXIuY29uY2F0KHN0ZGVycikudG9TdHJpbmcoInV0ZjgiKSwKICAgICAgICAgICAgY29kZTogY29kZSA/PyAxLAogICAgICAgIH0pOwogICAgfSk7Cn0pOwo=",