notify-client-server


Namenotify-client-server JSON
Version 0.1.9 PyPI version JSON
download
home_pageNone
Summarynotification server/website with client functions
upload_time2025-07-13 00:52:33
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords notifications client server
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            * website: <https://arrizza.com/python-notify-client-server.html>
* installation: see <https://arrizza.com/setup-common.html>

## Summary

This project shows a client and server for notifications.

I have several PCs that are on my home network (a couple of Ubuntu, a Mac laptop and a Win11 laptop).
These have backup scripts but those jobs are independent.
To see if any of them ran or failed, I had to manually check the logs and files.
That was a pain.

To fix it, a central server is needed and each of the PCs send a notification when a job passes or fails.
Then using a browser I can see what happened at 2:00AM when the backup jobs ran.

## Server

The server is started by running ```./do_server```.

It assumes that there is a directory "webhost" for a sqlite database to be created there.

Note that the server can be started as an Ubuntu service.
See src/notify_client_server/template_notify_server.service for a starting point for your
service.

Copy that template to ```./webhost/notify_server.service```

Configure it by changing the values in braces ```{ }```.
Currently, these are:

* ```User={svc_user_name}``` : the userid the service should run under
* ```Group={svc_group_name}```: the groupid the service should run under
* ```WorkingDirectory={svc_working_dir}``` : the working directory.
  Use the notify-client-server git repo directory
* ```ExecStart={svc_do_server_path}``` : the path to the ./do_server script.
  Normally this is the svc_working_dir above with ```/do_server```
* use absolute full paths

For example:

```text
<snip>
[Service]
User=my_userid
Group=my_userid
WorkingDirectory=/home/my_userid/projects/notify-client-server
ExecStart=/home/my_userid/projects/notify-client-server/do_server
<snip>
```

## Configuration

See src/notify_client_server/template_cfg.json for configuration.

The configuration json is in three sections.

* ```is_local```: if true, uses value from ```local_mode```, otherwise uses values from ```remote_mode```

#### local_mode

This section is used when your server is running on this local (Ubuntu) machine

* ```is_debug```: is used for the flask server to be automatically restarted when it detects a change.
  Very handy during development!
* ```server_hostname```: the hostname of your local PC
* ```server_port```: the URL port to use. Currently 5004
* ```server_ip```: you can use 127.0.0.1 or 0.0.0.0
* ```server_url```: defaults to the current hostname and port
* ```server_sudo_pwd_path```: holds your root pwd. Since the Ubuntu service is installed via root
  you'll to provide it. Currently, webhost/pf_pwd holds that. The "pf_" indicates it is a personal file
  and the .gitignore does not commit it.
* ```svc_name``` : the name of the service file. Currently notify_server.service
* ```svc_user_name```: the userid to run the service under
* ```svc_group_name```: the groupid to run the service under
* ```svc_working_dir```: the location of the local notify-client-server directory
* ```svc_do_server_path```: the location of the do_server script

* ```clients```: the list of clients to use for ```do_admin push```

#### remote_mode

These are the same as the values above, except for the remote server.

#### pc_info

Holds client information as needed by ```do_admin push```

```pc hostname```: e.g. "pc1" to identify the hostname used by ssh to connect to that client

Each PC entry has to have the following fields:

* ```user_name```: the userid used by ssh to connect to that client
* ```working_dir```: the location of the notify-client-server directory
* any additional fields required for do_admin e.g. jad_dir (see example below)

## Admin

To install and interact with the server, you can use ```do_admin```

That script assumes you have fully installed the notify-client-server:

```text
./do_subm_update full
./do_install

ls -al webhost
-rw-r--r--  1 my_userid my_userid 8282 May  1 20:33 notify_server.db   <= will be created when the server is started
-rw-rw-r--  1 my_userid my_userid 3080 May  4 14:45 notify-server.json
-rw-rw-r--  1 my_userid my_userid  346 May  4 14:45 notify_server.service

./do_server
# fix any warnings, issues, etc.
# note this expects the configuration to be correctly set up, see above
```

* ```./do_admin install``` : installs the Ubuntu service using the notify_server.service
* ```./do_admin restart``` : starts or restarts the service
* ```./do_admin status``` : shows the service's current status
* ```./do_admin journal``` : shows the service log for any warnings etc.
* ```./do_admin stop``` : stops the service
* ```./do_admin uninstall ``` : removes the service from Ubuntu services

* ```./do_admin push ``` : sends the .cfg file to all of your clients
* ```./do_admin test ``` : checks you can connect to your clients and run multiple commands.

