Linux: Triggering desktop notification pop-up when service is executed with systemd - linux

I want to trigger a desktop notification pop-up when a service is executed with systemd on my Linux desktop. The main reason why I am doing this is that I want to learn how to work with systemd timers and services by creating my own scheduled jobs and I would like to pop-up a desktop notification, when a service/job is executed, just to know that something is happening.
I have created a basic example to do that:
notifysystemd.sh:
#!/bin/bash
# Variable to hold path to systemd job logs
SYSTEMD_LOG_DIR='/home/jay/scheduledJobLogs/systemDJobLogs'
SYSTEMD_JOB_NAME='NotifySystemD'
CURRENT_MONTH=$(date '+%b')
# Send notification to desktop
notify-send 'You can automate and schedule anything with systemd today!'
# Write down in the log
CURRENT_TIME=$(date '+%Y-%m-%d-%H:%M')
LOG_RECORD="${CURRENT_TIME} SystemD notification job executed."
# Create a directory for systemd jobs logging, if it doesn't already exist. And don't error if it does exist
mkdir -p $SYSTEMD_LOG_DIR/$SYSTEMD_JOB_NAME
# Write the log record!
echo $LOG_RECORD >> $SYSTEMD_LOG_DIR/$SYSTEMD_JOB_NAME/$CURRENT_MONTH.txt
with this service file:
notifysystemd.service:
[Unit]
Description=A basic service to send a desktop notification using the systemd scheduler
Wants=notifysystemd.timer
[Service]
Type=forking
ExecStart=/home/jay/systemDJobs/notifysystemd.sh
Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus" "XAUTHORITY=/home/jay/.Xauthority"
[Install]
WantedBy=default.target
and this timer file:
notifysystemd.timer:
[Unit]
Description=Send a notification three minutes after PC start
RefuseManualStart=false # Allow manual starts
RefuseManualStop=false # Allow manual stops
[Timer]
#Execute job if it missed a run due to machine being off
Persistent=true
OnBootSec=180
#File describing job to execute
Unit=notifysystemd.service
[Install]
WantedBy=timers.target
The service is executed correctly with the correct delay (I can see that in the log created), but I am getting no desktop notification.
I have looked into several questions already asked on this forum:
systemd service not executing notify-send
notify-send command doesn't launch the notification through systemd service
Which suggest specifying environment variables in either the .service file or in the shell script.
I have tried all of them and none led to a notification appearing.
I have done the same with cronie, where was sufficient to specify the DISPLAY and DBUS_SESSION_BUS_ADDRESS environement variables the same way as I did in the notifysystemd.service file.
Lastly, if there is a better way how to achieve the same result, but which revolves around usage of systemd, I am opened to optimal, or more ergonomic solutions.

