How to run Python script in background on boot successfully?


#! /usr/bin/python

import glib
import re
import subprocess
import requests
import bs4
import datetime
import sys
import os
import time
from selenium import webdriver
from pyudev import Context, Monitor
from selenium.common.exceptions import NoSuchElementException


def demote():
    def result():
        os.setgid(100)
        os.setuid(1000)
    return result


def inotify(title, message):
    subprocess.call('notify-send', '{}
'.format(title), '{0}
'.format(message)], preexec_fn=demote())
    #os.system('notify-send ' + title + ' ' + message)


def get_network_data(tout):
    """Scrapes balance data from ISP website."""


    if tout is not None:
        driver = webdriver.PhantomJS()
        driver.set_window_size(1120, 550)
        driver.get('http://mydata.myisp.co.ke')
        try:
            acc = driver.find_element_by_id('account')
            tbody = acc.find_element_by_tag_name('tbody')
        
            match_obj = re.findall(r'(\w\s]+)\:\s(\d\s\.\-]+\w]{0,3})\s
](\w\s]+)\:\s(\d\s\.\-]+\w]{0,3})\s
]', tbody.text)
            if match_obj is not None:
                data = {}
                for item in match_obj:
                    data.update(dict(zip(*[iter(item)]*2)))


                expiry_date = datetime.datetime.strptime(data'Expire Date'], '%Y-%m-%d')
                remaining = expiry_date - datetime.datetime.now()
                bundle_balance = 'Bundle balance: {0} - {1} day(s) remaining'.format(data'Bundle Balance'], remaining.days)
                airtime_balance = 'Airtime balance: %s' % data'Current Airtime Balance']
                time_now = datetime.datetime.now().strftime('%H:%-M %Y/%m/%d')
                print('Sending nofitication...')
                imon = open('/home/Giantas/bin/monitori.txt', 'a+')
                imon.write(time_now + '
' + bundle_balance + '
' + airtime_balance + '

')
                imon.close()
                full_msg = '{0}
{1}'.format(bundle_balance.capitalize(), airtime_balance.capitalize())
                inotify('Orange Balance', full_msg)
                #subprocess.call('notify-send', 'Orange Balance', '
{0}
{1}'.format(bundle_balance.capitalize(), airtime_balance.capitalize())], preexec_fn=demote())


            else:
                print('Could not retrieve data from page...')
                full_msg = '{0}'.format('Error: Could not retrieve data from page.')
                inotify('Orange Balance', full_msg)
                #subprocess.call('notify-send', 'Orange Balance', '
{0}'.format('Error: Could not retrieve data from page.')], preexec_fn=demote())


        except NoSuchElementException:
            print('Could not locate element...')
            full_msg = '{0}'.format('Error: Could not locate element - acc.')
            inotify('Orange Balance', full_msg)
            #subprocess.call('notify-send', 'iMonitor:get_network_data', '
{0}'.format('Error: Could not locate element - acc.')], preexec_fn=demote())
        
    else:
        print('Could not find USB device...')
        full_msg = '
{0}'.format('Error: Could not find USB device.')
        inotify('Orange Balance', full_msg)
        #subprocess.call('notify-send', 'iMonitor', '
{0}'.format('Error: Could not find USB device.')], preexec_fn=demote())


def identify_phone(observer, device):
    """Identifies if specific USB device (phone) is connected (tethered)."""


    global last_updated, initial_search, msg_count
    
    current_time = datetime.datetime.now()
    time_diff = current_time - last_updated


    if (time_diff.seconds > 300) or initial_search:
        try:
            time.sleep(0.25)
            tout = subprocess.check_output("lsusb | grep 1234:5678", shell=True)
        except subprocess.CalledProcessError:
            tout = None


        last_updated = datetime.datetime.now()
        initial_search = False


        get_network_data(tout)
        
    if time_diff.seconds > 10:
        msg_count = 1


    if not initial_search and msg_count == 1:
        wait_time = datetime.datetime.fromtimestamp(600 - time_diff.seconds)
        message = wait_time.strftime('You may have to wait %-M minute(s), %-S second(s) before another check is done.')
        print('Could not retrieve data from page...')
        full_msg = '
{0}'.format(message)
        inotify('Orange Balance', full_msg)
        #subprocess.call('notify-send', 'iMonitor:Identify Phone', '
{0}'.format(message)], preexec_fn=demote())
        msg_count += 1


try:
    initial_search = True
    last_updated = datetime.datetime.now()
    msg_count = 1
    try:
        from pyudev.glib import MonitorObserver


    except ImportError:
        from pyudev.glib import GUDevMonitorObserver as MonitorObserver


    context = Context()
    monitor = Monitor.from_netlink(context)


    monitor.filter_by(subsystem='usb')
    observer = MonitorObserver(monitor)


    observer.connect('device-added', identify_phone)
    monitor.start()


    glib.MainLoop().run()


except KeyboardInterrupt:
    print('
Shutdown requested.
Exiting gracefully...')
    sys.exit(0)

I have the above script. I would like it to run on boot such that when I tether my phone to my PC I get a desktop notification. I have created a service in /*etc/systemd/system/ named *imon.service with the following configuration


[Unit] 
Description=ImonService 

[Service] 
ExecStart=/path/to/imonitor.py 

[Install] 
WantedBy=multi-user.target 
**

**

However, since the script runs as root, I am having a hard time getting it to display a desktop notification to a user logged in via GUI.
Any help would be appreciated.

Other relevant data:
KDE Plasma Version: 5.5.5
OpenSUSE Leap 42.1**

While I may be too pessimistic and I did not read and understand what your Python script is doing, so I maybe wrong at some points. But I asume you have to re-think your approach.

Boot is NOT the same as login. That means that when you run something at boot, it is sure that no user is logged in at all at that moment in time, not in the GUI, not in the CLI.

When you run something as root and that program (script) wants to do something on the desktop of a user that is logged in into the GUI “in the seat”, be prepared that that is not easy (if possible at all). X normally blocks all access from outside to the desktop. Even from root. The DISPLAY environment variable for the process wanting access must be correct (to use the correct session (remember Unix/Linux is a multi-user, multi-session operating system) and, as said the owner of the session must grant access.

While I never tried such a thing with “phones”, I assume that what you try to do is typical for the user. Basically thus not root intervention should be required (the important security rule: never do as root what can be done without using root). So to mee it sounds more as something that must be run at the start of the desktop session of the user (and of course owned by that same user).

Just my thoughts.

In KDE Plasma, to run a script at login, you have to use the settings in “System Settings” → “Workspace” → “Startup and Shutdown” → “Autostart”.

If you really need to run this as the root user, then I have a bash script that can broadcast to the desktop user by using DBUS. It works most easily with X11. It can work with wayland if the user creates a dbus proxy on login (wayland has tighter security). It’s available as a github gist:

[https://gist.github.com/digitaltrails/26aad3282d8739db1de8bc2e59c812eb](http:// https://gist.github.com/digitaltrails/26aad3282d8739db1de8bc2e59c812eb)

 
#!/bin/bash
# Provides a way for a root process to perform a notify send for each
# of the local desktop users on this machine.
#
# Intended for use by cron and timer jobs. Arguments are passed straight
# to notify send.  Falls back to using wall.  Care must be taken to
# avoid using this script in any potential fast loops.
#
# X11 users should already have a dbus address socket at /run/user/<userid>/bus
# and this script should work without requiring any initialisation. Should
# this not be the case, X11 users could initilise a proxy as per the wayland
# instructions below.
#
# Due to stricter security requirments Wayland lacks an dbus socket 
# accessable to root.   Wayland users will need to run a proxy to 
# provide root with the necessary socket.  Each user can must add
# the following to a Wayland session startup script:
#
#      notify-desktop --create-dbus-proxy
#
# That will start xdg-dbus-proxy process and make a socket available under:
#      /run/user/<userid>/proxy_dbus_<desktop_sessionid>
#
# Once there is a listening socket, any root script or job can pass
# messages using the syntax of notify-send (man notify-send).
#
# Example messages
#      notify-desktop -a Daily-backup -t 0 -i dialog-information.png "Backup completed without error"
#      notify-desktop -a Remote-rsync -t 6000 -i dialog-warning.png "Remote host not currently on the network"
#      notify-desktop -a Daily-backup -t 0 -i dialog-error.png "Error running backup, please consult journalctl"
#      notify-desktop -a OS-Upgrade -t 0 -i dialog-warning.png "Update in progress, do not shutdown until further completion notice."
#
# Warnings:
#      1) There has only been limited testing on wayland
#      2) There has only been no testing for multiple GUI sessions on one desktop
#

if  $1 == "--create-dbus-proxy" ]
then
    if  -n "$DBUS_SESSION_BUS_ADDRESS" ]
    then
        sessionid=$(cat /proc/self/sessionid)
        xdg-dbus-proxy $DBUS_SESSION_BUS_ADDRESS /run/user/$(id -u)/proxy_dbus_$sessionid &
        exit 0
    else
        echo "ERROR: no value for DBUS_SESSION_BUS_ADDRESS environment variable - not a wayland/X11 session?"
        exit 1
    fi
fi


function find_desktop_session {
	for sessionid in $(loginctl list-sessions --no-legend | awk '{ print $1 }')
	do 
		loginctl show-session -p Id -p Name -p User -p State -p Type -p Remote -p Display $sessionid | 
            awk -F= '
                /[A-Za-z]+/ { val$1] = $2; } 
                END { 
                    if (val"Remote"] == "no" && 
                    val"State"] == "active" && 
                    (val"Type"] == "x11" || val"Type"] == "wayland")) {
                        print val"Name"], val"User"], val"Id"];
                    } 
                }'
	done
}

count=0
while read -r -a desktop_info
do
    if  ${#desktop_info@]} -eq 3 ]
    then
        desktop_user=${desktop_info[0]}
        desktop_id=${desktop_info[1]}
        desktop_sessionid=${desktop_info[2]}
        proxy_bus_socket="/run/user/$desktop_id/proxy_dbus_$desktop_sessionid"
        if  -S $proxy_bus_socket ]
        then
            bus_address="$proxy_bus_socket"
        else
            bus_address="/run/user/$desktop_id/bus"
        fi
        sudo -u $desktop_user DBUS_SESSION_BUS_ADDRESS="unix:path=$bus_address" notify-send "$@" 
        count=$[count + 1]
    fi
done <<<$(find_desktop_session)

# If no one has been notified fall back to wall
if  $count -eq 0 ]
then
    echo "$@" | wall
fi

# Don't want this to cause a job to stop
exit 0