#### To add or replace a command

* create a class that inherits from AdminBase
* add any new or override commands to self.fn_map()
* implement the functions as needed
* add fields to notify_server.json as needed

```python
import sys

from notify_client_server.admin_base import AdminBase
from notify_client_server.cfg import cfg
from notify_client_server.os_specific import OsSpecific


# -------------------
## run a command for the local PC or for a remote PC
class App(AdminBase):
    # -------------------
    ## run a command
    #
    # @param cmd  the command to run
    # @return None
    def run(self, cmd):
        # add sample of new command: test
        self.fn_map['pull'] = self.do_pull
        self.fn_map['push'] = self.do_push_cfg  # override builtin push()

        self.run_command(cmd)

    # -------------------
    ## do a git pull in all jad directories
    #
    # @return None
    def do_pull(self):
        lines = [
            'hostname -s',
            'pwd',
            'cd {jad_dir}', <== 
            'pwd',
            'git pull',  # get latest repo version
        ]
        self.run_on_all_pcs('pull', lines, skip_local=False)

    # -------------------
    ## push files to remote PCs
    #
    # @return None
    def do_push_cfg(self):
        # <snip> set rsync opts here
        pc_list = self._get_pc_list()
        for pc in pc_list:
            if pc == OsSpecific.hostname:
                # skip the sender PC
                continue

            path = '{pc}:{jad_dir}/webhost'
            path = cfg.translate_for(pc, path)
            cmd = f'rsync {opts} webhost/ {path}/'
            print(f' --> pushing to {pc}')
            OsSpecific.run_cmd(cmd, print_cb=self._print_cb)
```

## Client(s)

The clients are kicked off using ```./doit```.
See sample/app.py to see how a notification is sent.

It also shows how to send a "ping". This can be used to ensure communication is working
before sending a notification.

## Run

Running ```./do_server```, as typical output:

```text
==== do_server: starting...
     gen: constants_version.py rc=0
16:24:21.076 ==== on 2025/05/04 
00:00:00.000 ==== server.main host=0.0.0.0, port=5004, debug=True
 * Serving Flask app 'server'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5004
 * Running on http://10.0.0.17:5004
Press CTRL+C to quit
 * Restarting with stat
16:24:21.155 ==== on 2025/05/04 
00:00:00.000 ==== server.main host=0.0.0.0, port=5004, debug=True
 * Debugger is active!
 * Debugger PIN: 926-003-063
```

Then open a browser to: http://localhost:5004/. There should be no notifications.

Run ```./doiit```

```text
(venv)$ ./doit
OK   set_env: rc=0
OK   doit: out_dir: /home/myuserid/projects/notify-client-server/out
==== doit: starting...
16:26:29.954 INFO on 2025/05/04 
00:00:00.000 INFO App.init
00:00:00.000 INFO App.run
00:00:00.002 DBG  ping successful      POST 200 http://john26:5004
00:00:00.007 DBG  notify successful    POST 200 http://john26:5004
     doit: run_it rc=0
     doit: overall rc=0
```

The server should output some logging:

```text
127.0.0.1 - - [04/May/2025 16:25:38] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [04/May/2025 16:25:38] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [04/May/2025 16:26:29] "POST /ping HTTP/1.1" 200 -
127.0.0.1 - - [04/May/2025 16:26:29] "POST /notify HTTP/1.1" 200 -
127.0.0.1 - - [04/May/2025 16:27:26] "GET / HTTP/1.1" 200 -
```

Refresh the browser page. There should be a sample notification:

```text
Notifications
        date                 source event         status	description
Delete  2025/05/04 16:26:29	 john26	backup_nas x	ok	    running good 17
Pings: 1
```

The "Pings" should be 1. Run ```doit``` again and it should bump to 2, etc.

Click the Delete buttons to remove the notification on that row.

## crontab

I have a python script that backs up the PC it's on to a NAS and
at the end of that process it sends a notifications to notify_server:

```python
def _notify(self, status, desc):
  tag = self._tag      <== indicates some information where the backup occurred e.g. crontab
  if len(sys.argv) > 1:
      tag = f'{self._tag} {sys.argv[1]}'  <== add info from the command line
  self._client.notify(tag, status, desc) 
```

The status is "ok", "fail" or "warn" depending on what happened during the backup:

```python
status = 'ok'
if self._backups_warn > 0:      <== keeps track of any warnings logged
    status = 'warn'
if self._backups_failed > 0:    <== keeps track of any errors logged
    status = 'err'
```

