diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c09c90321453feadd9808e5b8de4ef227409e0d3..14f36c93e438ae78dd11fd8f1bfc1973516c3e64 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -48,7 +48,7 @@ linux-amd64-debian-buster-dbg:
     image: demlabs/amd64/debian-buster:linuxbuilder
     before_script: /opt/buildtools/prepare_environment.sh amd64-linux
     script:
-      - ./prod_build/build.sh --target linux debug -DBUILD_WITH_PYTHON_ENV=ON
+      - ./prod_build/build.sh --target linux debug -DBUILD_WITH_PYTHON_ENV=ON -DBUILD_DIAGTOOL=ON
       - ./prod_build/pack.sh --target linux debug
 
 linux-amd64-debian-buster-rwd:
@@ -56,7 +56,7 @@ linux-amd64-debian-buster-rwd:
     image: demlabs/amd64/debian-buster:linuxbuilder
     before_script: /opt/buildtools/prepare_environment.sh amd64-linux
     script:
-      - ./prod_build/build.sh --target linux rwd -DBUILD_WITH_PYTHON_ENV=ON
+      - ./prod_build/build.sh --target linux rwd -DBUILD_WITH_PYTHON_ENV=ON -DBUILD_DIAGTOOL=ON
       - ./prod_build/pack.sh --target linux rwd
 
 linux-amd64-debian-buster:
@@ -64,7 +64,7 @@ linux-amd64-debian-buster:
     image: demlabs/amd64/debian-buster:linuxbuilder
     before_script: /opt/buildtools/prepare_environment.sh amd64-linux
     script: 
-      - ./prod_build/build.sh --target linux release -DBUILD_WITH_PYTHON_ENV=ON
+      - ./prod_build/build.sh --target linux release -DBUILD_WITH_PYTHON_ENV=ON -DBUILD_DIAGTOOL=ON
       - ./prod_build/pack.sh --target linux release
 
 linux-armhf-debian-bullseye:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5a2213d0fb85db4c2a953f599de52dfdefbc1e7f..2a1d256bb62ae82564e3a513e1b62d682b018b2c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,6 +16,9 @@ SET( CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
 SET( CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
 SET( CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
 
+#enable for diagtool 
+#set(BUILD_DIAGTOOL OFF)
+
 SET(CMAKE_INSTALL_PREFIX "/opt/${PROJECT_NAME}")
 SET(CPACK_INSTALL_PREFIX "/opt/${PROJECT_NAME}")
 SET(DESTDIR "/opt/${PROJECT_NAME}")
@@ -223,6 +226,11 @@ if(UNIX AND NOT WIN32)
     set(NODE_CLI_LIBRARIES m cellframe-sdk)
 
     set(NODE_TOOL_LIBRARIES m cellframe-sdk)
+    
+    if(BUILD_DIAGTOOL)
+        message("[*] Diagtool build on")
+        add_subdirectory(diagtool)
+    endif()
 
     if (SUPPORT_PYTHON_PLUGINS)
         message("[+] Build with python plugins support")
@@ -307,6 +315,7 @@ if(DARWIN)
     INSTALL(TARGETS ${NODE_CLI_TARGET} DESTINATION ${BINDIR} )
     INSTALL(TARGETS ${NODE_TOOL_TARGET} DESTINATION ${BINDIR} )
 
+    INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/os/macos/com.demlabs.cellframe-node.plist DESTINATION ${SHAREDIR} )
     INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/ DESTINATION ${SHAREDIR} FILES_MATCHING PATTERN "*"  PATTERN "*" PERMISSIONS OWNER_EXECUTE;OWNER_READ;OWNER_WRITE;WORLD_READ;GROUP_READ )
     INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts.darwin/ DESTINATION ${SHAREDIR} FILES_MATCHING PATTERN "*"  PATTERN "*" PERMISSIONS OWNER_EXECUTE;OWNER_READ;OWNER_WRITE;WORLD_READ;GROUP_READ )
 else()
@@ -322,6 +331,10 @@ else()
     INSTALL(TARGETS ${NODE_CLI_TARGET} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin )
     INSTALL(TARGETS ${NODE_TOOL_TARGET} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin )
 
+    if (BUILD_DIAGTOOL)
+        INSTALL(TARGETS cellframe-diagtool DESTINATION ${CMAKE_INSTALL_PREFIX}/bin )
+    endif()
+
     if(NOT ANDROID)
 	    #install all python-specific files back to its original location
         if (SUPPORT_PYTHON_PLUGINS AND BUILD_WITH_PYTHON_ENV)
