{"id":18974513,"url":"https://github.com/m7a/lo-madashdock","last_synced_at":"2026-04-08T15:30:19.405Z","repository":{"id":164554608,"uuid":"288576515","full_name":"m7a/lo-madashdock","owner":"m7a","description":"Grafana, Influxdb and Telegraf for System and Internet Monitoring","archived":false,"fork":false,"pushed_at":"2024-04-28T19:44:38.000Z","size":705,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-01T09:08:00.926Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/m7a.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-18T22:21:47.000Z","updated_at":"2024-04-28T19:44:42.000Z","dependencies_parsed_at":"2024-04-28T20:46:59.128Z","dependency_job_id":null,"html_url":"https://github.com/m7a/lo-madashdock","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m7a%2Flo-madashdock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m7a%2Flo-madashdock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m7a%2Flo-madashdock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m7a%2Flo-madashdock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/m7a","download_url":"https://codeload.github.com/m7a/lo-madashdock/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239972038,"owners_count":19727290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T15:15:16.885Z","updated_at":"2026-04-08T15:30:19.300Z","avatar_url":"https://github.com/m7a.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\nsection: 37\nx-masysma-name: dashboards_with_docker\ntitle: Grafana, Influxdb and Telegraf for System and Internet Monitoring\ndate: 2020/08/11 23:43:27\nlang: en-US\nauthor: [\"Linux-Fan, Ma_Sys.ma (Ma_Sys.ma@web.de)\"]\nkeywords: [\"docker\", \"grafana\", \"telegraf\", \"influxdb\", \"telegraf\", \"monitoring\", \"dashboard\", \"linux\", \"internet\", \"performance\"]\nx-masysma-version: 1.0.0\nx-masysma-repository: https://www.github.com/m7a/lo-madashdock\nx-masysma-website: https://masysma.net/37/dashboards_with_docker.xhtml\nx-masysma-owned: 1\nx-masysma-copyright: |\n  Copyright (c) 2020 Ma_Sys.ma.\n  For further info send an e-mail to Ma_Sys.ma@web.de.\n---\nOverview\n========\n\nThis repository contains resources for building a local system monitoring\ncomposed of the following components:\n\n * Grafana dashboards\n    * _Few Systems_ dashboard to do basic Linux system monitoring\n    * _Internet Connectivty_ dashboard to monitor connection outrages\n    * _SMART_ dashboard to view HDD health information across systems\n * Influxdb database\n * Telegraf for metric acquisition\n * Docker for running the server-side components\n\nThis is largely running as-is at the Ma_Sys.ma. The following components might\nbe interesting in other contexts, too:\n\n * Configuration settings for running the services with Docker.\n * Dashboard configurations.\n\n## System Composition\n\nThe idea of this repository is to provide sample configuration for the following\nuse case:\n\n * Centralized monitoring server (_server_)\n * Multiple clients sending metrics to the server (_clients_)\n\nTo do this, the server runs the following services as individual Docker\ncontainers:\n\n * `grafana` -- Dashboard exposed on port 3000.\n   Users intending to view the dashboards connect to this endpoint.\n * `influxdb` -- Database, only locally accessible\n * `telegraf-iconqualmon` -- Telegraf instance for acquiring Internet\n   Connectivity metrics. May be left out if not needed.\n * `stunnel` -- TLS endpoint exposed on port 5086.\n   Telegraf instances on the clients connect to this endpoint.\n\nAll of these services are defined in `server/docker-compose.yml`. All of their\nconfiguration files are collected in directory `server`. Their connections among\neach other could be visualized as follows:\n\n    .             //////////////////////////////////////////////////////////////\n                  //////////////////////////////////////////////////////////////\n                  /////                                                    /////\n         o        /////  Server-Side (running in containers)               /////\n        _|_       /////                                                    /////\n         |        /////  +------+---------+                                /////\n        / \\ ---\u003e exposed   3000 | grafana |                                /////\n      Client      /////  +------+---------+                                /////\n      (User)      /////            ^                                       /////\n                  /////            |                                       /////\n                  /////  +------+----------+     +----------------------+  /////\n                  /////  | 8086 | influxdb | \u003c-- | telegraf-iconqualmon |  /////\n    //////////    /////  +------+----------+     +----------------------+  /////\n    //      //    /////            ^             (optional for Internet    /////\n    // Cli- //    /////            |              connectivity monitoring) /////\n    // ent  //    /////  +------+---------+                                /////\n    //      ---\u003e exposed   5086 | stunnel |                                /////\n    // Tel- //    /////  +------+---------+                                /////\n    // egr- //    /////                                                    /////\n    // af   //    //////////////////////////////////////////////////////////////\n    //////////    //////////////////////////////////////////////////////////////\n\nThe clients connect using Telegraf. System metrics are best acquired by running\non the actual system. Hence, Telegraf is run as a regular systemd service (not\nin a Docker container).\n\nDatabase connections are secured using TLS server and client certificates. As\nInfluxdb does not support client certificates directly, `stunnel` is used to\nhandle TLS traffic.\n\nEach host will need individual configuration e.g. graphics card metrics may\nnot be available on all hosts, VMs will need different metrics compared to\nphysical systems etc. To address this need, a script to generate an installation\nscript for Telegraf is provided as `genclientinstaller.sh`. See section\n_Client Installation_ for details.\n\nServer Installation\n===================\n\n## TLS Configuration Preparation\n\nBefore running the server, necessary TLS certificates and keys need to be\ngernated. Directory `scripts-tlsmanagement` contains some scripts to\nsimplify the process (a little).\n\nThe following certificates and keys are needed in the end:\n\n * CA private key (`ca.key`)\n * CA certificate (`ca.cer`)\n * Server certificate (`server.cer`)\n * Server private key (`server.key`)\n * Client certificate (`client.cer`)\n * Client pirvate key (`client.key`)\n\n### 1. Configure Server Host IP\n\nProper TLS requires that certificates match the hosts they are created for.\n`stunnel` can be configured to ignore this such that the individual clients do\nnot need their certificates to be tied to their hostnames or IP addresses. For\nthe server side, however, the server's hostname (or IP) needs to match whats\ngiven in the certificate thus the first step is to write the server's IP\naddress to file `scripts-tlsmanagement/keys/extfile.cnf`. For instance, if\nthe server's address is 192.168.1.139, then configure the file as follows:\n\n\tsubjectAltName = IP:192.168.1.139\n\n### 2. Generate CA and Server Keys\n\nScript `scripts-tlsmanagement/geninitialkeys.sh` generates the CA and server\nkeys and certificates using the following openssl commands:\n\n~~~{.bash}\n# set RSA strength in bits\nstrength=8192\n# generate CA certificate\nopenssl req -days 36500 -nodes -newkey rsa:$strength -keyform PEM -keyout keys/ca.key -x509 -outform PEM -out keys/ca.cer\n# generate server key\nopenssl genrsa -out keys/server.key $strength\n# generate server certificate signing request\nopenssl req -new -key keys/server.key -out keys/server.req -sha256\n# sign the server's key using the CA's certificate\nopenssl x509 -req -days 36500 -in keys/server.req -CA keys/ca.cer -CAkey keys/ca.key -CAcreateserial -outform PEM -out keys/server.cer -sha256 -extfile keys/extfile.cnf\n~~~\n\nAll resulting keys are written to the `keys` directory. Note that expiry is set\nto 100 years here to avoid monitoring from stopping due to key expiry. If you\nconsider it important, you can of course set a shorter validity.\n\n### 3. Generate Client Key\n\nGenerate a client key using script `scripts-tlsmanagement/genclientkeys.sh`\nwhich runs the following commands:\n\n~~~{.bash}\n# generate client key\nopenssl genrsa -out \"keys/$clnt/client.key\" \"$strength\"\n# generate client certificate signing request\nopenssl req -new -key \"keys/$clnt/client.key\" -out \"keys/$clnt/client.req\"\n# sign the client's key using the CA's certificate\nopenssl x509 -req -in \"keys/$clnt/client.req\" -CA keys/ca.cer \\\n\t\t\t-CAkey keys/ca.key -extensions client -outform PEM \\\n\t\t\t-out \"keys/$clnt/client.cer\"\n~~~\n\n### 4. Distribute Keys to the Server\n\nBy configuration from `stunnel.conf` and `docker-compose.yml`, the following\nkey files are required on the server:\n\n * `keys-server/server.key` -- the server's private key. Copy over from\n   `scripts-tlsmanagement/keys`.\n * `keys-server/server-ca.cer` -- the server's certificate (`server.cer`)\n   followed by the CA's certificate (`ca.cer`) concatenated in the same file.\n * `keys-server/allclients.cer` -- all of the clients' certificates concatenated\n   into a single file.\n\nA working set of files can be obtained by performing the following steps:\n\n~~~{.bash}\n# copy server's private key\ncp keys/server.key ../server/keys-server\n# assemble server's certificate among with the CA's certificate\ncat keys/server.cer keys/ca.cer \u003e ../server/keys-server/server-ca.cer\n# assemble all client keys\ncat keys/*/client.cer \u003e ../server/keys-server/allclients.cer\n~~~\n\nMake sure that `keys-server` and its files are owned by user 101 (that's what\nstunnel runs under inside the Docker container).\n\n## Password Configuration\n\nPasswords can be configured either through environment variables or directly in\n`docker-compose.yml`. To easily set the environment variables, create file\n`server/.env` with contents of the following scheme (without the comments).\n\n\tGF_SECURITY_ADMIN_PASSWORD=password1         # Grafana password\n\tINFLUXDB_ADMIN_PASSWORD=password2            # Influxdb admin password\n\tINFLUXDB_READ_USER_PASSWORD=password3        # Influxdb read-only\n\tINFLUXDB_WRITE_USER_PASSWORD=password4       # Influxdb write-only\n\tMASYSMAWRITER_PASSWORD=password4\n\tMASYSMAREADER_PASSWORD=password3\n\nPasswords `INFLUXDB_READ_USER_PASSWORD` and `MASYSMAREADER_PASSWORD` as well\nas `INFLUXDB_WRITE_USER_PASSWORD` and `MASYSMA_READ_USER_PASSWORD` need to match\n(otherwise database connectivity will fail).\n\n## Internet Connectivity\n\nIf you want to use the _Internect Connectivity_ dashboard, be sure to configure\ndifferent URLs and servers in `server/iconqualnmon/telegraf.conf`\n\n## Run\n\nThe containers can be started with `docker-compsoe` from directory `server`:\n\n\t# docker-compose up grafana influxdb stunnel\n\nIf you have configured the _Internet Connectivity_ settings in\n`server/iconqualnmon/telegraf.conf` you can start all containers (i.e. including\nthe optional `telegraf-iconqualmon` service) with:\n\n\t# docker-compose up\n\nBy default, all data will be stored within the respective containers. While this\nallows easy testing and cleanup, it also effectively disables persistence. Once\nyou intend to run the containers more permanently, edit `docker-compose.yml` and\nenable the commented-out mappings from the `volumes` sections (grafana directory\nneeds to belong to user 472). Change the\nhost-side according to your local configuration and then re-create all\ncontainers and this time run them with `docker-compse up -d` to run them in\nbackground.\n\nClient Installation\n===================\n\nIf you are running the _Internet Connectivity_ dashboard, the server\ninstallation may be enough. If you want to monitor individual systems, of\ncourse, they need to run a local Telegraf instance to gather system metrics.\n\nScript `scripts-clientmon/genclieninstaller.sh` is prepared to generate\ninstallation scripts to be used to install the client on Debian systems. The\nidea is to package all necessary key material, configuration and setup scripts\ninto a single “installer script” such that adding new clients is reasonably\neasy.\n\nBefore using it, create a file `.env` next to `genclientinstaller.sh` with the\nfollowing conents:\n\n~~~\nkeydir=\".../clients/$1\"\ncacert=\".../ca.cer\"\nMASYSMA_INFLUXDB=...\n~~~\n\nThe dots need to be set according to your local file structure and network:\n\n`keydir`\n:   Set this to a directory where the key material for the current client can\n    be found.\n`cacert`\n:   Set this to the `ca.cer` file's location\n`MASYSMA_INFLUXDB`\n:   Set this to the server's hostname where the Influxdb is running.\n\nAfter this configuration, invoke\n\n~~~\n$ ./genclientinstaller.sh \u003cclient\u003e \u003e install-on-\u003cclient\u003e.sh\n~~~\n\nto generate a setup script. This will include the key material and configuration\ndata to use on that client. Be sure to tweak the generated `telegraf.conf`\nbefore running `install-on-\u003cclient\u003e.sh` on the target machine as root.\n\nNote: By its original package, Telegraf runs as a separate user. Given that it\nmight be interesting to also monitor Docker, however, it becomes necessary to\neffectively give root permissions to Telegraf (either by adding it to the\n`docker` group or by running it as root). If you do not want to monitor Docker\n(or SMART or other things that require root), consider changing\n`genclientinstaller.sh` accordingly.\n\nDashboards\n==========\n\n## Internet Connectivity\n\nThis dashboard is intended to show a continuous measure of connection quality\nbe displaying the timings of regular ping packets and TCP connections.\n\nNote: In case you configured different hostnames in Telegraf, you may need to\nedit the panels to use your host names as filters rather than the ones provided\nin the sample configuration.\n\n![Internet Connectivity Dashboard](dashboards_with_docker_att/iconqual.png)\n\nThe screen is divided into nine parts with three entries per row.\n\nIn the first row, all times are given in milliseconds.\n\nFirst row: _Ping_\n:   Probably the most important panel. This is displaying the timings of\n    ping requests. Failed pings are indicated by a spike in the bold red line\n    `resultcode`.\n\nThis panel's queries are largely independent of the configuration:\n\n~~~{.sql}\nSELECT max(\"maximum_response_ms\") FROM \"ping\" WHERE $timeFilter\n\t\t\t\tGROUP BY time($__interval), \"url\" fill(null)`\nSELECT MAX(\"result_code\") FROM \"ping\" WHERE $timeFilter\n\t\t\t\tGROUP BY time($__interval) fill(null)\n~~~\n\nFirst row: _Ping Hist_\n:   A histogram of ping results. Here, one can see that most pings\n    finish in just under 16 ms and only a small fraction requires more than\n    18 ms.\n\nThe qureies' WHERE clauses need to be edited to match the Telegraf\nconfiguration:\n\n~~~{.sql}\n-- For ping 8.8.4.4\nSELECT max(\"maximum_response_ms\") FROM \"ping\" WHERE url = '8.8.4.4'\n\t\t\tAND $timeFilter GROUP BY time($__interval) fill(null) \n-- For ping masysma.lima-city.de\nSELECT max(\"maximum_response_ms\") FROM \"ping\" WHERE url = 'masysma.lima-city.de'\n\t\t\tAND $timeFilter GROUP BY time($__interval) fill(null) \n~~~\n\nIn the second row, all times are given in seconds.\n\nSecond row: _HTTP Response Time_\n:   Similar to the _Ping_ panel, this one displays the duration of the download\n    of a small webpage through HTTPS. Again, a bold red line `response` may\n    spike to show failed connections.\n\nThe queries for this panel are generic again:\n\n~~~{.sql}\nSELECT max(\"result_code\") FROM \"http_response\" WHERE $timeFilter\n\t\t\t\tGROUP BY time($__interval) fill(null)\nSELECT max(\"response_time\") FROM \"http_response\" WHERE $timeFilter\n\t\t\t\tGROUP BY time($__interval), \"server\" fill(null)\n~~~\n\nSecond Row: _Response Time_ Historgram\n:   Presents a histogram display of the measured HTTP response times.\n\n~~~{.sql}\n-- Response time masysma.lima-city.de\nSELECT max(\"response_time\") FROM \"http_response\"\n\tWHERE (\"server\" = 'https://masysma.lima-city.de/31/web_main.xhtml')\n\t\tAND $timeFilter GROUP BY time($__interval) fill(null)\n-- Response Time www.telekom.de\nSELECT max(\"response_time\") FROM \"http_response\"\n\tWHERE (\"server\" = 'https://www.telekom.de/start')\n\t\tAND $timeFilter GROUP BY time($__interval) fill(null)\n~~~\n\nThe third row is dedicated to displaying numbers of failures.\n\nThird row: _content match_\n:   This special panel shows the number of times the content retrieved through\n    HTTPS matched the expectations. One can see that in the screeshot, all\n    360 connections were successfully returning the expected content. For this\n    matching to work, you need to configure the `response_string_match` and\n    `urls` in Telegraf:\n\n~~~\n# server/iconqualnmon/telegraf.conf excerpt\n[[inputs.http_response]]\n  interval = \"120s\"\n  urls = [\"https://masysma.lima-city.de/31/web_main.xhtml\"] # CONFIGURE HERE\n  response_timeout = \"4s\"\n  method = \"GET\"\n  response_string_match = \"\u003ch1\u003eMa_Sys.ma Startseite\"        # CONFIGURE HERE\n  follow_redirects = false\n~~~\n\nThe panel's queries also need to be configured to use the correct URL:\n\n~~~{.sql}\n-- match (generic)\nSELECT COUNT(\"result_code\") FROM \"http_response\"\n\tWHERE \"response_string_match\" = 1 AND $timeFilter\n-- mismatch (configuration required)\nSELECT COUNT(\"result_code\") FROM \"http_response\"\n\tWHERE (\"result_code\" \u003c\u003e 0 OR \"response_string_match\"  = 0)\n\t\tAND \"server\" = 'https://masysma.lima-city.de/31/web_main.xhtml'\n\t\tAND $timeFilter\n~~~\n\nThird row: Tables\n:   The remaining two panels show exact counters for failed pings and failed\n    HTTP connections respectively. The tables are generic and need not be\n    configured.\n\n~~~{.sql}\n-- Table: Packet losses\nSELECT COUNT(\"result_code\") FROM \"ping\" WHERE (\"percent_packet_loss\" \u003e 0 OR\n\t\t\"result_code\" = 1) AND $timeFilter GROUP BY \"url\" fill(none)\n-- Table: HTTP/Web Connection Failures\nSELECT COUNT(\"result_code\") FROM \"http_response\"\n\t\tWHERE (http_response_code \u003c\u003e 200 OR response_string_match = 0 OR\n\t\t\tresult_code \u003c\u003e 0) AND $timeFilter GROUP BY \"server\";\n~~~\n\n## Few Systems Overview\n\nThis dashboard is intended to show the system health and load for about three\nsystems (depending on screen space). It works for Linux systems and degrades\ngracefully if some non-essential metrics are missing.\n\n![Few Systems Overview with just a single system](dashboards_with_docker_att/fewsysover.png)\n\nThe first six fields for each system are as follows:\n\n 1. Uptime Indicator:\n    The first item displayed is the system's uptime.\n    This metric is important to see if a server that should be always online\n    has been restarted recently.\n 2. Total RAM Indicator\n 3. Total SWAP Indicator\n 4. Load Average Indicator\n 5. RAM used indicator\n 6. SWAP used indicator\n\nThe following fields are present under special cirumstances:\n\napcupsd panel\n:   If connected to an UPS supported by `apcupsd` (and enabled in Telegraf),\n    two metrics are displayed: `.remain` displays the time the UPS may stay\n    on battery as of now and `onbat` displays `GRID` while the system is\n    attached to external power and the time the UPS has been running on battery\n    if not. If `apcupsd` data is not available, the whole field will display\n    `NO UPS`.\n\nDocker Containers panel\n:   Displays the number of running Docker conatiners (if reported by Telegraf).\n\nNote: It would be interesting to display the status of RAID arrays, too, but\nTelegraf does not provide this as a metric.\n_TODO z: It should be possible to do this by parsing `/proc/mdstat`, though._\n\nAfter these special panels follow some larger tabular-style panels:\n\n * CPU load panel with individual CPU loads\n * File system use table\n * Network Interface rx/tx and error data.\n   _TODO z: It would be interesting to show IP addresses here, but Telegraf\n   does not seem to collect them._\n\nThe last panel is a diagram which displays all the important system metrics in\na single chart:\n\n * CPU load (averaged across all cores; 100% means all cores are 100% loaded)\n * RAM usage\n * SWAP usage\n * GPU usage (as reported by `nvidia-smi`, if the metric is collected)\n * Disk usage: `DISKFULL` reports the percentage used for the “fullest” file\n   system. The idea is that if you run out of disk space (on any disk), this\n   line will approach 100%.\n\n## HDD S.M.A.R.T Values\n\nThis advanced dashboard is intended to provide an overview of the S.M.A.R.T\ndata across the HDDs and SSDs installed across multiple systems. It is highly\nexperimental and hints for making it more useful are welcome!\n\n![S.M.A.R.T Dashboard](dashboards_with_docker_att/smart.png)\n\nFor each HDD, two panels are displayed:\n\nTable: All Attributes\n:   This table is intended to show the S.M.A.R.T values in numeric form.\n    By using different colors, it tries to distinguish common values like 0\n    or 100 from less common ones. It will at most display one value per day\n    as S.M.A.R.T data is not expected to change too rapidly.\n\nNote: The table in their current form could see some improvements wrt. the\nunits of data displayed and wrt. displaying only the relevant one of\n`normalized` and `raw_value`. The problem here is that the interpretation of\nattributes is manfacturer and thus essentially drive-specific and Grafana does\nnot seem to provide a convenient means to attach the device-specific information\nto the table (short of hand-crafting the table for each and every invidiual\ndrive).\n\nDiagram: Attribute changes\n:   Displays the differences in attributes across time.\n    As of now, the diagram is not overly useful as it displays values that\n    change with +1 or +2 alongside values which change +32M or +38K... Here, one\n    may better consult the table displayed alongside the diagram and maybe\n    consult the diagram only for the individual values\n    (by clicking on one of the attributes)\n\nAdditional Security Considerations\n==================================\n\nWhile the setup proposed here has some security, it is weak in at least\nthe following regards:\n\n * _All clients share the same client certificate_.\n   `stunnel` documentation describes that it should be possible to provide\n   _multiple_ client certificates in a CAfile, however this did not seem to\n   work when tested. Only the first certificate listed was ever accepted. Hence\n   the idea to use only one certificate at all. Another approach might be to use\n   certificate directories (instead of a single file) which should allow for\n   multiple client certificates.\n\n * _Grafana access is not secured_. To complete the secure setup, TLS access\n   should be used for accessing the Grafana dashboards, too. While it is of\n   less concern if the dashboards are expected to be publically viewable, it\n   may still sometimes be needed to access the admin ínteraface and this access\n   should be encrypted such that attackers cannot sniff the login information\n   from packets transmitted over network.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm7a%2Flo-madashdock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fm7a%2Flo-madashdock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm7a%2Flo-madashdock/lists"}