The desc variable contains some useful info:

```text
00:00:08 overallrc=0; done:0 warns:0 failed:0; needs commit: True;"
   ^-- time to complete the backup HH:MM:SS
           ^-- overall return code
                        ^-- number of directories backed up
                               ^-- number of warnings
                                      ^-- number of errors
                                                ^-- state of git the repo I use for this kind of maintenance  
```

crontab entry looks like this:

```text
45  02  *   *   *     cd /jad; test_notify/doit "crontab" > /jad/test_notify/test.log
      ^-- runs every day at 2:45AM
                        ^-- enters my maintenance repo
                                     ^-- runs the backup and indicates its from a crontab entry instead of manual
                                                                ^-- saves the full log

```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "notify-client-server",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "\"J. Arrizza\" <cppgent0@gmail.com>",
    "keywords": "notifications, client, server",
    "author": null,
    "author_email": "\"J. Arrizza\" <cppgent0@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/a5/de/9242f349eaba89c6b1ff97114439d282a980f36984c3a16788af243a1b48/notify_client_server-0.1.9.tar.gz",
    "platform": null,
    "description": "* website: <https://arrizza.com/python-notify-client-server.html>\n* installation: see <https://arrizza.com/setup-common.html>\n\n## Summary\n\nThis project shows a client and server for notifications.\n\nI have several PCs that are on my home network (a couple of Ubuntu, a Mac laptop and a Win11 laptop).\nThese have backup scripts but those jobs are independent.\nTo see if any of them ran or failed, I had to manually check the logs and files.\nThat was a pain.\n\nTo fix it, a central server is needed and each of the PCs send a notification when a job passes or fails.\nThen using a browser I can see what happened at 2:00AM when the backup jobs ran.\n\n## Server\n\nThe server is started by running ```./do_server```.\n\nIt assumes that there is a directory \"webhost\" for a sqlite database to be created there.\n\nNote that the server can be started as an Ubuntu service.\nSee src/notify_client_server/template_notify_server.service for a starting point for your\nservice.\n\nCopy that template to ```./webhost/notify_server.service```\n\nConfigure it by changing the values in braces ```{ }```.\nCurrently, these are:\n\n* ```User={svc_user_name}``` : the userid the service should run under\n* ```Group={svc_group_name}```: the groupid the service should run under\n* ```WorkingDirectory={svc_working_dir}``` : the working directory.\n  Use the notify-client-server git repo directory\n* ```ExecStart={svc_do_server_path}``` : the path to the ./do_server script.\n  Normally this is the svc_working_dir above with ```/do_server```\n* use absolute full paths\n\nFor example:\n\n```text\n<snip>\n[Service]\nUser=my_userid\nGroup=my_userid\nWorkingDirectory=/home/my_userid/projects/notify-client-server\nExecStart=/home/my_userid/projects/notify-client-server/do_server\n<snip>\n```\n\n## Configuration\n\nSee src/notify_client_server/template_cfg.json for configuration.\n\nThe configuration json is in three sections.\n\n* ```is_local```: if true, uses value from ```local_mode```, otherwise uses values from ```remote_mode```\n\n#### local_mode\n\nThis section is used when your server is running on this local (Ubuntu) machine\n\n* ```is_debug```: is used for the flask server to be automatically restarted when it detects a change.\n  Very handy during development!\n* ```server_hostname```: the hostname of your local PC\n* ```server_port```: the URL port to use. Currently 5004\n* ```server_ip```: you can use 127.0.0.1 or 0.0.0.0\n* ```server_url```: defaults to the current hostname and port\n* ```server_sudo_pwd_path```: holds your root pwd. Since the Ubuntu service is installed via root\n  you'll to provide it. Currently, webhost/pf_pwd holds that. The \"pf_\" indicates it is a personal file\n  and the .gitignore does not commit it.\n* ```svc_name``` : the name of the service file. Currently notify_server.service\n* ```svc_user_name```: the userid to run the service under\n* ```svc_group_name```: the groupid to run the service under\n* ```svc_working_dir```: the location of the local notify-client-server directory\n* ```svc_do_server_path```: the location of the do_server script\n\n* ```clients```: the list of clients to use for ```do_admin push```\n\n#### remote_mode\n\nThese are the same as the values above, except for the remote server.\n\n#### pc_info\n\nHolds client information as needed by ```do_admin push```\n\n```pc hostname```: e.g. \"pc1\" to identify the hostname used by ssh to connect to that client\n\nEach PC entry has to have the following fields:\n\n* ```user_name```: the userid used by ssh to connect to that client\n* ```working_dir```: the location of the notify-client-server directory\n* any additional fields required for do_admin e.g. jad_dir (see example below)\n\n## Admin\n\nTo install and interact with the server, you can use ```do_admin```\n\nThat script assumes you have fully installed the notify-client-server:\n\n```text\n./do_subm_update full\n./do_install\n\nls -al webhost\n-rw-r--r--  1 my_userid my_userid 8282 May  1 20:33 notify_server.db   <= will be created when the server is started\n-rw-rw-r--  1 my_userid my_userid 3080 May  4 14:45 notify-server.json\n-rw-rw-r--  1 my_userid my_userid  346 May  4 14:45 notify_server.service\n\n./do_server\n# fix any warnings, issues, etc.\n# note this expects the configuration to be correctly set up, see above\n```\n\n* ```./do_admin install``` : installs the Ubuntu service using the notify_server.service\n* ```./do_admin restart``` : starts or restarts the service\n* ```./do_admin status``` : shows the service's current status\n* ```./do_admin journal``` : shows the service log for any warnings etc.\n* ```./do_admin stop``` : stops the service\n* ```./do_admin uninstall ``` : removes the service from Ubuntu services\n\n* ```./do_admin push ``` : sends the .cfg file to all of your clients\n* ```./do_admin test ``` : checks you can connect to your clients and run multiple commands.\n\n#### To add or replace a command\n\n* create a class that inherits from AdminBase\n* add any new or override commands to self.fn_map()\n* implement the functions as needed\n* add fields to notify_server.json as needed\n\n```python\nimport sys\n\nfrom notify_client_server.admin_base import AdminBase\nfrom notify_client_server.cfg import cfg\nfrom notify_client_server.os_specific import OsSpecific\n\n\n# -------------------\n## run a command for the local PC or for a remote PC\nclass App(AdminBase):\n    # -------------------\n    ## run a command\n    #\n    # @param cmd  the command to run\n    # @return None\n    def run(self, cmd):\n        # add sample of new command: test\n        self.fn_map['pull'] = self.do_pull\n        self.fn_map['push'] = self.do_push_cfg  # override builtin push()\n\n        self.run_command(cmd)\n\n    # -------------------\n    ## do a git pull in all jad directories\n    #\n    # @return None\n    def do_pull(self):\n        lines = [\n            'hostname -s',\n            'pwd',\n            'cd {jad_dir}', <== \n            'pwd',\n            'git pull',  # get latest repo version\n        ]\n        self.run_on_all_pcs('pull', lines, skip_local=False)\n\n    # -------------------\n    ## push files to remote PCs\n    #\n    # @return None\n    def do_push_cfg(self):\n        # <snip> set rsync opts here\n        pc_list = self._get_pc_list()\n        for pc in pc_list:\n            if pc == OsSpecific.hostname:\n                # skip the sender PC\n                continue\n\n            path = '{pc}:{jad_dir}/webhost'\n            path = cfg.translate_for(pc, path)\n            cmd = f'rsync {opts} webhost/ {path}/'\n            print(f' --> pushing to {pc}')\n            OsSpecific.run_cmd(cmd, print_cb=self._print_cb)\n```\n\n## Client(s)\n\nThe clients are kicked off using ```./doit```.\nSee sample/app.py to see how a notification is sent.\n\nIt also shows how to send a \"ping\". This can be used to ensure communication is working\nbefore sending a notification.\n\n## Run\n\nRunning ```./do_server```, as typical output:\n\n```text\n==== do_server: starting...\n     gen: constants_version.py rc=0\n16:24:21.076 ==== on 2025/05/04 \n00:00:00.000 ==== server.main host=0.0.0.0, port=5004, debug=True\n * Serving Flask app 'server'\n * Debug mode: on\nWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.\n * Running on all addresses (0.0.0.0)\n * Running on http://127.0.0.1:5004\n * Running on http://10.0.0.17:5004\nPress CTRL+C to quit\n * Restarting with stat\n16:24:21.155 ==== on 2025/05/04 \n00:00:00.000 ==== server.main host=0.0.0.0, port=5004, debug=True\n * Debugger is active!\n * Debugger PIN: 926-003-063\n```\n\nThen open a browser to: http://localhost:5004/. There should be no notifications.\n\nRun ```./doiit```\n\n```text\n(venv)$ ./doit\nOK   set_env: rc=0\nOK   doit: out_dir: /home/myuserid/projects/notify-client-server/out\n==== doit: starting...\n16:26:29.954 INFO on 2025/05/04 \n00:00:00.000 INFO App.init\n00:00:00.000 INFO App.run\n00:00:00.002 DBG  ping successful      POST 200 http://john26:5004\n00:00:00.007 DBG  notify successful    POST 200 http://john26:5004\n     doit: run_it rc=0\n     doit: overall rc=0\n```\n\nThe server should output some logging:\n\n```text\n127.0.0.1 - - [04/May/2025 16:25:38] \"GET / HTTP/1.1\" 200 -\n127.0.0.1 - - [04/May/2025 16:25:38] \"GET /favicon.ico HTTP/1.1\" 404 -\n127.0.0.1 - - [04/May/2025 16:26:29] \"POST /ping HTTP/1.1\" 200 -\n127.0.0.1 - - [04/May/2025 16:26:29] \"POST /notify HTTP/1.1\" 200 -\n127.0.0.1 - - [04/May/2025 16:27:26] \"GET / HTTP/1.1\" 200 -\n```\n\nRefresh the browser page. There should be a sample notification:\n\n```text\nNotifications\n        date                 source event         status\tdescription\nDelete  2025/05/04 16:26:29\t john26\tbackup_nas x\tok\t    running good 17\nPings: 1\n```\n\nThe \"Pings\" should be 1. Run ```doit``` again and it should bump to 2, etc.\n\nClick the Delete buttons to remove the notification on that row.\n\n## crontab\n\nI have a python script that backs up the PC it's on to a NAS and\nat the end of that process it sends a notifications to notify_server:\n\n```python\ndef _notify(self, status, desc):\n  tag = self._tag      <== indicates some information where the backup occurred e.g. crontab\n  if len(sys.argv) > 1:\n      tag = f'{self._tag} {sys.argv[1]}'  <== add info from the command line\n  self._client.notify(tag, status, desc) \n```\n\nThe status is \"ok\", \"fail\" or \"warn\" depending on what happened during the backup:\n\n```python\nstatus = 'ok'\nif self._backups_warn > 0:      <== keeps track of any warnings logged\n    status = 'warn'\nif self._backups_failed > 0:    <== keeps track of any errors logged\n    status = 'err'\n```\n\nThe desc variable contains some useful info:\n\n```text\n00:00:08 overallrc=0; done:0 warns:0 failed:0; needs commit: True;\"\n   ^-- time to complete the backup HH:MM:SS\n           ^-- overall return code\n                        ^-- number of directories backed up\n                               ^-- number of warnings\n                                      ^-- number of errors\n                                                ^-- state of git the repo I use for this kind of maintenance  \n```\n\ncrontab entry looks like this:\n\n```text\n45  02  *   *   *     cd /jad; test_notify/doit \"crontab\" > /jad/test_notify/test.log\n      ^-- runs every day at 2:45AM\n                        ^-- enters my maintenance repo\n                                     ^-- runs the backup and indicates its from a crontab entry instead of manual\n                                                                ^-- saves the full log\n\n```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "notification server/website with client functions",
    "version": "0.1.9",
    "project_urls": {
        "Download": "https://bitbucket.org/arrizza-public/notify-client-server/get/master.zip",
        "Source": "https://bitbucket.org/arrizza-public/notify-client-server/src/master",
        "Website": "https://arrizza.com/python-notify-client-server"
    },
    "split_keywords": [
        "notifications",
        " client",
        " server"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a5de9242f349eaba89c6b1ff97114439d282a980f36984c3a16788af243a1b48",
                "md5": "c881b02c2f6b9b5c2288d6c4af0ba5db",
                "sha256": "3d900b676e0441c5b1290e4632a261b67a48339e0e0151af8fc15be1de766b1c"
            },
            "downloads": -1,
            "filename": "notify_client_server-0.1.9.tar.gz",
            "has_sig": false,
            "md5_digest": "c881b02c2f6b9b5c2288d6c4af0ba5db",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 20559,
            "upload_time": "2025-07-13T00:52:33",
            "upload_time_iso_8601": "2025-07-13T00:52:33.045305Z",
            "url": "https://files.pythonhosted.org/packages/a5/de/9242f349eaba89c6b1ff97114439d282a980f36984c3a16788af243a1b48/notify_client_server-0.1.9.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-13 00:52:33",
    "github": false,
    "gitlab": false,
    "bitbucket": true,
    "codeberg": false,
    "bitbucket_user": "arrizza-public",
    "bitbucket_project": "notify-client-server",
    "lcname": "notify-client-server"
}
        
Elapsed time: 2.68332s