In systemd instead of notify-send put the output in a file.
Write a bash script and start it on desktop login (so this script can notify-send).
In the script write an endless loop.
Use inotifywait in the loop to monitor the output file for modification.
(This will suspend your script and won't eat cpu)
After inotify, read the content of the file and use notify-send.

Related

Is there a way to make crontab run a gnu screen session?

I have a discord bot running on a raspberry pi that i need to restart every day, I'm trying to do this through crontab but with no luck.
It manages to kill the active screen processes but never starts an instance, not that I can see with "screen -ls" and I can tell that it doesn't create one that I can't see as the bot itself does not come online.
Here is the script:
#!/bin/bash
sudo pkill screen
sleep 2
screen -dmS discordBot
sleep 2
screen -S "discordBot" -X stuff "node discordBot/NEWNEWNEWN\n"
Here is also the crontab:
0 0 * * * /bin/bash /home/admin/discordBot/script.sh
Is it possible to have crontab run a screen session? And if so how?
Previously I tried putting the screen command stright into cron but now I have it in a bash script instead.
If I run the script in the terminal it works perfectly, it’s just cron where it fails. Also replacing "screen" with the full path "/usr/bin/screen" does not change anything.
So the best way of doing it, without investigating the underlying problem would be to create a systemd service and putting a restart command into cron.
 
/etc/systemd/system/mydiscordbot.service:
[Unit]
Description=A very simple discord bot
[Service]
Type=simple
ExecStart=node /path/to/my/discordBot.js
[Install]
WantedBy=multi-user.target
Now you can run your bot with systemctl start mydiscordbot and can view your logs with journalctl --follow -u mydiscord bot
Now you only need to put
45 2 * * * systemctl restart discordbot
into root's crontab and you should be good to go.
You probably should also write the logs into a logfile, maybe /var/log/mydiscordbot.log, but how and if you do that is up to you.
OLD/WRONG ANSWER:
cron is run with a minimal environment, and depending on your os, /usr/bin/ is probably not in the $PATH var. screen is mostlikely located at /usr/bin/screen, so cron can't run it because it can't find the screen binary. try replacing screen in your script with /usr/bin/screen
But the other question here is: Why does your discord bot need to be restarted every day. I run multiple bots with 100k+ users, and they don't need to be restarted at all. Maybe you should open a new question with the error and some code snippets.
I don't know what os your rpi is running, but I had a similar issue earlier today trying to get vms on a server to launch a terminal and run a python script with crontab. I found a very easily solution to automatically restart it on crashes, and to run something simply in the background. Don't rely on crontab or an init service. If your rpi as an x server running, or anything that can be launched on session start, there is a simple shell solution. Make a bash script like
while :; do
/my/script/to/keep/running/here -foo -bar -baz >> /var/log/myscriptlog 2>&1
done
and then you would start it on .xprofile or some similar startup operation like
/path/to/launcher.sh &
to have it run the background. It will restart automatically everytime it closes if started in something like .xprofile, .xinitrc, or anything ran at startup.
Then maybe make a cronjob to restart the raspberry pi or whatever system is running the script but this script wil restart the service whenever it's closed anyway. Maybe you can put that cronjob on a script but I don't think it would launch the GUI.
Some other things you can do to launch a GUI terminal in my research with cronjob that you can try, though they didn't work for my situation on these custom linux vms, and that I read was a security risk to do this from a cronjob, is you can specify the display.
* * * * * DISPLAY=:0 /your/gui/stuff/here
but you would would need to make sure the user has the right permissions and it's considered an unsafe hack to even do this as far as I have read.
for my issue, I had to launch a terminal that stayed open, and then changed to the directory of a python script and ran the script, so that the local files in directory would be called in the python script. here is a rough example of the "launcher.sh" I called from the startup method this strange linux distro used lol.
#!/bin/sh
while :; do
/usr/bin/urxvt -e /bin/bash -c "cd /path/to/project && python loader.py"
done
Also check this source for process management
https://mywiki.wooledge.org/ProcessManagement

Battery reminder POSIX script having issues with DBUS

I wrote a POSIX shell script to remind me about my battery life using notify-send and a cronjob, but I'm having problems with DBUS stuff
This is what the script looks like
#!/bin/sh
percent=`upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep percentage:
notify-send "battery life" "$percent"
It works as intended, and pops up with this notification when called.
I wrote the script because my window manager, i3 lacks a notification system for battery status, so I found myself running out of battery on my laptop even though I was right next to an outlet at home.
Of course; having a script like this is pointlesss unless it's automated to pop up by itself, so after some fiddling, I set up a Cron-job that runs the script every 10 minutes.
This is what the cron-tab looks like:
*/10 * * * * export DISPLAY=:0 ; export DBUS_SESSION_BUS_ADDRESS=a; batterystatus.sh
It works, except that without the little snippet about the DBUS_SESSION_BUS_ADDRESS stuff, for some reason notify-status doesn't work.
So, everything was cool until I rebooted and found that this value used in the cron-tab: unix:abstract=/tmp/dbus-FOSTebXqX5,guid=a7ad198d91d224b8c056efc6615a3610 changes upon boot.
That means that I would have to change the cron-job everytime I boot up my computer so that the script would work. Is there any way around this?
Rather than use crontab, it might be better to use systemd. You can have a systemd user service and timer so that D-Bus has access to the session D-Bus information for session notifications.
I've tested this out using the Python dbus library pydbus as an example bus I'm sure you could use your shell script also.
Python script for getting battery power and posting notifications:
#!/usr/bin/python3
import pydbus
sys_bus = pydbus.SystemBus()
ses_bus = pydbus.SessionBus()
power = sys_bus.get('.UPower', '/org/freedesktop/UPower/devices/battery_BAT0')
notifications = ses_bus.get('.Notifications')
print(power.Percentage)
notifications.Notify('test', 0, 'dialog-information', "Battery Notification!",
f"Percentage: {power.Percentage}%", [], {}, 5000)
Created a user service in /etc/systemd/user/battery.service:
[Unit]
Description=Check for battery percentage
[Service]
Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=/home/thinkabit1/stack_overflow/battery_monitor.py
You can test this works with:
$ systemctl --user start battery.service
To see the status of the service do:
$ systemctl --user status battery.service
To have a timer run this every 10 minutes create /etc/systemd/user/battery.timer
[Unit]
Description=Run battery monitor every 10 minutes
[Timer]
OnBootSec=10min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target
This can be started with:
$ systemctl --user start battery.timer
To see the status:
$ systemctl --user list-timers
To have it start automatically after reboot use:
systemctl --user enable battery.timer

Is it possible to pass input to a running service or daemon?

I want to create a Java console application that runs as a daemon on Linux, I have created the application and the script to run the application as a background daemon. The application runs and waits for command line input.
My question:
Is it possible to pass command line input to a running daemon?
On Linux, all running processes have a special directory under /proc containing information and hooks into the process. Each subdirectory of /proc is the PID of a running process. So if you know the PID of a particular process you can get information about it. E.g.:
$ sleep 100 & ls /proc/$!
...
cmdline
...
cwd
environ
exe
fd
fdinfo
...
status
...
Of note is the fd directory, which contains all the file descriptors associated with the process. 0, 1, and 2 exist for (almost?) all processes, and 0 is the default stdin. So writing to /proc/$PID/fd/0 will write to that process' stdin.
A more robust alternative is to set up a named pipe connected to your process' stdin; then you can write to that pipe and the process will read it without needing to rely on the /proc file system.
See also Writing to stdin of background process on ServerFault.
The accepted answer above didn't quite work for me, so here's my implementation.
For context I'm running a Minecraft server on a Linux daemon managed with systemctl. I wanted to be able to send commands to stdin (StandardInput).
First, use mkfifo /home/user/server_input to create a FIFO file somewhere (also known as the 'named pipe' solution mentioned above).
[Service]
ExecStart=/usr/local/bin/minecraft.sh
StandardInput=file:/home/user/server_input
Then, in your daemon *.service file, execute the bash script that runs your server or background program and set the StandardInput directive to the FIFO file we just created.
In minecraft.sh, the following is the key command that runs the server and gets input piped into the console of the running service.
tail -f /home/user/server_input| java -Xms1024M -Xmx4096M -jar /path/to/server.jar nogui
Finally, run systemctl start your_daemon_service and to pass input commands simply use:
echo "command" > /home/user/server_input
Creds to the answers given on ServerFault

User input during a systemctl/service call for CentOS?

Lets say I have a service (like rsyslog) that when I stop it, I want to log the reasoning behind someone shutting it down. Expressing this simply would be
systemctl start rsyslog
systemctl stop rsyslog
(Begin the prompt as to why a user is doing this after shutting down rsyslog)
#!/bin/bash
echo "you are shutting down the syslog service. Please state your name and reason."
read -p "[name?]" name && read -p "[reason?]" reason
logger $name:$reason
Modifying the Unit Files (located in /usr/lib/systemd/system/rsyslog.service), to include an ExecStop of the path to the script, I am able to run the above script. I know that the script is working as checking the log messages shows a :, the nonvariable portion that was passed to logger.
I need to be able to have this script operate like said shell script the instant someone attempts to shutdown the logging service. This means that echo commands are shown on the terminal and variables can be recorded using the read command.
This may be similar to this persons question, but I can not understand it.
Thanks to Mark Stosberg for sharing information about the systemd-ask-password command that takes user input during a systemctl call.
For those unaware, the systemd-ask-password command is a password prompt that's available for all machines sponsoring the systemd service. A unique feature of this command is the fact it can allow for user input during a systemctl/service call. Knowing this, one can prompt for as much data as they like which integrates perfectly into standard bash scripts, allowing for the interaction that may or may not be needed during a call.
Here is an example script:
#!/bin/bash
date=$(/bin/date)
echo "Rsyslog has been turned off at $date. Below is the name and reason"
name=`systemd-ask-password --echo --no-tty "name:"`
reason=`systemd-ask-password --echo --no-tty "reason for shutting down rsyslog:"`
echo LOG:$name:$reason:ENDLOG >>/var/log/messages
You must make sure that when initializing any of your services you make changes to the unit files located in /usr/lib/systemd/system/ with an ExecStart, ExecStop, and so forth under the [Service] tag to equal the path to your scripts. You can find what other options you can here as well as some syntax, and tie in with the unit file as needed.

Arch Linux / systemd - prevent any kind of shutdown/rebboot

I'm running Arch-based Manjaro Linux and wrote myself a little update program, that starts every 7 hours and runs completely in the background. This update program is started by systemd.
What I wanna know is: How can I prevent any system shutdown/reboot during the time this program runs no matter if the user just wants to turn it off or any program wants to do so.
The best would be, if any shutdown/reboot action wouldn't be cancelled but delayed instead, so when the update program has finished its run, the shutdown/reboot continues.
My systemd parts are:
uupgrades.timer
[Unit]
Description=UU Upgrades Timer
[Timer]
OnBootSec=23min
OnUnitActiveSec=7h
Unit=uupgrades.target
[Install]
WantedBy=basic.target
uupgrades.target
[Unit]
Description=UU Upgrades Timer Target
StopWhenUnneeded=yes
and in the folder uupgrades.target.wants
uupgrades.service
[Unit]
Description=UU Update Program
[Service]
Nice=19
IOSchedulingClass=2
IOSchedulingPriority=7
ExecStart=/usr/bin/uupgrades
How can I achieve this?
If a user with sufficient permissions to reboot the server or manipulate processes wants to stop or reboot the machine you cant stop them. That's just how linux works. You should set up permissions and accounts such that no other users have root permissions or permissions sufficient to manipulate the process or user that the process is running as.
When I want to block myself from rebooting or shutdown, I alias my usual shutdown and reboot aliases to beep;beep;beep;.
In multiuser environments you could move the reboot, shutdown etc. binaries and move them back, when shutdown should be allowed again.
You could also temporarily move an executable shell script outputting information about the postponed shutdown possibility in place of the corresponding binaries. This script could set a flag, if a shutdown was requested.
Q&D example script:
#!/usr/bin/env bash
echo "preventing reboot"
BACKUPBINARY_REBOOT=$(mktemp);
mv /bin/reboot $BACKUPBINARY_REBOOT;
FLAGFILE=$(mktemp);
echo '#!/usr/bin/env bash' > /bin/reboot;
echo '# original reboot binary was moved to'"$BACKUPBINARY_REBOOT" >> /bin/reboot;
echo 'echo request-reboot > '"$FLAGFILE" >> /bin/reboot;
echo 'echo reboot is prevented, your request will trigger later' >> /bin/reboot;
chmod 666 "$FLAGFILE";
chmod +x /bin/reboot;
echo "postponed reboot - press enter to allow it again and make up for requested reboot";
read;
mv "$BACKUPBINARY_REBOOT" /bin/reboot;
if grep -q request-reboot "$FLAGFILE"; then
rm $FLAGFILE;
/bin/reboot;
fi
You can add another systemd service at /usr/lib/systemd/system-shutdown/ which will be run at shutdown, and have it check if your update script is running, and if so, cancel or delay the shutdown.

Resources