diff --git a/diagtool/AbstractDiagnostic.cpp b/diagtool/AbstractDiagnostic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b175b29ed3ff856dafce1d9d7b8a48bb0e6fa8b1
--- /dev/null
+++ b/diagtool/AbstractDiagnostic.cpp
@@ -0,0 +1,150 @@
+#include "AbstractDiagnostic.h"
+
+AbstractDiagnostic::AbstractDiagnostic(QObject *parent)
+    :QObject(parent)
+{
+    s_timer_update = new QTimer();
+    s_mac = get_mac();
+}
+
+AbstractDiagnostic::~AbstractDiagnostic()
+{
+    delete s_timer_update;
+}
+
+void AbstractDiagnostic::set_timeout(int timeout){
+    s_timer_update->stop();
+    s_timeout = timeout;
+    s_timer_update->start(s_timeout);
+}
+
+void AbstractDiagnostic::start_diagnostic()
+{
+    s_timer_update->start(s_timeout);
+}
+
+void AbstractDiagnostic::stop_diagnostic()
+{
+    s_timer_update->stop();
+}
+
+QJsonValue AbstractDiagnostic::get_mac()
+{
+    QString MAC{"unknown"};
+    foreach(QNetworkInterface netInterface, QNetworkInterface::allInterfaces())
+    {
+        // Return only the first non-loopback MAC Address
+        if (!(netInterface.flags() & QNetworkInterface::IsLoopBack))
+        {
+            qDebug()<<netInterface.hardwareAddress();
+            if(!netInterface.hardwareAddress().isEmpty())
+            {
+                MAC = netInterface.hardwareAddress();
+                break;
+            }
+        }
+    }
+
+    return MAC;
+}
+
+QString AbstractDiagnostic::get_uptime_string(long sec)
+{
+    QTime time(0, 0);
+    time = time.addSecs(sec);
+    int fullHours = sec/3600;
+
+    QString uptime = QString("%1:").arg(fullHours) + time.toString("mm:ss");
+
+    return uptime;
+}
+
+quint64 AbstractDiagnostic::get_file_size (QString flag, QString path ) {
+
+    if(flag == "log")
+        path += "/var/log";
+    else
+    if (flag == "DB")
+        path += "/var/lib/global_db";
+    else
+    if (flag == "chain")
+        path += "/var/lib/network";
+    else
+        path += "";
+
+    QDir currentFolder( path );
+
+    quint64 totalsize = 0;
+
+    currentFolder.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks );
+    currentFolder.setSorting( QDir::Name );
+
+    QFileInfoList folderitems( currentFolder.entryInfoList() );
+
+    foreach ( QFileInfo i, folderitems ) {
+        QString iname( i.fileName() );
+        if ( iname == "." || iname == ".." || iname.isEmpty() )
+            continue;
+        if(flag == "log" && i.suffix() != "log" && !i.isDir())
+            continue;
+        else
+        if(flag == "DB" && (i.suffix() != "dat" && !i.suffix().isEmpty()) && !i.isDir())
+            continue;
+        else
+        if(flag == "chain" && i.suffix() != "dchaincell" && !i.isDir())
+            continue;
+
+        if ( i.isDir() )
+            totalsize += get_file_size("", path+"/"+iname);
+        else
+            totalsize += i.size();
+    }
+
+    return totalsize;
+}
+
+QString AbstractDiagnostic::get_memory_string(long num)
+{
+    QString result = QString::number(num);
+    return result;
+
+    int gb = (num / 1024) / 1024;
+    int mb = (num-gb*1024*1024) /1024;
+    int kb = (num - (gb*1024*1024+mb*1024));
+    if (gb > 0)
+       result = QString::number(gb) + QString(" Gb ");
+    else
+       result = QString("");
+    if (mb > 0)
+       result += QString::number(mb) + QString(" Mb ");
+    if (kb > 0)
+       result += QString::number(kb) + QString(" Kb ");
+
+    return result;
+}
+
+QJsonObject AbstractDiagnostic::roles_processing()
+{
+    QJsonObject rolesObject;
+
+    QDir currentFolder("/opt/cellframe-node/etc/network");
+
+    currentFolder.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks );
+    currentFolder.setSorting( QDir::Name );
+
+    QFileInfoList folderitems( currentFolder.entryInfoList() );
+
+    foreach ( QFileInfo i, folderitems ) {
+        QString iname( i.fileName() );
+        if ( iname == "." || iname == ".." || iname.isEmpty() )
+            continue;
+        if(i.suffix() == "cfg" && !i.isDir())
+        {
+            QSettings config(i.absoluteFilePath(), QSettings::IniFormat);
+
+            rolesObject.insert(i.completeBaseName(), config.value("node-role", "unknown").toString());
+        }
+    }
+
+    return rolesObject;
+}
diff --git a/diagtool/AbstractDiagnostic.h b/diagtool/AbstractDiagnostic.h
new file mode 100644
index 0000000000000000000000000000000000000000..b02714602357c97629fd4a3cf71588d3c5c8e407
--- /dev/null
+++ b/diagtool/AbstractDiagnostic.h
@@ -0,0 +1,52 @@
+#ifndef ABSTRACTDIAGNOSTIC_H
+#define ABSTRACTDIAGNOSTIC_H
+
+#include <QObject>
+#include "qtimer.h"
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QDebug>
+#include <QUrl>
+#include <QFileInfo>
+#include <QCoreApplication>
+#include <QProcess>
+#include <QString>
+#include <QRegularExpression>
+#include <QSettings>
+
+#include <QDir>
+#include <QNetworkInterface>
+
+class AbstractDiagnostic : public QObject
+{
+    Q_OBJECT
+public:
+    explicit AbstractDiagnostic(QObject * parent = nullptr);
+    ~AbstractDiagnostic();
+
+public:
+    void start_diagnostic();
+    void stop_diagnostic();
+    void set_timeout(int timeout);
+    quint64 get_file_size(QString flag, QString path);
+    QString get_uptime_string(long sec);
+    QString get_memory_string(long num);
+    QJsonValue get_mac();
+
+    QJsonDocument get_full_info(){return s_full_info;};
+
+    QJsonObject roles_processing();
+
+public:
+    QTimer * s_timer_update;
+    int s_timeout{1000};
+    QJsonDocument s_full_info;
+    QJsonValue s_mac;
+
+signals:
+    void data_updated(QJsonDocument);
+
+};
+
+#endif // ABSTRACTDIAGNOSTIC_H
diff --git a/diagtool/CMakeLists.txt b/diagtool/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..76aea61926b58360c6856d9b1ddf10d8220acb9e
--- /dev/null
+++ b/diagtool/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(cellframe-diagtool)
+
+find_package(Qt5 5.15 REQUIRED COMPONENTS
+    Core
+    Network
+)
+
+set(CMAKE_AUTOMOC ON)
+
+add_executable(${PROJECT_NAME}
+    main.cpp
+    DiagnosticWorker.cpp
+    AbstractDiagnostic.cpp
+    LinuxDiagnostic.cpp
+)
+
+target_link_libraries(${PROJECT_NAME}
+    Qt5::Core Qt5::Network
+)
+
diff --git a/diagtool/DiagnosticWorker.cpp b/diagtool/DiagnosticWorker.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba1f9a385a6d33d4bd70d54abf319d0baa3ee84e
--- /dev/null
+++ b/diagtool/DiagnosticWorker.cpp
@@ -0,0 +1,88 @@
+#include "DiagnosticWorker.h"
+
+static QString group = "global.users.statistic";
+
+DiagnosticWorker::DiagnosticWorker(QObject * parent)
+    : QObject{parent}
+{
+    s_uptime_timer = new QTimer(this);
+    connect(s_uptime_timer, &QTimer::timeout,
+            this, &DiagnosticWorker::slot_uptime,
+            Qt::QueuedConnection);
+    s_uptime_timer->start(1000);
+
+    s_elapsed_timer = new QElapsedTimer();
+    s_elapsed_timer->start();
+
+    m_diagnostic = new LinuxDiagnostic();
+
+    connect(m_diagnostic, &AbstractDiagnostic::data_updated,
+            this, &DiagnosticWorker::slot_diagnostic_data,
+            Qt::QueuedConnection);
+
+    m_diagnostic->start_diagnostic();
+    m_diagnostic->set_timeout(30000);
+}
+DiagnosticWorker::~DiagnosticWorker()
+{
+    delete s_uptime_timer;
+    delete s_elapsed_timer;
+    delete m_diagnostic;
+}
+
+void DiagnosticWorker::slot_diagnostic_data(QJsonDocument data)
+{
+    //insert uptime dashboard and more into system info
+    QJsonObject obj = data.object();
+    QJsonObject system = data["system"].toObject();
+    QJsonObject proc = data["process"].toObject();
+
+    system.insert("uptime_dashboard", s_uptime);
+    system.insert("time_update_unix", QDateTime::currentSecsSinceEpoch());
+
+//    system.insert("time_update", QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm"));
+    obj.insert("system",system);
+
+    if(proc["status"].toString() == "Offline") //if node offline - clear version
+        m_node_version = "";
+    else
+    if(m_node_version.isEmpty())
+    {
+        QProcess proc;
+        proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"), QStringList()<<"version");
+        proc.waitForFinished(5000);
+        QString result = proc.readAll();
+        if(result.contains("version"))
+        {
+            result = result.split("version")[1];
+            m_node_version = result.split('\n', QString::SkipEmptyParts).first().trimmed();
+        }
+    }
+
+    proc.insert("version", m_node_version);
+    obj.insert("process",proc);
+    data.setObject(obj);
+
+    write_data(data);
+}
+
+void DiagnosticWorker::slot_uptime()
+{
+    s_uptime = m_diagnostic->get_uptime_string(s_elapsed_timer->elapsed()/1000);
+}
+
+void DiagnosticWorker::write_data(QJsonDocument data)
+{
+    QString key = m_diagnostic->s_mac.toString();
+
+    QProcess proc;
+    QString program = "/opt/cellframe-node/bin/cellframe-node-cli";
+    QStringList arguments;
+    arguments << "global_db" << "write" << "-group" << QString(group)
+              << "-key" << QString(key) << "-value" << QByteArray(data.toJson());
+    proc.start(program, arguments);
+    proc.waitForFinished(5000);
+    QString res = proc.readAll();
+
+    qDebug()<<res;
+}
diff --git a/diagtool/DiagnosticWorker.h b/diagtool/DiagnosticWorker.h
new file mode 100644
index 0000000000000000000000000000000000000000..01341f29c0a589d448adb3fffa35535f7cb563ec
--- /dev/null
+++ b/diagtool/DiagnosticWorker.h
@@ -0,0 +1,41 @@
+#ifndef DIAGNOSTICWORKER_H
+#define DIAGNOSTICWORKER_H
+
+#include <QObject>
+#include <QTimer>
+#include <QElapsedTimer>
+#include <QSettings>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "LinuxDiagnostic.h"
+
+
+class DiagnosticWorker : public QObject
+{
+    Q_OBJECT
+public:
+    explicit DiagnosticWorker(QObject *parent = nullptr);
+    ~DiagnosticWorker();
+
+private:
+    QString m_node_version{""};
+    QSettings m_settings;
+    AbstractDiagnostic* m_diagnostic;
+
+    QTimer *s_uptime_timer;
+    QElapsedTimer *s_elapsed_timer;
+    QString s_uptime{"00:00:00"};
+
+private slots:
+    void slot_diagnostic_data(QJsonDocument);
+    void slot_uptime();
+
+private:
+    void write_data(QJsonDocument);
+
+};
+
+#endif // DIAGNOSTICWORKER_H
diff --git a/diagtool/LinuxDiagnostic.cpp b/diagtool/LinuxDiagnostic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..40528801d64be54f05ca85b1f5352c4e7e5a8cb3
--- /dev/null
+++ b/diagtool/LinuxDiagnostic.cpp
@@ -0,0 +1,509 @@
+#include "LinuxDiagnostic.h"
+#include <sys/vfs.h>
+#include <sys/stat.h>
+
+LinuxDiagnostic::LinuxDiagnostic(AbstractDiagnostic *parent)
+    : AbstractDiagnostic{parent}
+{
+    connect(s_timer_update, &QTimer::timeout,
+            this, &LinuxDiagnostic::info_update,
+            Qt::QueuedConnection);
+}
+
+
+void LinuxDiagnostic::info_update(){
+
+    // QSettings config("/opt/cellframe-node/etc/cellframe-node.cfg",
+    //                    QSettings::IniFormat);
+
+    // bool isEnabled = config.value("Diagnostic/enabled",false).toBool();
+
+    if(true) //isEnabled
+    {
+        QJsonObject proc_info;
+        QJsonObject sys_info;
+        QJsonObject cli_info;
+        QJsonObject full_info;
+
+        sys_info = get_sys_info();
+        sys_info.insert("mac", s_mac);
+        sys_info.insert("disk", get_disk_info());
+
+        QFile file("/opt/cellframe-node/etc/diagdata.json");
+        if(file.open(QIODevice::ReadOnly | QIODevice::Text))
+        {
+            QJsonParseError err;
+            QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err);
+            file.close();
+
+            if(err.error == QJsonParseError::NoError && !doc.isEmpty())
+                sys_info.insert("data", doc.object());
+            else
+                qWarning()<<err.errorString();
+        }
+
+        QJsonObject obj = sys_info["memory"].toObject();
+        int mem = obj["total"].toInt();
+
+        proc_info = get_process_info(get_pid(), mem);
+        proc_info.insert("roles", roles_processing());
+
+        if(s_node_status)
+        {
+            cli_info = get_cli_info();
+            full_info.insert("cli_data", cli_info);
+        }
+
+
+        full_info.insert("system", sys_info);
+        full_info.insert("process", proc_info);
+
+        s_full_info.setObject(full_info);
+
+        emit data_updated(s_full_info);
+    }
+}
+
+QJsonObject LinuxDiagnostic::get_disk_info()
+{
+    QString path = "/opt/cellframe-node";
+
+    QString apath = QDir(path).absolutePath();
+
+    struct statfs stfs;
+
+    if (::statfs(apath.toLocal8Bit(),&stfs) == -1)
+    {
+        QJsonObject diskObj;
+        diskObj.insert("total",     -1);
+        diskObj.insert("free",      -1);
+        diskObj.insert("available", -1);
+        diskObj.insert("used",      -1);
+
+        return diskObj;
+    }
+
+    quint64 total     = stfs.f_blocks * stfs.f_bsize;
+    quint64 free      = stfs.f_bfree  * stfs.f_bsize;
+    quint64 available = stfs.f_bavail * stfs.f_bsize;
+    quint64 used      = total - free;
+
+    QJsonObject diskObj;
+    diskObj.insert("total",     QString::number(total));
+    diskObj.insert("free",      QString::number(free));
+    diskObj.insert("available", QString::number(available));
+    diskObj.insert("used",      QString::number(used));
+
+    return diskObj;
+}
+
+
+long LinuxDiagnostic::get_pid()
+{
+    long pid;
+    QString path = QString("%1/var/run/cellframe-node.pid").arg("/opt/cellframe-node/");
+
+    QFile file(path);
+    QByteArray data;
+    if (!file.open(QIODevice::ReadOnly))
+        return 0;
+    data = file.readAll();
+    pid = data.toLong();
+
+    return pid;
+}
+
+/// ---------------------------------------------------------------
+///        Sys info
+/// ---------------------------------------------------------------
+QJsonObject LinuxDiagnostic::get_sys_info()
+{
+    QJsonObject obj_sys_data, obj_cpu, obj_memory;
+
+    ifstream stat_stream;
+    string buff;
+
+    //-------
+
+    //get cpu info
+    size_t idle_time, total_time;
+    get_cpu_times(idle_time, total_time);
+
+    const float idle_time_delta = idle_time - previous_idle_time;
+    const float total_time_delta = total_time - previous_total_time;
+    const float utilization = 100.0 * (1.0 - idle_time_delta / total_time_delta);
+
+    previous_idle_time = idle_time;
+    previous_total_time = total_time;
+
+    stat_stream.open("/proc/cpuinfo");
+    for(int i = 0; i < 16;i++) stat_stream >> buff;
+    getline(stat_stream,buff);
+
+    obj_cpu.insert("load", (int)utilization);
+    obj_cpu.insert("model", QString::fromStdString(buff));
+    stat_stream.close();
+
+    //get uptime system
+    stat_stream.open("/proc/uptime");
+    stat_stream >> buff;
+    QString uptime = get_uptime_string(atoi(buff.c_str()));
+    stat_stream.close();
+
+    //get memory data
+    stat_stream.open("/proc/meminfo");
+    QString memory, memory_used, memory_free;
+    string total,free,available;
+    stat_stream >> buff >> total >> buff >> buff >> free >> buff >> buff >> available;
+    stat_stream.close();
+
+    int total_value = atoi(total.c_str());
+    int available_value = atoi(available.c_str());
+
+    memory = get_memory_string(total_value);
+    memory_used = QString::number((total_value - available_value) *100 / total_value);
+    memory_free = get_memory_string(available_value);
+
+//    obj_memory.insert("total", memory);
+    obj_memory.insert("total", total_value);
+//    obj_memory.insert("total_value", total_value);
+    obj_memory.insert("free", memory_free);
+    obj_memory.insert("load", memory_used);
+
+    //-------
+
+    obj_sys_data.insert("uptime", uptime);
+    obj_sys_data.insert("CPU", obj_cpu);
+    obj_sys_data.insert("memory", obj_memory);
+
+    return obj_sys_data;
+}
+
+QString LinuxDiagnostic::get_running(char* pid)
+{
+    char tbuf[32];
+    char *cp;
+    int fd;
+    char c;
+
+    sprintf(tbuf, "/proc/%s/stat", pid);
+    fd = open(tbuf, O_RDONLY, 0);
+    if (fd == -1) return QString("no open");
+
+    memset(tbuf, '\0', sizeof tbuf); // didn't feel like checking read()
+    read(fd, tbuf, sizeof tbuf - 1);
+
+    cp = strrchr(tbuf, ')');
+    if(!cp) return QString("no read");
+
+    c = cp[2];
+    close(fd);
+    qDebug()<<c;
+
+    if (c=='R') {
+      return "running";
+    }else
+    if (c=='D') {
+      return "blocked";
+    }
+    return QString("blocked");
+}
+
+
+QString LinuxDiagnostic::get_proc_path(long pid) // work with root
+{
+    char exePath[PATH_MAX];
+    char arg1[20];
+    sprintf( arg1, "/proc/%ld/exe", pid );
+    ssize_t len = ::readlink(arg1, exePath, sizeof(exePath));
+    if (len == -1 || len == sizeof(exePath))
+        len = 0;
+    exePath[len] = '\0';
+    return QString::fromUtf8(exePath);
+}
+
+std::vector<size_t> LinuxDiagnostic::get_cpu_times() {
+    std::ifstream proc_stat("/proc/stat");
+    proc_stat.ignore(5, ' '); // Skip the 'cpu' prefix.
+    std::vector<size_t> times;
+    for (size_t time; proc_stat >> time; times.push_back(time));
+    return times;
+}
+
+bool LinuxDiagnostic::get_cpu_times(size_t &idle_time, size_t &total_time) {
+    const std::vector<size_t> cpu_times = get_cpu_times();
+    if (cpu_times.size() < 4)
+        return false;
+    idle_time = cpu_times[3];
+    total_time = std::accumulate(cpu_times.begin(), cpu_times.end(), 0);
+    return true;
+}
+
+
+/// ---------------------------------------------------------------
+///        Process info
+/// ---------------------------------------------------------------
+QJsonObject LinuxDiagnostic::get_process_info(long proc_id, int totalRam)
+{
+   using std::ios_base;
+   using std::ifstream;
+   using std::string;
+
+//   vm_usage     = 0.0;
+//   resident_set = 0.0;
+
+   // 'file' stat seems to give the most reliable results
+   char arg1[20];
+   sprintf( arg1, "/proc/%ld/stat", proc_id );
+   ifstream stat_stream(arg1,ios_base::in);
+
+   // dummy vars for leading entries in stat that we don't care about
+   //
+   string pid, comm, state, ppid, pgrp, session, tty_nr;
+   string tpgid, flags, minflt, cminflt, majflt, cmajflt;
+   string utime, stime, cutime, cstime, priority, nice;
+   string O, itrealvalue;
+
+   // the two fields we want
+   //
+   unsigned long vsize;
+   long rss;
+   double starttime;
+
+   stat_stream >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr
+               >> tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt
+               >> utime >> stime >> cutime >> cstime >> priority >> nice
+               >> O >> itrealvalue >> starttime >> vsize >> rss; // don't care about the rest
+
+   stat_stream.close();
+
+   long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages
+//   vm_usage     = (vsize/1024.0);
+   long resident_set = rss * page_size_kb;
+
+   int precentUseRss = (resident_set * 100) / totalRam;
+
+   QProcess proc;
+   QString program = "ps";
+   QStringList arguments;
+   arguments << "-p" << pid.c_str() << "-o" << "etimes";
+   proc.start(program, arguments);
+   proc.waitForFinished(1000);
+   QString res = proc.readAll();
+
+   static QRegularExpression rx(R"([0-9]+)");
+
+   QJsonObject process_info;
+   QString status = "Offline";
+   process_info.insert("status", "Offline");
+
+   QString node_dir = "/opt/cellframe-node/";
+
+   QString log_size, db_size, chain_size;
+   log_size = get_memory_string(get_file_size("log", node_dir) / 1024);
+   db_size = get_memory_string(get_file_size("DB", node_dir) / 1024);
+   chain_size = get_memory_string(get_file_size("chain", node_dir) / 1024);
+
+   if(log_size.isEmpty()) log_size = "0";
+   if(db_size.isEmpty()) db_size = "0";
+   if(chain_size.isEmpty()) chain_size = "0";
+
+   process_info.insert("log_size", log_size);
+   process_info.insert("DB_size", db_size);
+   process_info.insert("chain_size", chain_size);
+
+   if(QString::fromLocal8Bit(comm.c_str()).contains("cellframe-node"))
+   {
+       int uptime_sec = rx.match(res).captured(0).toInt();
+
+       QString uptime= get_uptime_string(uptime_sec);
+
+       QString memory_use_value = get_memory_string(resident_set);
+
+//       process_info.insert("PPID",QString::fromLocal8Bit(ppid.c_str()));
+//       process_info.insert("priory",QString::fromLocal8Bit(priority.c_str()));
+//       process_info.insert("start_time",QString::number(((starttime/100)/60)));
+       process_info.insert("memory_use",precentUseRss);
+       process_info.insert("memory_use_value",memory_use_value);
+       process_info.insert("uptime",uptime);
+       process_info.insert("name","cellframe-node");
+
+       status = "Online";
+   }
+
+   if(status == "Offline")
+   {
+       process_info.insert("memory_use","0");
+       process_info.insert("memory_use_value","0 Kb");
+       process_info.insert("uptime","00:00:00");
+   }
+
+   s_node_status = status == "Online" ? true : false;
+
+   process_info.insert("status", status);
+
+   return process_info;
+}
+
+/// ---------------------------------------------------------------
+///        Cli info
+/// ---------------------------------------------------------------
+QJsonObject LinuxDiagnostic::get_cli_info()
+{
+    QStringList networks = get_networks();
+
+    QJsonObject netObj;
+
+    for(QString net : networks)
+    {
+        QJsonObject dataObj;
+
+        dataObj.insert("net_info", get_net_info(net));
+        dataObj.insert("mempool" , get_mempool_count(net));
+//        dataObj.insert("ledger"  , get_ledger_count(net));
+        dataObj.insert("blocks"  , get_blocks_count(net));
+        dataObj.insert("events"  , get_events_count(net));
+
+        netObj.insert(net, dataObj);
+    }
+
+    return netObj;
+}
+
+QStringList LinuxDiagnostic::get_networks()
+{
+    QProcess proc;
+    proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"), QStringList()<<"net"<<"list");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QStringList listNetworks;
+    result.remove(' ');
+    result.remove("\r");
+    result.remove("\n");
+    result.remove("Networks:");
+    if(!(result.isEmpty() || result.isNull() || result.contains('\'') || result.contains("error") || result.contains("Error") || result.contains("err")))
+    {
+        listNetworks = result.split("\t", QString::SkipEmptyParts);
+    }
+
+    return listNetworks;
+}
+
+QJsonObject LinuxDiagnostic::get_net_info(QString net)
+{
+    QProcess proc;
+    proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"),
+               QStringList()<<"net"<<"-net"<<QString(net)<<"get"<<"status");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    // ---------- State & TargetState ----------------
+
+    QRegularExpression rx(R"***(^Network "(\S+)" has state (\S+) \(target state (\S*)\), .*cur node address ([A-F0-9]{4}::[A-F0-9]{4}::[A-F0-9]{4}::[A-F0-9]{4}))***");
+    QRegularExpressionMatch match = rx.match(result);
+    if (!match.hasMatch()) {
+        return {};
+    }
+
+    QJsonObject resultObj({
+                                {"state"              , match.captured(2)},
+                                {"target_state"       , match.captured(3)},
+                                {"node_address"       , match.captured(4)}
+                            });
+
+    // ---------- Links count ----------------
+    QRegularExpression rxLinks(R"(\), active links (\d+) from (\d+),)");
+    match = rxLinks.match(result);
+    if (!match.hasMatch()) {
+        return resultObj;
+    }
+
+    resultObj.insert("active_links_count", match.captured(1));
+    resultObj.insert("links_count"       , match.captured(2));
+
+    return resultObj;
+}
+
+QJsonObject LinuxDiagnostic::get_mempool_count(QString net)
+{
+    QProcess proc;
+    proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"),
+               QStringList()<<"mempool_list"<<"-net"<<QString(net));
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QRegularExpression rx(R"(\.(.+): Total (.+) records)");
+
+    ///TODO: bug in requests. Always returns both chains
+//    QRegularExpressionMatch match = rx.match(result);
+//    if (!match.hasMatch()) {
+//        return {};
+//    }
+
+//    proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"),
+//               QStringList()<<"mempool_list"<<"-net"<<QString(net)<<"-chain"<<"zero");
+//    proc.waitForFinished(5000);
+//    result = proc.readAll();
+
+    QJsonObject resultObj;
+
+    QRegularExpressionMatchIterator matchItr = rx.globalMatch(result);
+
+    while (matchItr.hasNext())
+    {
+        QRegularExpressionMatch match = matchItr.next();
+        resultObj.insert(match.captured(1), match.captured(2));
+    }
+
+    return resultObj;
+}
+
+QJsonObject LinuxDiagnostic::get_ledger_count(QString net)
+{
+    //TODO: legder tx -all -net   NOT WORKING
+    return {};
+}
+
+QJsonObject LinuxDiagnostic::get_blocks_count(QString net)
+{
+    QProcess proc;
+    proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"),
+               QStringList()<<"block"<<"list"<<"-net"<<QString(net) <<"-chain" << "main");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QRegularExpression rx(R"(\.(.+): Have (.+) blocks)");
+    QRegularExpressionMatch match = rx.match(result);
+    if (!match.hasMatch()) {
+        return {};
+    }
+
+    QJsonObject resultObj;
+    resultObj.insert(match.captured(1), match.captured(2));
+
+    return resultObj;
+
+}
+
+QJsonObject LinuxDiagnostic::get_events_count(QString net)
+{
+    QProcess proc;
+    proc.start(QString("/opt/cellframe-node/bin/cellframe-node-cli"),
+               QStringList()<<"dag"<<"event"<<"list"<<"-net"<<QString(net) <<"-chain" << "zerochain");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QRegularExpression rx(R"(\.(.+) have total (.+) events)");
+    QRegularExpressionMatch match = rx.match(result);
+    if (!match.hasMatch()) {
+        return {};
+    }
+
+    QJsonObject resultObj;
+    resultObj.insert(match.captured(1), match.captured(2));
+
+    return resultObj;
+}
+
+
diff --git a/diagtool/LinuxDiagnostic.h b/diagtool/LinuxDiagnostic.h
new file mode 100644
index 0000000000000000000000000000000000000000..4258138a0ee21329901afb5330926648a153a6c7
--- /dev/null
+++ b/diagtool/LinuxDiagnostic.h
@@ -0,0 +1,63 @@
+#ifndef LINUXDIAGNOSTIC_H
+#define LINUXDIAGNOSTIC_H
+
+
+#include "AbstractDiagnostic.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <vector>
+
+#include <fstream>
+#include <numeric>
+#include <vector>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <locale.h>
+#include <signal.h>
+#include <dirent.h>
+
+#include <sys/sysinfo.h>
+
+using namespace std;
+
+
+class LinuxDiagnostic : public AbstractDiagnostic
+{
+    Q_OBJECT
+public:
+    explicit LinuxDiagnostic(AbstractDiagnostic * parent = nullptr);
+
+private:
+    QJsonObject get_sys_info();
+    bool get_cpu_times(size_t &idle_time, size_t &total_time);
+    std::vector<size_t> get_cpu_times();
+    long get_pid();
+    QString get_running(char* pid);
+    QString get_proc_path(long pid);
+    QJsonObject get_process_info(long pid, int totalRam);
+    QJsonObject get_disk_info();
+    QJsonObject get_cli_info();
+
+
+    //CLI
+
+    QStringList get_networks();
+    QJsonObject get_net_info(QString net);
+    QJsonObject get_mempool_count(QString net);
+    QJsonObject get_ledger_count(QString net);
+    QJsonObject get_blocks_count(QString net);
+    QJsonObject get_events_count(QString net);
+
+private slots:
+    void info_update();
+
+private:
+    size_t previous_idle_time{0}, previous_total_time{0};
+    bool s_node_status{false};
+
+};
+
+#endif // LINUXDIAGNOSTIC_H
diff --git a/diagtool/NodeDiagnostic.pro b/diagtool/NodeDiagnostic.pro
new file mode 100644
index 0000000000000000000000000000000000000000..828b78ba9bb68f21463c9b012dd5a58b5ea43644
--- /dev/null
+++ b/diagtool/NodeDiagnostic.pro
@@ -0,0 +1,25 @@
+QT -= gui
+QT += core network
+
+CONFIG += c++17 console
+CONFIG -= app_bundle
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+        AbstractDiagnostic.cpp \
+        DiagnosticWorker.cpp \
+        LinuxDiagnostic.cpp \
+        main.cpp
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+HEADERS += \
+    AbstractDiagnostic.h \
+    DiagnosticWorker.h \
+    LinuxDiagnostic.h
diff --git a/diagtool/main.cpp b/diagtool/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1c0a7638c198ee4a821d11dc7f0839014f322597
--- /dev/null
+++ b/diagtool/main.cpp
@@ -0,0 +1,11 @@
+#include <QCoreApplication>
+#include "DiagnosticWorker.h"
+
+int main(int argc, char *argv[])
+{
+    qSetMessagePattern("%{type} %{if-category}%{category}: %{endif}%{function}: %{message}");
+    QCoreApplication a(argc, argv);
+    DiagnosticWorker * wrkr = new DiagnosticWorker();
+
+    return a.exec();
+}
diff --git a/dist.linux/share/cellframe-diagtool.service b/dist.linux/share/cellframe-diagtool.service
new file mode 100644
index 0000000000000000000000000000000000000000..f88e370fc1f98e97e834aca9a855a1c2fba292ea
--- /dev/null
+++ b/dist.linux/share/cellframe-diagtool.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=Cellframe DiagTool
+After=network.target
+
+[Service]
+WorkingDirectory=/opt/cellframe-node/
+ExecStart=/opt/cellframe-node/bin/cellframe-diagtool &
+ExecStop=/bin/kill -SIGTERM $MAINPID
+Restart=always
+User=root
+Group=root
+RestartSec=10
+LogNamespace=cellframe
+CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_MKNOD CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW CAP_SYS_NICE CAP_SYS_RAWIO CAP_SYSLOG CAP_WAKE_ALARM CAP_SYS_RESOURCE CAP_DAC_READ_SEARCH
+
+[Install]
+WantedBy=multi-user.target
diff --git a/os/debian/postinst b/os/debian/postinst
index 222cd3e85ba2fb067217ff85e93ce071ae5c2255..244613ab2ad1e27f5433c7af77aa5a17f73f106d 100755
--- a/os/debian/postinst
+++ b/os/debian/postinst
@@ -244,11 +244,33 @@ else
             chmod 0664 $filename 2>/dev/null || true
         fi
     done
