From 122fb6e7d296922602764ea8033c41aceb89d499 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 3 Nov 2023 23:46:22 +0100 Subject: [PATCH] New Plugin: Tmate --- debian/control | 9 + debian/nymea-plugin-tmate.install.in | 2 + nymea-plugins.pro | 1 + tmate/README.md | 26 +++ tmate/integrationplugintmate.cpp | 194 ++++++++++++++++++ tmate/integrationplugintmate.h | 62 ++++++ tmate/integrationplugintmate.json | 131 ++++++++++++ tmate/logo.png | Bin 0 -> 2998 bytes tmate/meta.json | 13 ++ tmate/tmate.pro | 8 + ...ab0d1-dbfe-48af-b196-523cc37a1e5e-en_US.ts | 103 ++++++++++ 11 files changed, 549 insertions(+) create mode 100644 debian/nymea-plugin-tmate.install.in create mode 100644 tmate/README.md create mode 100644 tmate/integrationplugintmate.cpp create mode 100644 tmate/integrationplugintmate.h create mode 100644 tmate/integrationplugintmate.json create mode 100644 tmate/logo.png create mode 100644 tmate/meta.json create mode 100644 tmate/tmate.pro create mode 100644 tmate/translations/d06ab0d1-dbfe-48af-b196-523cc37a1e5e-en_US.ts diff --git a/debian/control b/debian/control index d56a6ac5..49ef4ffd 100644 --- a/debian/control +++ b/debian/control @@ -581,6 +581,15 @@ Description: nymea integration plugin for Tempo time tracking This package contains the nymea integration plugin for Tempo time tracking. +Package: nymea-plugin-tmate +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + tmate, +Description: nymea integration plugin for tmate + This package contains the nymea integration plugin for tmate terminal access. + + Package: nymea-plugin-tplink Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-tmate.install.in b/debian/nymea-plugin-tmate.install.in new file mode 100644 index 00000000..709018cb --- /dev/null +++ b/debian/nymea-plugin-tmate.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationplugintmate.so +tmate/translations/*qm usr/share/nymea/translations/ diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 969f2796..6e9da803 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -75,6 +75,7 @@ PLUGIN_DIRS = \ telegram \ tempo \ texasinstruments \ + tmate \ tplink \ tuya \ udpcommander \ diff --git a/tmate/README.md b/tmate/README.md new file mode 100644 index 00000000..d6bcfe38 --- /dev/null +++ b/tmate/README.md @@ -0,0 +1,26 @@ +# Tmate + +This plugin allows to establish a terminal connection to the device where nymea is running. + +This is useful when maintaining remote nymea setups which may be hidden behind a firewall +and cannot be accessed from the public internet. A user can easily enable SSH access for +a nymea setup by adding a Thing using the app without having to deal with DNS and NAT. + +## Setup + +A tmate thing can be created without any additional information. Leaving all the fields +empty during setup will establish a default tmate session to tmate.io servers and generate +a new random token. This is the equivalent of just running `tmate` in a terminal. + +For a more permanent solution, an API key can be registered at tmate.io. Using the api key a custom session name can be set which will be used for every connection establishment, persisting across nymea restarts. In the +next step, provide the SSH credentials for the user on the SSH server which has been created before. Once the login succeeds, the thing should become connected. + +> Note: Using custom/self hosted tmate servers is not supported at this point. + +### Connecting clients + +The thing will inform about the session names via states and can be accessed via ssh or web. + +## More + +https://tmate.io/ diff --git a/tmate/integrationplugintmate.cpp b/tmate/integrationplugintmate.cpp new file mode 100644 index 00000000..c7e0d006 --- /dev/null +++ b/tmate/integrationplugintmate.cpp @@ -0,0 +1,194 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2023, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationplugintmate.h" +#include "plugininfo.h" + +#include +#include +#include + +IntegrationPluginTmate::IntegrationPluginTmate() +{ + +} + +IntegrationPluginTmate::~IntegrationPluginTmate() +{ + foreach (QProcess *process, m_processes) { + process->terminate(); + } +} + +void IntegrationPluginTmate::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + + QStringList arguments; + QString apiKey = thing->paramValue(tmateThingApiKeyParamTypeId).toString(); + QString sessionName = thing->paramValue(tmateThingSessionNameParamTypeId).toString(); + + arguments << "-F"; + if (!apiKey.isEmpty()) { + arguments << "-k" << apiKey; + if (!sessionName.isEmpty()) { + arguments << "-n" << sessionName; + arguments << "-r" << "ro-" + sessionName; + } + } + QProcess *process = new QProcess(thing); + process->setProgram("tmate"); + process->setArguments(arguments); + process->setProcessChannelMode(QProcess::MergedChannels); + + m_processes.insert(info->thing(), process); + + connect(process, &QProcess::stateChanged, thing, [=](QProcess::ProcessState newState){ + switch (newState) { + case QProcess::Starting: + qCDebug(dcTmate()) << "Connection starting for" << thing->name(); + return ; + case QProcess::Running: + qCInfo(dcTmate()) << "Reverse SSH connected for" << thing->name(); + thing->setStateValue(tmateConnectedStateTypeId, true); + return; + case QProcess::NotRunning: + qCInfo(dcTmate()) << "Reverse SSH disconnected for" << thing->name(); + thing->setStateValue(tmateConnectedStateTypeId, false); + thing->setStateValue(tmateSshStateTypeId, ""); + thing->setStateValue(tmateSshRoStateTypeId, ""); + thing->setStateValue(tmateWebStateTypeId, ""); + thing->setStateValue(tmateWebRoStateTypeId, ""); + thing->setStateValue(tmateClientsStateTypeId, 0); + return; + } + }); + connect(process, &QProcess::readyRead, thing, [=](){ + while (process->canReadLine()) { + QByteArray data = process->readLine(); + + qCDebug(dcTmate()) << thing->name() << ":" << data; + auto extractSession = [thing](const StateTypeId &stateTypeId, const QString &type, const QString &input) { + int sessionStart = input.indexOf(type); + if (sessionStart >= 0) { + int sessionEnd = input.indexOf(QChar('\n'), sessionStart); + qCInfo(dcTmate()) << input << "Session start" << sessionStart << "session end" << sessionEnd; + QString session =input.mid(sessionStart + type.length(), sessionEnd); + thing->setStateValue(stateTypeId, session); + } + }; + + extractSession(tmateSshStateTypeId, "ssh session: ssh ", data); + extractSession(tmateSshRoStateTypeId, "ssh session read only: ssh ", data); + extractSession(tmateWebStateTypeId, "web session: ", data); + extractSession(tmateWebRoStateTypeId, "web session read only: ", data); + + QRegularExpression joinAddressRegex("joined \\(([0-9\\.]+)\\)"); + QRegularExpressionMatchIterator it = joinAddressRegex.globalMatch(data); + while (it.hasNext()) { + QRegularExpressionMatch match = it.next(); + QString word = match.captured(1); + qCInfo(dcTmate()) << "Connected:" << word; + thing->emitEvent(tmateClientConnectedEventTypeId, {{tmateClientConnectedEventClientAddressParamTypeId, word}}); + thing->setStateValue(tmateClientsStateTypeId, thing->stateValue(tmateClientsStateTypeId).toUInt()+1); + } + QRegularExpression leftAddressRegex("left \\(([0-9\\.]+)\\)"); + it = leftAddressRegex.globalMatch(data); + while (it.hasNext()) { + QRegularExpressionMatch match = it.next(); + QString word = match.captured(1); + qCInfo(dcTmate()) << "Disconnected:" << word; + thing->emitEvent(tmateClientDisconnectedEventTypeId, {{tmateClientDisconnectedEventClientAddressParamTypeId, word}}); + thing->setStateValue(tmateClientsStateTypeId, thing->stateValue(tmateClientsStateTypeId).toUInt()-1); + } + } + + }); + + + // Start up now if enabled + bool enabled = thing->stateValue(tmateActiveStateTypeId).toBool(); + if (enabled) { + process->start(); + } + + info->finish(Thing::ThingErrorNoError); + + + // Create a watchdog to reconnect if a connection drops... + if (!m_watchdog) { + m_watchdog = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_watchdog, &PluginTimer::timeout, this, [this](){ + foreach (Thing *thing, m_processes.keys()) { + QProcess *process = m_processes.value(thing); + if (thing->stateValue(tmateActiveStateTypeId).toBool() && process->state() == QProcess::NotRunning) { + qCInfo(dcTmate()) << "Reconnecting tmate for" << thing->name(); + process->start(); + } + } + }); + } +} + + +void IntegrationPluginTmate::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == tmateThingClassId) { + QProcess *process = m_processes.take(thing); + if (process->state() != QProcess::NotRunning) { + process->terminate(); + process->waitForFinished(); + } + } + + if (myThings().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_watchdog); + m_watchdog = nullptr; + } +} + + +void IntegrationPluginTmate::executeAction(ThingActionInfo *info) +{ + if (info->action().actionTypeId() == tmateActiveActionTypeId) { + bool active = info->action().paramValue(tmateActiveActionActiveParamTypeId).toBool(); + QProcess *process = m_processes.value(info->thing()); + if (active) { + qCInfo(dcTmate()) << "Reconnecting tmate for" << info->thing()->name(); + process->start(); + } else { + qCInfo(dcTmate()) << "Terminating session for" << info->thing()->name(); + process->terminate(); + } + info->thing()->setStateValue(tmateActiveStateTypeId, active); + info->finish(Thing::ThingErrorNoError); + } +} diff --git a/tmate/integrationplugintmate.h b/tmate/integrationplugintmate.h new file mode 100644 index 00000000..b308445e --- /dev/null +++ b/tmate/integrationplugintmate.h @@ -0,0 +1,62 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2023, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINREMOTESSH_H +#define INTEGRATIONPLUGINREMOTESSH_H + +#include "plugintimer.h" +#include "integrations/integrationplugin.h" +#include "extern-plugininfo.h" + +#include + +class IntegrationPluginTmate : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationplugintmate.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginTmate(); + ~IntegrationPluginTmate(); + +// void startPairing(ThingPairingInfo *info) override; +// void confirmPairing(ThingPairingInfo *info, const QString &user, const QString &secret) override; + void executeAction(ThingActionInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void thingRemoved(Thing *thing) override; + +private: + QHash m_processes; + PluginTimer *m_watchdog = nullptr; +}; + +#endif // INTEGRATIONPLUGINREMOTESSH_H diff --git a/tmate/integrationplugintmate.json b/tmate/integrationplugintmate.json new file mode 100644 index 00000000..5329448a --- /dev/null +++ b/tmate/integrationplugintmate.json @@ -0,0 +1,131 @@ +{ + "id": "d06ab0d1-dbfe-48af-b196-523cc37a1e5e", + "name": "Tmate", + "displayName": "Tmate", + "vendors": [ + { + "id": "b948d5e2-bfc6-4e28-a2ba-e40e46f4c213", + "name": "tmate", + "displayName": "Tmate", + "thingClasses": [ + { + "id": "3f06ad52-9514-41b1-9bf9-031241d34634", + "name": "tmate", + "displayName": "Tmate", + "createMethods": ["user"], + "setupMethod": "justadd", + "interfaces": [], + "paramTypes": [ + { + "id": "01f0c818-55e1-4842-a9b9-cc58bbfe76c6", + "name": "apiKey", + "displayName": "API key (optional)", + "type": "QString", + "defaultValue": "" + }, + { + "id": "e587e3dc-0beb-441f-8f07-b23c25580b10", + "name": "sessionName", + "displayName": "Session name (requires API key usage)", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes":[ + { + "id": "7009c176-e1aa-49bc-818c-63f7a9027306", + "name": "active", + "displayName": "Active", + "displayNameAction": "Set active", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "beac3113-04f1-4d70-875b-44ca8b307866", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false, + "suggestLogging": true, + "cached": false + }, + { + "id": "ef780fb1-b31a-4333-944b-a02bf3297fea", + "name": "sshRo", + "displayName": "SSH RO", + "type": "QString", + "defaultValue": "", + "cached": false + }, + { + "id": "e8ff1b90-7701-454c-a557-4b91dc8c649b", + "name": "ssh", + "displayName": "SSH", + "type": "QString", + "defaultValue": "", + "cached": false + }, + { + "id": "98248bc0-ddda-4ae6-8558-4d7155a39c33", + "name": "webRo", + "displayName": "Web RO", + "type": "QString", + "defaultValue": "", + "cached": false + }, + { + "id": "9c284ede-eea8-4a9b-a326-e59a6bc7bb7c", + "name": "web", + "displayName": "Web", + "type": "QString", + "defaultValue": "", + "cached": false + }, + { + "id": "786e7be7-917a-4062-83ff-aade80686ec5", + "name": "clients", + "displayName": "Clients", + "type": "uint", + "defaultValue": 0, + "cached": false + } + ], + "eventTypes": [ + { + "id": "0508a1e2-4ed2-42ee-ab70-ed7cdd1e261c", + "name": "clientConnected", + "displayName": "Client connected", + "suggestLogging": true, + "paramTypes": [ + { + "id": "a334c6e7-dffc-4720-aa21-815636be1bc1", + "name": "clientAddress", + "displayName": "Client address", + "type": "QString" + } + ] + }, + { + "id": "2871e481-1b67-4d77-b1ce-e0965784aa89", + "name": "clientDisconnected", + "displayName": "Client disconnected", + "suggestLogging": true, + "paramTypes": [ + { + "id": "0ad4ed71-4b9a-44ab-83b1-5c62482e1625", + "name": "clientAddress", + "displayName": "Client address", + "type": "QString" + } + ] + } + ] + } + ] + } + ] +} + + + diff --git a/tmate/logo.png b/tmate/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0872a0dc268c9974e0b195606b68e514cb467792 GIT binary patch literal 2998 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 literal 0 HcmV?d00001 diff --git a/tmate/meta.json b/tmate/meta.json new file mode 100644 index 00000000..3089444d --- /dev/null +++ b/tmate/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Tmate", + "tagline": "Shell access to the machine running nymea from anywhere via tmate.", + "icon": "logo.png", + "stability": "community", + "offline": false, + "technologies": [ + "cloud" + ], + "categories": [ + "tool" + ] +} diff --git a/tmate/tmate.pro b/tmate/tmate.pro new file mode 100644 index 00000000..b53656b6 --- /dev/null +++ b/tmate/tmate.pro @@ -0,0 +1,8 @@ +include(../plugins.pri) + +SOURCES += \ + integrationplugintmate.cpp \ + +HEADERS += \ + integrationplugintmate.h \ + diff --git a/tmate/translations/d06ab0d1-dbfe-48af-b196-523cc37a1e5e-en_US.ts b/tmate/translations/d06ab0d1-dbfe-48af-b196-523cc37a1e5e-en_US.ts new file mode 100644 index 00000000..e7936aff --- /dev/null +++ b/tmate/translations/d06ab0d1-dbfe-48af-b196-523cc37a1e5e-en_US.ts @@ -0,0 +1,103 @@ + + + + + Tmate + + + API key (optional) + The name of the ParamType (ThingClass: tmate, Type: thing, ID: {01f0c818-55e1-4842-a9b9-cc58bbfe76c6}) + + + + + + Active + The name of the ParamType (ThingClass: tmate, ActionType: active, ID: {7009c176-e1aa-49bc-818c-63f7a9027306}) +---------- +The name of the StateType ({7009c176-e1aa-49bc-818c-63f7a9027306}) of ThingClass tmate + + + + + + Client address + The name of the ParamType (ThingClass: tmate, EventType: clientDisconnected, ID: {0ad4ed71-4b9a-44ab-83b1-5c62482e1625}) +---------- +The name of the ParamType (ThingClass: tmate, EventType: clientConnected, ID: {a334c6e7-dffc-4720-aa21-815636be1bc1}) + + + + + Client connected + The name of the EventType ({0508a1e2-4ed2-42ee-ab70-ed7cdd1e261c}) of ThingClass tmate + + + + + Client disconnected + The name of the EventType ({2871e481-1b67-4d77-b1ce-e0965784aa89}) of ThingClass tmate + + + + + Clients + The name of the StateType ({786e7be7-917a-4062-83ff-aade80686ec5}) of ThingClass tmate + + + + + Connected + The name of the StateType ({beac3113-04f1-4d70-875b-44ca8b307866}) of ThingClass tmate + + + + + SSH + The name of the StateType ({e8ff1b90-7701-454c-a557-4b91dc8c649b}) of ThingClass tmate + + + + + SSH RO + The name of the StateType ({ef780fb1-b31a-4333-944b-a02bf3297fea}) of ThingClass tmate + + + + + Session name (requires API key usage) + The name of the ParamType (ThingClass: tmate, Type: thing, ID: {e587e3dc-0beb-441f-8f07-b23c25580b10}) + + + + + Set active + The name of the ActionType ({7009c176-e1aa-49bc-818c-63f7a9027306}) of ThingClass tmate + + + + + + + Tmate + The name of the ThingClass ({3f06ad52-9514-41b1-9bf9-031241d34634}) +---------- +The name of the vendor ({b948d5e2-bfc6-4e28-a2ba-e40e46f4c213}) +---------- +The name of the plugin Tmate ({d06ab0d1-dbfe-48af-b196-523cc37a1e5e}) + + + + + Web + The name of the StateType ({9c284ede-eea8-4a9b-a326-e59a6bc7bb7c}) of ThingClass tmate + + + + + Web RO + The name of the StateType ({98248bc0-ddda-4ae6-8558-4d7155a39c33}) of ThingClass tmate + + + +