+
+    #diagtool service
+    if [ -f "$DAP_PREFIX/bin/cellframe-diagtool" ]; then
+        if [ -e /etc/systemd/system/cellframe-diagtool.service ]; then
+            echo "[*] Restarting cellframe-diagtool service"
+            systemctl --system stop cellframe-diagtool  >> /dev/null|| true
+            echo "[*] Stopped cellframe-diagtool"
+            systemctl daemon-reload || true
+            systemctl --system start cellframe-diagtool || true
+            echo "[*] Started cellframe-diagtool"
+        else
+            echo "[!] Installing cellframe-diagtool as systemd service"
+            ln -sf $DAP_PREFIX/share/cellframe-diagtool.service /etc/systemd/system/cellframe-diagtool.service || true
+            systemctl --system enable $DAP_PREFIX/share/cellframe-diagtool.service || true
+        fi
+    fi
+
     chmod 0666 $DAP_CFG
     chmod 0666 $DAP_CFG_TPL
     chmod 0774 $DAP_PREFIX/bin/* || true
     chmod 0777 $DAP_PREFIX/bin/$DAP_APP_NAME-cli $DAP_PREFIX/bin/pip3* $DAP_PREFIX/bin/python3* || true
     
+    #set rwo permissions to configs
+    chmod 666 $(find ${DAP_PREFIX}/etc/ -type f)
+    #set rwx permissions to dirs
+    chmod 777 $(find ${DAP_PREFIX}/etc/ -type d)
+    
     service cellframe-node start || true
     systemctl restart cellframe-node || true
     
diff --git a/version.mk b/version.mk
index 9e74273e8118a983877f84ffd0626cb74cb4cdf5..b5583b90a9fe3440c9b768e36892af4b5684b28f 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 VERSION_MAJOR=5
 VERSION_MINOR=2
-VERSION_PATCH=116
+VERSION_PATCH=117